크리티컬섹션 :: 'Phaser JS' 카테고리의 글 목록 (4 Page)

달력

92024  이전 다음

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

이 튜토리얼의 목표는 기본 멀티 플레이어 HTML5 게임을 만드는 방법을 보여주는 것입니다. 게임 개발을 위해 Phaser를 사용하고 클라이언트 / 서버 통신을 위해 Eureca.io를 사용합니다. 이 튜토리얼에서는 HTML5 게임 개발에 대한 지식 (Preference에서 Phaser로 이미 알고있는 것으로 생각합니다)을 가정합니다. 또한 nodej에 대한 지식이 있고 이미 설치했다고 가정합니다. 내가 사용할 게임 코드는 페이저 웹 사이트의 Tanks 예제 게임을 기반으로합니다. 이 비디오는 최종 결과를 보여줍니다.



첫 번째 단계 : 코드 리팩터링

멀티 플레이어에 적합하도록 수정 및 단순화 되었습니다.  멀티 플레이어 모드에서 적 탱크는 단지 원격 플레이어이기 때문에 Tank라는 클래스의 플레이어와 적 탱크 코드를 분해했습니다 (이것은 Phaser 예제 코드에서 EnemyTank 클래스로 이름이 변경되었습니다). 생성 및 업데이트 코드가 Tank 클래스로 이동되었습니다.

Tank = function (index, game, player) {

this.cursor = {

left:false,

right:false,

up:false,

fire:false

}

  this.input = {

left:false,

right:false,

up:false,

fire:false

}

    var x = 0;

    var y = 0;

    this.game = game;

    this.health = 30;

    this.player = player;

    this.bullets = game.add.group();

    this.bullets.enableBody = true;

    this.bullets.physicsBodyType = Phaser.Physics.ARCADE;

    this.bullets.createMultiple(20, 'bullet', 0, false);

    this.bullets.setAll('anchor.x', 0.5);

    this.bullets.setAll('anchor.y', 0.5);

    this.bullets.setAll('outOfBoundsKill', true);

    this.bullets.setAll('checkWorldBounds', true);

    this.currentSpeed =0;

    this.fireRate = 500;

    this.nextFire = 0;

    this.alive = true;

    this.shadow = game.add.sprite(x, y, 'enemy', 'shadow');

    this.tank = game.add.sprite(x, y, 'enemy', 'tank1');

    this.turret = game.add.sprite(x, y, 'enemy', 'turret');

    this.shadow.anchor.set(0.5);

    this.tank.anchor.set(0.5);

    this.turret.anchor.set(0.3, 0.5);

    this.tank.id = index;

    game.physics.enable(this.tank, Phaser.Physics.ARCADE);

    this.tank.body.immovable = false;

    this.tank.body.collideWorldBounds = true;

    this.tank.body.bounce.setTo(0, 0);

    this.tank.angle = 0;

    game.physics.arcade.velocityFromRotation(this.tank.rotation, 0, this.tank.body.velocity);

};


Tank.prototype.update = function() {      

    for (var i in this.input) this.cursor[i] = this.input[i];    

    if (this.cursor.left)

    {

        this.tank.angle -= 1;

    }

    else if (this.cursor.right)

    {

        this.tank.angle += 1;

    }  

    if (this.cursor.up)

    {

        //  The speed we'll travel at

        this.currentSpeed = 300;

    }

    else

    {

        if (this.currentSpeed > 0)

        {

            this.currentSpeed -= 4;

        }

    }

    if (this.cursor.fire)

    {  

        this.fire({x:this.cursor.tx, y:this.cursor.ty});

    }

    if (this.currentSpeed > 0)

    {

        game.physics.arcade.velocityFromRotation(this.tank.rotation, this.currentSpeed, this.tank.body.velocity);

    }  

    else

    {

        game.physics.arcade.velocityFromRotation(this.tank.rotation, 0, this.tank.body.velocity);

    }  

    this.shadow.x = this.tank.x;

    this.shadow.y = this.tank.y;

    this.shadow.rotation = this.tank.rotation;

    this.turret.x = this.tank.x;

    this.turret.y = this.tank.y;

};

또한 게임 장면에서 탱크를 제거하기 위해 Tank.kill 메서드를 추가하고 fire () 코드를 Tank.fire로 이동했습니다.

Tank.prototype.fire = function(target) {

if (!this.alive) return;

        if (this.game.time.now > this.nextFire && this.bullets.countDead() > 0)

        {

            this.nextFire = this.game.time.now + this.fireRate;

            var bullet = this.bullets.getFirstDead();

            bullet.reset(this.turret.x, this.turret.y);

  bullet.rotation = this.game.physics.arcade.moveToObject(bullet, target, 500);

        }

}

Tank.prototype.kill = function() {

this.alive = false;

this.tank.kill();

this.turret.kill();

this.shadow.kill();

}

또한 단순화를 위해 피해를 처리하는 부분을 제거 했으므로 다른 탱크를 쏘아도 그들을 죽지 않을 것입니다. 하나의 마지막 변경은 플레이어 입력이 처리되는 방식으로 이루어졌습니다. 예제 코드는 (game.input.keyboard.createCursorKeys ()를 통해) 직접 phaser.input 네임 스페이스를 사용하지만, 우리의 경우 클라이언트가 직접 입력을 처리해서는 안됩니다. 그래서 플레이어 입력을 처리하기 위해 Tank.input 객체와 Tank.cursor 객체를 만들었습니다. 다음은 수정 된 update () 함수의 코드입니다.

function update () {

player.input.left = cursors.left.isDown;

player.input.right = cursors.right.isDown;

player.input.up = cursors.up.isDown;

player.input.fire = game.input.activePointer.isDown;

player.input.tx = game.input.x+ game.camera.x;

player.input.ty = game.input.y+ game.camera.y;

turret.rotation = game.physics.arcade.angleToPointer(turret);

    land.tilePosition.x = -game.camera.x;

    land.tilePosition.y = -game.camera.y;

    for (var i in tanksList)

    {

if (!tanksList[i]) continue;

var curBullets = tanksList[i].bullets;

var curTank = tanksList[i].tank;

for (var j in tanksList)

{

if (!tanksList[j]) continue;

if (j!=i)

{

var targetTank = tanksList[j].tank;

game.physics.arcade.overlap(curBullets, targetTank, bulletHitPlayer, null, this);

}

if (tanksList[j].alive)

{

tanksList[j].update();

}

}

    }

}

여기에서 리팩토링 된 코드를 다운로드하면 다음 튜토리얼 단계를 수행하는 데 도움이됩니다.

이제는 간단한 코드 만 만들어서 멀티 플레이어 게임으로 변환 해 보겠습니다.


***************************************************************************************

내부 요구 사항을 위해 개발 한 RPC 라이브러리 인 Eureca.io를 사용하고 소스 코드 (https://github.com/Ezelia/eureca.io에서 사용할 수있는 소스 코드)를 열어보기로했습니다.  여러 네트워킹 라이브러리 (socket.io, engine.io ... 등)가 있지만 Eureca.io가 일을 더 단순하게 만드는 방법을 보게됩니다 🙂

***************************************************************************************



웹 서버

게임을위한 기본적인 웹 서버를 만드는 것으로 시작하겠습니다. 여기서 nodejs 를 위한 익스프레스 라이브러리를 설치하고, 파일 서비스를 더 간단하게 만들면, eureca.io와 호환됩니다. Express는 멀티 플레이어 게임 (동적 인 웹 페이지, 세션, 쿠키, 양식 등을 처리 할 수있는 웹 페이지)을 구축하는 경우에도 도움이됩니다.

npm install express



Tank 게임의 루트 디렉토리에 server.js 파일을 만들고 다음 코드로 편집하십시오

var express = require('express')

  , app = express(app)

  , server = require('http').createServer(app);

// serve static files from the current directory

app.use(express.static(__dirname));

server.listen(8000);

브라우저를 열고 http : // localhost : 8000 /로 이동하십시오. Tank 게임이 잘 작동한다면 다음 단계로 넘어가거나 여기에서 코드를 다운로드 하면됩니다.



eureca.io 설치 및 준비

이제 Eureca.io로 게임을 시작해보십시오. engine.io가 사용되는 엔진 전송 레이어로 engine.io 또는 sockjs를 사용할 수 있습니다. eureca.io를 기본 구성으로 사용하려면 eureca.io와 engine.io를 설치해야합니다.

npm install engine.io

npm install eureca.io


이제 eureca.io를 추가하기 위해 서버 코드를 수정합니다 : server.listen (8000)

eureca.io 서버를 인스턴스화하고 다음 코드를 사용하여 HTTP 서버에 연결합니다.

//get EurecaServer class

var EurecaServer = require('eureca.io').EurecaServer;

//create an instance of EurecaServer

var eurecaServer = new EurecaServer();

//attach eureca.io to our http server

eurecaServer.attach(server);


그런 다음 클라이언트 연결과 연결 해제를 감지하는 이벤트 리스너를 추가합니다

//detect client connection

eurecaServer.onConnect(function (conn) {  

    console.log('New Client id=%s ', conn.id, conn.remoteAddress);

});

//detect client disconnection

eurecaServer.onDisconnect(function (conn) {  

    console.log('Client disconnected ', conn.id);

});


클라이언트 측에서는 tanks.js 스크립트보다 먼저 index.html에 다음 행을 추가합니다.

<script src="/eureca.js"></script>


이렇게 하면 클라이언트가 eureca.io를 사용할 수 있게됩니다. 이제 tanks.js 파일을 편집하고 처음에 다음 코드를 추가하십시오

var ready = false;

var eurecaServer;

//this function will handle client communication with the server

var eurecaClientSetup = function() {

//create an instance of eureca.io client

var eurecaClient = new Eureca.Client();

eurecaClient.ready(function (proxy) {

eurecaServer = proxy;

//we temporary put create function here so we make sure to launch the game once the client is ready

create();

ready = true;

});

}


여기서 우리가 하는 일은 eureca.io 클라이언트를 인스턴스화하고 클라이언트가 준비 될 때까지 기다린 다음 클라이언트가 초기화 될 때까지 기다리는 클라이언트 초기화 메소드 "eurecaClientSetup"을 작성한 다음 create () 메소드가 Phaser에 의해 처음 호출 된 게임 작성 메소드 (create ())를 호출하는 것입니다.

Game () 인스턴스화 메소드는 이 행을 수정하여 eurecaClientSetup을 호출합니다.

var game = new Phaser.Game(800, 600, Phaser.AUTO, 'phaser-example', { preload: preload, create: eurecaClientSetup, update: update, render: render });


중요 : 멀티 플레이어 게임을 만드는 경우 일반적으로 게임 코드를 시작하기 전에 서버를 사용할 수 있는지 확인해야합니다. 이것이 바로 eurecaClientSetup에서 수행하는 작업입니다.


마지막 한가지. ready 변수가 false로 설정된 것을 보았을 때 클라이언트 / 서버 초기화가 완료되었는지를 알 수 있습니다.

게임이 만들어졌습니다. 우리는 phaser가 create () 전에 update methode를 호출하는 것을 막기 위해 이를 사용합니다. 그래서 우리는 update () 메소드에 다음을 추가 할 필요가있다.

function update () {

//do not update if client not ready

if (!ready) return;

}


여기서 결과 코드를 다운로드 할 수 있습니다 : 탱크 게임 다운로드 2 단계 코드

server.js를 다시 (노드 server.js) 실행하고 http : // localhost : 8000 / 게임을 시작하면 서버가 클라이언트 연결을 감지했음을 알 수 있습니다. 이제 페이지를 새로 고침하면 클라이언트가 연결 해제 된 후 다시 연결되었음을 알 수 있습니다. 이것이 작동하면 다음 단계로 넘어갈 준비가되었습니다.



원격 플레이어 스폰/죽음

기본 멀티 플레이어 게임의 경우 서버는 연결된 모든 클라이언트를 추적해야합니다. 모든 클라이언트를 구별하기 위해 우리는 또한 각 플레이어에 대해 고유 식별자를 가질 필요가 있습니다 (우리는 eureca.io가 생성 한 고유 ID를 사용합니다). 이 고유 ID는 클라이언트와 서버간에 공유됩니다. 플레이어 데이터를 동기화하고 원격 클라이언트와 탱크를 연결할 수 있습니다.

구현은 다음과 같습니다.

- 새 클라이언트가 연결되면 서버는 uniq id (여기서는 eureca.io 세션 ID가 사용됨)

- 서버는이 uniq id를 클라이언트에 보낸다.

- 클라이언트는 플레이어의 탱크로 게임 장면을 만들고 그 고유의 ID를 탱크에 할당합니다.

- 클라이언트는 클라이언트 측에서 모든 것이 준비되었다는 것을 서버에 알립니다 (우리는 이것을 핸드 쉐이크라고 부릅니다)

- 서버는 각 연결된 플레이어에 대해 알림 및 호출 클라이언트 Spawn 메서드를 가져옵니다.

- 클라이언트는 연결된 각 플레이어에 대해 Tank 인스턴스를 생성합니다.

- 클라이언트가 연결을 끊으면 서버가이를 식별하여 연결된 클라이언트 목록에서 제거합니다.

- 모든 연결된 클라이언트의 서버단에서 Kill () 메서드 호출

- 각 클라이언트는 연결이 끊긴 플레이어의 Tank 인스턴스를 제거합니다.



클라이언트 사이드

Eureca.io 인스턴스에는 "exports"라는 특수한 네임 스페이스가 있습니다.

이 네임 스페이스 아래 정의 된 모든 메소드는 RPC에서 사용할 수있게됩니다. 우리는 그것을 어떻게 사용하는지 보게 될 것이다. 이를 위해 eurecaClientSetup 메소드를 수정해야합니다.

var eurecaClientSetup = function() {

//create an instance of eureca.io client

var eurecaClient = new Eureca.Client();

eurecaClient.ready(function (proxy) {

eurecaServer = proxy;

});

//methods defined under "exports" namespace become available in the server side

eurecaClient.exports.setId = function(id)

{

//create() is moved here to make sure nothing is created before uniq id assignation

myId = id;

create();

eurecaServer.handshake();

ready = true;

}

eurecaClient.exports.kill = function(id)

{

if (tanksList[id]) {

tanksList[id].kill();

console.log('killing ', id, tanksList[id]);

}

}

eurecaClient.exports.spawnEnemy = function(i, x, y)

{

if (i == myId) return; //this is me

console.log('SPAWN');

var tnk = new Tank(i, game, tank);

tanksList[i] = tnk;

}

}

위의 예제에서 setId, kill 및 spawnEnemy와 같이 서버 측에서 호출 할 수있는 세 가지 메소드가 있습니다.

클라이언트가 원격 서버 기능 : eurecaServer.handshake ()를 호출 중임을 참고하십시오.



서버 측

클라이언트 메소드 (setId, kill 및 spawnEnemy)가 신뢰할 수있는 클라이언트 함수라는 것을 Eureca.io에게 알려주는 첫 번째 사항은 그렇지 않습니다. 그렇지 않으면 eureca.io는 클라이언트 / 서버 개발에서 클라이언트 메소드를 호출하지 않으므로 클라이언트를 맹목적으로 신뢰해서는 안됩니다. 다음 코드는 Eureca.io에게 이러한 메소드를 신뢰하고 클라이언트 데이터를 보유 할 clientList 객체를 생성하도록 지시합니다.

var eurecaServer = new EurecaServer({allow:['setId', 'spawnEnemy', 'kill']});

var clients = {};


이제 onConnect 및 onDisconnect 메소드를 수정 해 봅시다.

//detect client connection

eurecaServer.onConnect(function (conn) {  

    console.log('New Client id=%s ', conn.id, conn.remoteAddress);

//the getClient method provide a proxy allowing us to call remote client functions

    var remote = eurecaServer.getClient(conn.id);  

//register the client

clients[conn.id] = {id:conn.id, remote:remote}

//here we call setId (defined in the client side)

remote.setId(conn.id);

});

//detect client disconnection

eurecaServer.onDisconnect(function (conn) {  

    console.log('Client disconnected ', conn.id);

var removeId = clients[conn.id].id;

delete clients[conn.id];

for (var c in clients)

{

var remote = clients[c].remote;

//here we call kill() method defined in the client side

remote.kill(conn.id);

}

});


서버가 원격 클라이언트 기능을 호출하는 방법에 유의하십시오 : remote.setId (conn.id) 및 remote.kill (conn.id);


기억한다면 클라이언트도 서버 측 메서드를 호출합니다. 여기에 선언하는 방법이 있습니다.

eurecaServer.exports.handshake = function()

{

//var conn = this.connection;

for (var c in clients)

{

var remote = clients[c].remote;

for (var cc in clients)

{

remote.spawnEnemy(clients[cc].id, 0, 0);

}

}

}


이제 서버를 시작하고 http : // localhost : 8000에서 첫 번째 브라우저 창을 열고, 탱크를 조금 옮기고 http : // localhost : 8000 /에서 다른 브라우저 창을 엽니다.

첫 번째 창에서 탱크가 생성되는 것을 볼 수 있습니다. 마지막 창을 닫으면 탱크가 사라집니다. 이것은 꽤 좋았지만 여전히 멀티 플레이어 게임은 아닙니다. 탱크 운동은 아직 재결합되지 않았고, 이것은 우리가 다음 단계에서 할 것입니다. 그건 그렇고, 여기에 위의 단계 😀의 전체 코드입니다



입력 처리 / 상태 동기화

멀티 플레이어 게임에서 서버는 클라이언트 상태를 제어해야 하며 유일한 신뢰할 수있는 엔터티는 서버입니다. (P2P 게임과 같은 몇 가지 다른 변종이 있지만 여기에서는 설명하지 않겠습니다 🙂)

클라이언트 / 서버 게임의 이상적인 구현은 클라이언트와 서버가 모두 동작을 시뮬레이션하면서 서버가 클라이언트에 상태 데이터를 보냅니다.

현지 지위를 수정 / 보완 할 것입니다. 이 예에서는 최소한의 정보 만 동기화 할것입니다.

플레이어가 입력 (이동 또는 공격)을 하면 지역 코드로 직접 처리되지 않습니다.

대신 서버에 보내면 서버는 연결된 모든 클라이언트에서 처리하여 다시 클라이언트 입력을 보냅니다.

각 클라이언트는 이 입력을 탱크의 클라이언트 측 사본에 적용합니다.

탱크는 로컬 입력에 의해 발행 된대로 서버가 보낸 입력을 처리합니다.


이 외에도 입력 정보가 전송 될 때마다 탱크 위치에 대한 정보를 보내고, 이 정보는 탱크 상태를 연결된 모든 클라이언트와 동기화하는 데 사용됩니다. 이것을 처리 할 코드를 작성해 보겠습니다.

eurecaClient.exports.updateState = function(id, state)

{

if (tanksList[id])  {

tanksList[id].cursor = state;

tanksList[id].tank.x = state.x;

tanksList[id].tank.y = state.y;

tanksList[id].tank.angle = state.angle;

tanksList[id].turret.rotation = state.rot;

tanksList[id].update();

}

}



중요포인트 : exports 메소드를 서버에서 호출 할 수 있습니다. updateState 메서드는 공유 플레이어 입력으로 Tank.cursor를 업데이트하지만 탱크 위치와 각도도 수정합니다. 이제 우리는 Tank.update 메소드에서 이것을 처리 할 필요가 있습니다. Tank.prototype.update를 편집하고 다음 라인을 대체하십시오

for (var i in this.input) this.cursor[i] = this.input[i];


이 코드와 함께

var inputChanged = (

this.cursor.left != this.input.left ||

this.cursor.right != this.input.right ||

this.cursor.up != this.input.up ||

this.cursor.fire != this.input.fire

);

if (inputChanged)

{

//Handle input change here

//send new values to the server

if (this.tank.id == myId)

{

// send latest valid state to the server

this.input.x = this.tank.x;

this.input.y = this.tank.y;

this.input.angle = this.tank.angle;

this.input.rot = this.turret.rotation;

eurecaServer.handleKeys(this.input);

}

}

여기에서 로컬 플레이어가 입력 (마우스 클릭 또는 키보드 왼쪽 / 오른쪽 / 위로)을 입력 한 경우 서버에서 직접 처리하는 대신 eurecaServer.handle을 사용하여 서버 쪽 handleKeys 메서드가 입력을 다시 전송하여 모든 연결된 클라이언트에게 알려줍니다.



서버 측

먼저 새로 선언 된 클라이언트 메소드 (updateState)를 허용해야합니다.

var eurecaServer = new EurecaServer({allow:['setId', 'spawnEnemy', 'kill', 'updateState']});


그런 다음 handleKeys 메소드를 선언합니다.

eurecaServer.exports.handleKeys = function (keys) {

var conn = this.connection;

var updatedClient = clients[conn.id];

for (var c in clients)

{

var remote = clients[c].remote;

remote.updateState(updatedClient.id, keys);

//keep last known state so we can send it to new connected clients

clients[c].laststate = keys;

}

}


그리고 기존의 핸드 쉐이크 방식에 대에 약간 수정합니다.

eurecaServer.exports.handshake = function()

{

for (var c in clients)

{

var remote = clients[c].remote;

for (var cc in clients)

{

//send latest known position

var x = clients[cc].laststate ? clients[cc].laststate.x:  0;

var y = clients[cc].laststate ? clients[cc].laststate.y:  0;

remote.spawnEnemy(clients[cc].id, x, y);

}

}

}

모든 단계를 따르거나 (link 링크에서 최종 코드를 다운로드 한 경우) 서버를 시작하고 두 개 이상의 창을 엽니다. 이제 한 클라이언트에서 탱크를 움직이거나 발사체를 발사하면 다른 창으로 이동합니다.


다음은?

이제 기본 코드와 멀티 플레이어 게임 개념을 갖게되었습니다.

이 튜토리얼을 공유하고 싶다면 물론 코멘트와 제안을 환영합니다.

멀티 플레이어 탱크 게임 최종 코드 다운로드

Posted by 마스터킹
|

이제는 Phaser 프레임 워크에서 HTML5를 사용하여 "2048" 게임을 할 차례입니다.

트윈과 애니메이션을 만드는 것은 매우 쉽습니다. 그래서 여기에 "2048" 게임이 있습니다:

WASD 키로 플레이하고 2048 또는 그 이상으로가보십시오 !!


소스 코드를 보기 전에 흰색 PNG 이미지 만 사용하고 게임 필드 값을 1 차원 배열에 저장하고 있음을 기억하십시오.


이 스크립트를 공부하면 다음 10 가지 원칙을 배우게됩니다.


* 그래픽 애셋로드

* 스프라이트 만들기

* 텍스트를 작성하고 스타일을 지정

* 독립형 Sprite 또는 기존 Sprite의 자식으로 Sprite를 스테이지에 추가하고 제거

* 그룹 만들기

* 그룹을 통해 정렬 및 반복

* 키보드 입력 처리

* 트윈을 사용하여 애니메이션 만들기

* 콜백 관리

* 스프라이트에 색상 필터 적용


<!doctype html>
<html>
<head>
     <script src="phaser.min.js"></script>
     <style>
     body{margin:0}
     </style>
     <script type="text/javascript">
window.onload = function() {
                    // tile width, in pixels
var tileSize = 100;
// creation of a new phaser game, with a proper width and height according to tile
// size
var game = new Phaser.Game(tileSize*4,tileSize*4,Phaser.CANVAS,"",
{preload:onPreload, create:onCreate});
// game array, starts with all cells to zero
var fieldArray = new Array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
// this is the group which will contain all tile sprites
var tileSprites;
// variables to handle keyboard input
var upKey;
var downKey;
var leftKey;
var rightKey;
// colors to tint tiles according to their value
var colors = {
2:0xFFFFFF,
4:0xFFEEEE,
8:0xFFDDDD,
16:0xFFCCCC,
32:0xFFBBBB,
64:0xFFAAAA,
128:0xFF9999,
256:0xFF8888,
512:0xFF7777,
1024:0xFF6666,
2048:0xFF5555,
4096:0xFF4444,
8192:0xFF3333,
16384:0xFF2222,
32768:0xFF1111,
65536:0xFF0000
}
// at the beginning of the game, the player cannot move
                    var canMove=false;
// THE GAME IS PRELOADING
function onPreload() {
// preload the only image we are using in the game
game.load.image("tile", "tile.png");
}
// THE GAME HAS BEEN CREATED
function onCreate() {
// listeners for WASD keys
upKey = game.input.keyboard.addKey(Phaser.Keyboard.W);
upKey.onDown.add(moveUp,this);
     downKey = game.input.keyboard.addKey(Phaser.Keyboard.S);
     downKey.onDown.add(moveDown,this);
     leftKey = game.input.keyboard.addKey(Phaser.Keyboard.A);
     leftKey.onDown.add(moveLeft,this);
     rightKey = game.input.keyboard.addKey(Phaser.Keyboard.D);
     rightKey.onDown.add(moveRight,this);
     // sprite group declaration
tileSprites = game.add.group();
     // at the beginning of the game we add two "2"
addTwo();
addTwo();
}
// A NEW "2" IS ADDED TO THE GAME
function addTwo(){
// choosing an empty tile in the field
do{
var randomValue = Math.floor(Math.random()*16);
} while (fieldArray[randomValue]!=0)
// such empty tile now takes "2" value
fieldArray[randomValue]=2;
// creation of a new sprite with "tile" instance, that is "tile.png" we loaded
// before
var tile = game.add.sprite(toCol(randomValue)*tileSize,toRow(randomValue)*
tileSize,"tile");
// creation of a custom property "pos" and assigning it the index of the
// newly added "2"
tile.pos = randomValue;
// at the beginning the tile is completely transparent
tile.alpha=0;
// creation of a text which will represent the value of the tile
var text = game.add.text(tileSize/2,tileSize/2,"2",{font:"bold 16px Arial",
align:"center"});
                         // setting text anchor in the horizontal and vertical center
text.anchor.set(0.5);
// adding the text as a child of tile sprite
tile.addChild(text);
// adding tile sprites to the group
tileSprites.add(tile);
// creation of a new tween for the tile sprite
var fadeIn = game.add.tween(tile);
// the tween will make the sprite completely opaque in 250 milliseconds
fadeIn.to({alpha:1},250);
// tween callback
fadeIn.onComplete.add(function(){
// updating tile numbers. This is not necessary the 1st time, anyway
updateNumbers();
// now I can move
canMove=true;
})
// starting the tween
fadeIn.start();
}
// GIVING A NUMBER IN A 1-DIMENSION ARRAY, RETURNS THE ROW
function toRow(n){
return Math.floor(n/4);
}
// GIVING A NUMBER IN A 1-DIMENSION ARRAY, RETURNS THE COLUMN
function toCol(n){
return n%4;
}
// THIS FUNCTION UPDATES THE NUMBER AND COLOR IN EACH TILE
function updateNumbers(){
// look how I loop through all tiles
tileSprites.forEach(function(item){
// retrieving the proper value to show
var value = fieldArray[item.pos];
// showing the value
item.getChildAt(0).text=value;
// tinting the tile
item.tint=colors[value]
});
}
// MOVING TILES LEFT
function moveLeft(){
// Is the player allowed to move?
                         if(canMove){
                         // the player can move, let's set "canMove" to false to prevent moving again
// until the move process is done
                              canMove=false;
                              // keeping track if the player moved, i.e. if it's a legal move
                              var moved = false;
                              // look how I can sort a group ordering it by a property
     tileSprites.sort("x",Phaser.Group.SORT_ASCENDING);
     // looping through each element in the group
     tileSprites.forEach(function(item){
     // getting row and column starting from a one-dimensional array
     var row = toRow(item.pos);
     var col = toCol(item.pos);
     // checking if we aren't already on the leftmost column (the tile can't
// move)
     if(col>0){
     // setting a "remove" flag to false. Sometimes you have to
// remove tiles, when two merge into one
     var remove = false;
     // looping from column position back to the leftmost column
     for(i=col-1;i>=0;i--){
     // if we find a tile which is not empty, our search is
// about to end...
     if(fieldArray[row*4+i]!=0){
     // ...we just have to see if the tile we are landing
// on has the same value of the tile we are moving
     if(fieldArray[row*4+i]==fieldArray[row*4+col]){
     // in this case the current tile will be removed
     remove = true;
     i--;                                            
     }
     break;
     }
     }
     // if we can actually move...
     if(col!=i+1){
     // set moved to true
                                             moved=true;
                                             // moving the tile "item" from row*4+col to row*4+i+1 and (if allowed)
// remove it
                                             moveTile(item,row*4+col,row*4+i+1,remove);
     }
     }
     });
     // completing the move
     endMove(moved);
                         }
}
// FUNCTION TO COMPLETE THE MOVE AND PLACE ANOTHER "2" IF WE CAN
function endMove(m){
// if we move the tile...
if(m){
// add another "2"
     addTwo();
                         }
                         else{
                         // otherwise just let the player be able to move again
canMove=true;
}
}
// FUNCTION TO MOVE A TILE
function moveTile(tile,from,to,remove){
// first, we update the array with new values
                         fieldArray[to]=fieldArray[from];
                         fieldArray[from]=0;
                         tile.pos=to;
                         // then we create a tween
                         var movement = game.add.tween(tile);
                         movement.to({x:tileSize*(toCol(to)),y:tileSize*(toRow(to))},150);
                         if(remove){
                         // if the tile has to be removed, it means the destination tile must be multiplied by 2
                              fieldArray[to]*=2;
                              // at the end of the tween we must destroy the tile
                              movement.onComplete.add(function(){
                                   tile.destroy();
                              });
                         }
                         // let the tween begin!
                         movement.start();
                    }
                    
                    // MOVING TILES UP - SAME PRINCIPLES AS BEFORE
                    function moveUp(){
                          if(canMove){
                              canMove=false;
                              var moved=false;
     tileSprites.sort("y",Phaser.Group.SORT_ASCENDING);
     tileSprites.forEach(function(item){
     var row = toRow(item.pos);
     var col = toCol(item.pos);
     if(row>0){  
                                        var remove=false;
     for(i=row-1;i>=0;i--){
     if(fieldArray[i*4+col]!=0){
     if(fieldArray[i*4+col]==fieldArray[row*4+col]){
     remove = true;
     i--;                                            
     }
                                                  break
     }
     }
     if(row!=i+1){
                                             moved=true;
                                             moveTile(item,row*4+col,(i+1)*4+col,remove);
     }
     }
     });
     endMove(moved);
                         }
}
// MOVING TILES RIGHT - SAME PRINCIPLES AS BEFORE
                    function moveRight(){
                          if(canMove){
                              canMove=false;
                              var moved=false;
     tileSprites.sort("x",Phaser.Group.SORT_DESCENDING);
     tileSprites.forEach(function(item){
     var row = toRow(item.pos);
     var col = toCol(item.pos);
     if(col<3){
                                        var remove = false;
     for(i=col+1;i<=3;i++){
     if(fieldArray[row*4+i]!=0){
                                                  if(fieldArray[row*4+i]==fieldArray[row*4+col]){
     remove = true;
     i++;                                            
     }
     break
     }
     }
     if(col!=i-1){
                                             moved=true;
     moveTile(item,row*4+col,row*4+i-1,remove);
     }
     }
     });
     endMove(moved);
                         }
}
                    
                    // MOVING TILES DOWN - SAME PRINCIPLES AS BEFORE
                    function moveDown(){
                          if(canMove){
                              canMove=false;
                              var moved=false;
     tileSprites.sort("y",Phaser.Group.SORT_DESCENDING);
     tileSprites.forEach(function(item){
     var row = toRow(item.pos);
     var col = toCol(item.pos);
     if(row<3){
                                        var remove = false;
     for(i=row+1;i<=3;i++){
     if(fieldArray[i*4+col]!=0){
     if(fieldArray[i*4+col]==fieldArray[row*4+col]){
     remove = true;
     i++;                                            
     }
                                                  break
     }
     }
     if(row!=i-1){
                                             moved=true;
     moveTile(item,row*4+col,(i-1)*4+col,remove);
     }
     }
     });
          endMove(moved);
                         }
}
     };
</script>
    </head>
    <body>
    </body>
</html>


소스파일입니다.

2048.zip





Posted by 마스터킹
|

Roguelikes는 Dredmor의 Dungeons, Spelunky, Isaac의 바인딩, FTL이 광범위한 잠재 고객에게 도달하고 비평을 받고있는 게임과 함께 최근 주목을 받아 왔습니다. 작은 틈새 시장에서 하드 코어 플레이어가 오래 동안 즐기던 다양한 조합의 rockguelike 요소는 이제 많은 기존 장르에 깊이와 재연성을 가져다줍니다.




이 튜토리얼에서는 JavaScript와 HTML 5 게임 엔진 인 Phaser를 사용하여 전통적인 RogueLike 게임을 만드는 법을 배웁니다. 결국, 당신은 당신의 브라우저에서 재생할 수있는, 완벽한 기능을 갖춘 간단한 RogueLike 게임을 갖게 될 것입니다! (우리의 목적을 위해 전통적인 roguelike는 permadeath와 함께 싱글 플레이어, 무작위, 차례 기반 던전 크롤러로 정의됩니다.)



준비하기

이 자습서에서는 텍스트 편집기와 브라우저가 필요합니다. 나는 Notepad ++를 사용하고 있으며 광범위한 개발자 도구를 위해 Chrome을 선호하지만 워크 플로우는 선택한 텍스트 편집기와 브라우저에서 거의 동일합니다.


그런 다음 소스 파일을 다운로드 하고 init 폴더로 시작해야합니다. 여기에는 게임용 Phaser와 기본 HTML 및 JS 파일이 포함되어 있습니다. 현재 비어있는 rl.js 파일에 게임 코드를 씁니다.


index.html 파일은 단순히 Phaser와 앞서 언급 한 게임 코드 파일을로드합니다.

<!DOCTYPE html>
<head>
    <title>roguelike tutorial</title>
    <script src="phaser.min.js"></script>
    <script src="rl.js"></script>
</head>
</html>



초기화 및 정의

당분간은 ASCII 그래픽을 사용하게 될 것입니다. 미래에는 비트 맵 그래픽으로 바꿀 수 있지만, 지금은 간단한 ASCII를 사용하면 더 쉽게 사용할 수 있습니다.


폰트 크기, 맵의 크기 (즉, 레벨), 그리고 스폰자의 수에 대한 몇 가지 상수를 정의 해 보겠습니다.

// font size
var FONT = 32;
 
// map dimensions
var ROWS = 10;
var COLS = 15;
 
// number of actors per level, including player
var ACTORS = 10;



Phaser를 초기화하고 키보드 키 업 이벤트를 설정해 봅시다. 

// initialize phaser, call create() once done
var game = new Phaser.Game(COLS * FONT * 0.6, ROWS * FONT, Phaser.AUTO, null, {
        create: create
});
 
function create() {
        // init keyboard commands
        game.input.keyboard.addCallbacks(null, null, onKeyUp);
}
 
function onKeyUp(event) {
        switch (event.keyCode) {
                case Keyboard.LEFT:
 
                case Keyboard.RIGHT:
 
                case Keyboard.UP:
 
                case Keyboard.DOWN:
 
        }
}



기본 모노 스페이스 글꼴은 높이가 60 % 정도되는 경향이 있으므로 캔버스 크기를 0.6 * 글꼴 크기 * 열 수로 초기화했습니다. 우리는 또한 Phaser에게 초기화가 끝난 직후에 create () 함수를 호출해야한다고 말하면서 키보드 컨트롤을 초기화합니다.


볼거리가 많지는 않지만 여기까지 게임을 볼 수 있습니다!



지도

타일 맵은 우리의 플레이 영역을 나타냅니다. 

// the structure of the map
var map;



가장 단순한 형태의 절차를 사용하여 맵을 생성합니다. 벽을 포함 할 셀과 바닥을 임의로 결정합니다.

function initMap() {
        // create a new random map
        map = [];
        for (var y = 0; y < ROWS; y++) {
                var newRow = [];
                for (var x = 0; x < COLS; x++) {
                     if (Math.random() > 0.8)
                        newRow.push('#');
                    else
                        newRow.push('.');
                }
                map.push(newRow);
        }
}



관련 게시물

- BSP 트리를 사용하여 게임 맵을 생성하는 방법

- 셀 오토마타를 사용하여 무작위 동굴 생성


이것은 우리에게 80%가 벽이고 나머지가 바닥 인것을 제공합니다.


키보드 이벤트 리스너를 설정 한 직후 create () 함수로 게임의 새 맵을 초기화합니다.

function create() {
        // init keyboard commands
        game.input.keyboard.addCallbacks(null, null, onKeyUp);
 
        // initialize map
        initMap();
}


여기서 데모를 볼 수 있습니다. 아직 지도를 렌더링하지 않았으므로 아무 것도 볼 수 없습니다.



화면

지도를 그릴 시간입니다! 우리의 화면은 각각 하나의 문자를 포함하는 텍스트 요소의 2D 배열입니다.

// the ascii display, as a 2d array of characters
var asciidisplay;


두 가지 모두 간단한 ASCII 문자이기 때문에 지도를 그리면 화면의 내용이 지도의 값으로 채워집니다.

function drawMap() {
    for (var y = 0; y < ROWS; y++)
        for (var x = 0; x < COLS; x++)
            asciidisplay[y][x].content = map[y][x];
}



마지막으로 맵을 그리기 전에 화면을 초기화해야합니다. 우리는 create () 함수로 돌아 갑니다.

function create() {
        // init keyboard commands
        game.input.keyboard.addCallbacks(null, null, onKeyUp);
 
        // initialize map
        initMap();
 
        // initialize screen
        asciidisplay = [];
        for (var y = 0; y < ROWS; y++) {
                var newRow = [];
                asciidisplay.push(newRow);
                for (var x = 0; x < COLS; x++)
                        newRow.push( initCell('', x, y) );
        }
        drawMap();
}
 
function initCell(chr, x, y) {
        // add a single cell in a given position to the ascii display
        var style = { font: FONT + "px monospace", fill:"#fff"};
        return game.add.text(FONT*0.6*x, FONT*y, chr, style);
}


이제 프로젝트를 실행할 때 무작위지도가 표시됩니다.




액터

다음 줄에는 플레이어 캐릭터와 패배해야 할 적들이 있습니다. 각 액터는 맵의 위치에 대해 x와 y, 히트 포인트에 대해 hp의 세 필드가있는 객체입니다.


우리는 모든 액터를 actorList 배열에 유지합니다 (첫 번째 요소는 플레이어입니다). 우리는 또한 빠른 검색을위한 키로 액터의 위치와 연관 배열을 유지하므로 어떤 액터가 특정 위치를 차지하고 있는지 찾기 위해 전체 액터리스트를 반복 할 필요가 없습니다. 이것은 우리가 이동과 전투를 코드화 할 때 우리를 도울 것입니다.

// a list of all actors; 0 is the player
var player;
var actorList;
var livingEnemies;
 
// points to each actor in its position, for quick searching
var actorMap;


우리는 모든 액터를 만들고 지도에 무작위로 자유로운 위치에 할당합니다.

function randomInt(max) {
   return Math.floor(Math.random() * max);
}
 
function initActors() {
        // create actors at random locations
        actorList = [];
        actorMap = {};
        for (var e=0; e<ACTORS; e++) {
                // create new actor
                var actor = { x:0, y:0, hp:e == 0?3:1 };
                do {
                        // pick a random position that is both a floor and not occupied
                        actor.y=randomInt(ROWS);
                        actor.x=randomInt(COLS);
                } while ( map[actor.y][actor.x] == '#' || actorMap[actor.y + "_" + actor.x] != null );
 
                // add references to the actor to the actors list & map
                actorMap[actor.y + "_" + actor.x]= actor;
                actorList.push(actor);
        }
 
        // the player is the first actor in the list
        player = actorList[0];
        livingEnemies = ACTORS-1;
}


액터를 보여줄 시간입니다! 우리는 모든 적을 e로, 플레이어 캐릭터를 HP의 숫자로 그립니다.

function drawActors() {
        for (var a in actorList) {
                if (actorList[a].hp > 0)
                        asciidisplay[actorList[a].y][actorList[a].x].content = a == 0?''+player.hp:'e';
        }
}


방금 작성한 함수를 사용하여 create () 함수에서 모든 액터를 초기화하고 그립니다.

function create() {
    ...
    // initialize actors
    initActors();
    ...
    drawActors();

}.


우리는 이제 플레이어 캐릭터와 적들이 화면에 표시되어 있는것을 볼수 있습니다.




차단 및 이동식 타일

우리는 액터가 스크린과 벽을 벗어나지 않도록해야합니다. 그래서 간단한 체크를 추가하여 주어진 액터가 어느 방향으로 걸어 갈 수 있는지 살펴 봅시다.

function canGo(actor,dir) {
    return  actor.x+dir.x >= 0 &&
        actor.x+dir.x <= COLS - 1 &&
                actor.y+dir.y >= 0 &&
        actor.y+dir.y <= ROWS - 1 &&
        map[actor.y+dir.y][actor.x +dir.x] == '.';
}



이동과 전투

우리는 마침내 약간의 상호 작용에 도달했습니다 : 

이동과 전투! 클래식 roguelikes에서 기본적인 공격은 다른 액터로 이동함으로써 트리거 되기 때문에 우리는 액터와 방향을 취하는 moveTo () 함수 (두 방향을 x와 y를 액터가 들어가는 위치로 옮깁니다).를 추가 하겠습니다.

function moveTo(actor, dir) {
        // check if actor can move in the given direction
        if (!canGo(actor,dir))
                return false;
 
        // moves actor to the new location
        var newKey = (actor.y + dir.y) +'_' + (actor.x + dir.x);
        // if the destination tile has an actor in it
        if (actorMap[newKey] != null) {
                //decrement hitpoints of the actor at the destination tile
                var victim = actorMap[newKey];
                victim.hp--;
 
                // if it's dead remove its reference
                if (victim.hp == 0) {
                        actorMap[newKey]= null;
                        actorList[actorList.indexOf(victim)]=null;
                        if(victim!=player) {
                                livingEnemies--;
                                if (livingEnemies == 0) {
                                        // victory message
                                        var victory = game.add.text(game.world.centerX, game.world.centerY, 'Victory!\nCtrl+r to restart', { fill : '#2e2', align: "center" } );
                                        victory.anchor.setTo(0.5,0.5);
                                }
                        }
                }
        } else {
                // remove reference to the actor's old position
                actorMap[actor.y + '_' + actor.x]= null;
 
                // update position
                actor.y+=dir.y;
                actor.x+=dir.x;
 
                // add reference to the actor's new position
                actorMap[actor.y + '_' + actor.x]=actor;
        }
        return true;
}



기본적으로

- 액터가 올바른 위치로 이동하려고하는지 확인합니다.

- 그 위치에 다른 액터가 있다면, 우리는 그것을 공격합니다 (HP 숫자가 0이되면 죽이기도합니다).

- 새로운 위치에 다른 액터가 없다면, 우리는 거기로 이동합니다.


우리는 또한 마지막 적을 살해 한 후 간단한 승리 메시지를 보여 주고, 우리가 유효한 이동을 수행했는지 여부에 따라 false 또는 true를 반환합니다.


이제는 onKeyUp () 함수로 돌아가서 사용자가 키를 누를 때마다 화면에서 이전 액터의 위치를 지우고 (지도를 위에 그려서) 플레이어 캐릭터를 이동하고, 위치를 찾은 다음 액터를 다시 그립니다.

function onKeyUp(event) {
        // draw map to overwrite previous actors positions
        drawMap();
 
        // act on player input
        var acted = false;
        switch (event.keyCode) {
                case Phaser.Keyboard.LEFT:
                        acted = moveTo(player, {x:-1, y:0});
                        break;
 
                case Phaser.Keyboard.RIGHT:
                        acted = moveTo(player,{x:1, y:0});
                        break;
 
                case Phaser.Keyboard.UP:
                        acted = moveTo(player, {x:0, y:-1});
                        break;
 
                case Phaser.Keyboard.DOWN:
                        acted = moveTo(player, {x:0, y:1});
                        break;
        }
 
        // draw actors in new positions
        drawActors();
}


우리는 곧 행동 변수를 사용하여 각 플레이어가 입력 한 후에 적들이 행동해야 하는지를 알 수 있습니다.




기본 인공 지능


이제 플레이어 캐릭터가 움직이고 공격하기 때문에 플레이어가 적과 거리가 6 단계 이하인 경우 매우 간단한 경로 찾기에 따라 적이 플레이를 공격하도록 하겠습니다.. (플레이어가 멀어지면 적들은 무작위로 걷습니다.)


우리의 공격 코드는 액터가 누구를 공격하는지 상관하지 않습니다. 즉, 만약 당신이 그들을 바로 정렬한다면, 적들은 플레이어 캐릭터를서로 공격 할 것입니다!

function aiAct(actor) {
        var directions = [ { x: -1, y:0 }, { x:1, y:0 }, { x:0, y: -1 }, { x:0, y:1 } ];
        var dx = player.x - actor.x;
        var dy = player.y - actor.y;
 
        // if player is far away, walk randomly
        if (Math.abs(dx) + Math.abs(dy) > 6)
                // try to walk in random directions until you succeed once
                while (!moveTo(actor, directions[randomInt(directions.length)])) { };
 
        // otherwise walk towards player
        if (Math.abs(dx) > Math.abs(dy)) {
                if (dx < 0) {
                        // left
                        moveTo(actor, directions[0]);
                } else {
                        // right
                        moveTo(actor, directions[1]);
                }
        } else {
                if (dy < 0) {
                        // up
                        moveTo(actor, directions[2]);
                } else {
                        // down
                        moveTo(actor, directions[3]);
                }
        }
        if (player.hp < 1) {
                // game over message
                var gameOver = game.add.text(game.world.centerX, game.world.centerY, 'Game Over\nCtrl+r to restart', { fill : '#e22', align: "center" } );
                gameOver.anchor.setTo(0.5,0.5);
        }
}


또한 우리는 적의 한 명이 플레이어를 죽이면 게임오버 메세지를 출력하도록 하였습니다.


이제는 플레이어가 움직일 때마다 적이 움직니는 것처럼  만드는 것입니다. 이 작업은 새로운 위치에 액터를 그리기 전에 onKeyUp () 함수의 끝 부분에 다음 코드를 추가해야합니다.

function onKeyUp(event) {
        ...
        // enemies act every time the player does
        if (acted)
                for (var enemy in actorList) {
                        // skip the player
                        if(enemy==0)
                                continue;
 
                        var e = actorList[enemy];
                        if (e != null)
                                aiAct(e);
                }
 
        // draw actors in new positions
        drawActors();
}




마지막 코드까지 수정된 데모 버젼입니다.


Posted by 마스터킹
|


점프 정리하기

이제 우리의 영웅이 뛰고 있습니다. 그러나 문제가 있습니다. 때로는 점프가 발생하지 않으며 게임에 대한 흥미가 없습니다.


몇 가지 테스트를 한 결과 리스너가 때로 잘못된 순서로 호출되었다는 것을 알게되었습니다. 빨리 클릭하면 다운 이벤트가 발생하기 전에 위 이벤트가 실행되고 즉시 파워메터의 전원이 제거됩니다. 나는 이것을 세 가지로 고정시켰습니다.


- 다운 이벤트가 트리거 될 때까지 업 이벤트를 등록하기 위해 대기 중

- 각각의 이벤트 리스너가 트리거 된 이후에 다운 및 업 이벤트 리스너 제거

- 필요에 따라 이벤트 재 등록

아래의 강조 표시된 행을 참조하십시오.

var StateMain = {

    preload: function() {

        game.load.image("ground", "images/ground.png");

        game.load.image("hero", "images/hero.png");

        game.load.image("bar", "images/powerbar.png");

        game.load.image("block", "images/block.png");

    },

    create: function() {

        this.power = 0;

        //turn the background sky blue

        game.stage.backgroundColor = "#00ffff";

        //add the ground

        this.ground = game.add.sprite(0, game.height * .9, "ground");

        //add the hero in

        this.hero = game.add.sprite(game.width * .2, this.ground.y - 25, "hero");

        //add the power bar just above the head of the hero

        this.powerBar = game.add.sprite(this.hero.x + 25, this.hero.y - 25, "bar");

        this.powerBar.width = 0;

        //start the physics engine

        game.physics.startSystem(Phaser.Physics.ARCADE);

        //enable the hero for physics

        game.physics.enable(this.hero, Phaser.Physics.ARCADE);

        game.physics.enable(this.ground, Phaser.Physics.ARCADE);

        //game.physics.arcade.gravity.y = 100;

        this.hero.body.gravity.y = 200;

        this.hero.body.collideWorldBounds = true;

        this.hero.body.bounce.set(0, .2);

        this.ground.body.immovable = true;

        //set listeners

        game.input.onDown.add(this.mouseDown, this);

    },

    mouseDown: function() {

        game.input.onDown.remove(this.mouseDown, this);

        this.timer = game.time.events.loop(Phaser.Timer.SECOND / 1000, this.increasePower, this);

        game.input.onUp.add(this.mouseUp, this);

    },

    mouseUp: function() {

        game.input.onUp.remove(this.mouseUp, this);

        this.doJump();

        game.time.events.remove(this.timer);

        this.power = 0;

        this.powerBar.width = 0;

        game.input.onDown.add(this.mouseDown, this);

    },

    increasePower: function() {

        this.power++;

        this.powerBar.width = this.power;

        if (this.power &gt; 50) {

            this.power = 50;

        }

    },

    doJump: function() {

        this.hero.body.velocity.y = -this.power * 12;

    },

    update: function() {

        game.physics.arcade.collide(this.hero, this.ground);

    }

}




이제 완벽한 점프를 하게 되었지만 다른 문제를 발견했을 것입니다. 당신은 영웅이 날 수 있도록 마우스를 계속 누를 수 있습니다. 중력과 속력을 결합하면 날아 다니는 새 게임을 만드는 데 도움이 될 것입니다. 주인공이 점프 할 때 영웅이되기를 원하기 때문에 주인공의 위치를 기록해 두십시오.


mouseDown에 대한 이벤트 리스너를 추가하기 바로 전에 create 함수에서이 행을 추가하십시오.

//record the initial position

this.startY = this.hero.y;


이제 mouseDown 함수의 위치를 확인하면됩니다. 주인공이 시작 위치에 있지 않으면, 우리는 단순히 기능을 종료하고 점프하지 않습니다.

mouseDown 함수의 시작 부분에 다음을 추가합니다.

if (this.hero.y != this.startY) {

            return;

}




이제 영웅이 뛰어 넘을 무언가를 만들 때입니다. 우리는 블록 벽을 만들고 싶습니다. 그 벽을 구성하는 모든 블록을 보유 할 그룹을 만들어 봅시다.


create 함수의 끝에서 add 합니다.

this.blocks = game.add.group();


그런 다음 해당 그룹에 임의의 수의 블록을 추가하는 함수를 만들어 보겠습니다.

makeBlocks: function() {

        var wallHeight=game.rnd.integerInRange(2, 6);

        for (var i = 0; i &lt; wallHeight; i++) {

            var block = game.add.sprite(0, -i * 25, "block");

            this.blocks.add(block);

        }

    }


결과는 다음과 같습니다.



이제 우리는 벽을 가지고 있습니다. 다음 장에서는 게임에 움직임을 추가 할 것입니다.

Posted by 마스터킹
|


물리엔진 추가하기


우리는 Phaser Js 로 Runner 게임 만들기 Part - 1 에서 주인공의 파워메터 컨트롤 부분만 제작하였습니다. 직접 주인공을 제어하고 움직이는 부분은 제작하지 않았습니다.  

그래서 주인공을 제어하고 움직이게 하기 위해서, 가장 먼저 할 일은 물리 엔진을 시작하는 것입니다. Phaser 에는 여러 개의 엔진이 있습니다. 우리는 아케이드 엔진을 사용할 것입니다.


create 함수의 끝에서 (그러나 mouseListeners 바로 위에)이 코드 라인을 배치하여 물리 엔진을 시작하십시오

//start the physics engine

game.physics.startSystem(Phaser.Physics.ARCADE);


이것은 우리가 스프라이트에 물리엔진을 추가하고 싶다는 것을 Phaser 가 알 수있게 해줍니다. 그러나 모든 단일 스프라이트에 물리를 자동으로 추가하지는 않습니다. 각 스프라이트를 활성화해야합니다. 이 코드를 다음 행에 배치하십시오.

//enable the hero for physics

game.physics.enable(this.hero, Phaser.Physics.ARCADE);


이제 우리의 스프라이트는 물리 속성을 설정할 준비가되었습니다. 영웅의 속도를 설정하는 점프 기능을 만들어 봅시다.

doJump: function() {

        this.hero.body.velocity.y = -this.power * 12;

    }


우리는 단지 y 속도를 원합니다. 그리고 그것을 위로 향하게 하기 위해서 그것을 음수로 설정하고 싶습니다. 나는 그것을 힘 (또는 PowerBar의 폭) 12 번으로 설정했습니다. 이것은 적당한 속도를 위쪽으로 내는 것처럼 보일것입니다.


다음은 stateMain.js 코드 입니다.

var StateMain = {

    preload: function() {

        game.load.image("ground", "images/ground.png");

        game.load.image("hero", "images/hero.png");

        game.load.image("bar", "images/powerbar.png");

        game.load.image("block", "images/block.png");

    },

    create: function() {

        this.power = 0;

        //turn the background sky blue

        game.stage.backgroundColor = "#00ffff";

        //add the ground

        var ground = game.add.sprite(0, game.height * .9, "ground");

        //add the hero in

        this.hero = game.add.sprite(game.width * .2, ground.y - 25, "hero");

        //add the power bar just above the head of the hero

        this.powerBar = game.add.sprite(this.hero.x + 25, this.hero.y - 25, "bar");

        this.powerBar.width = 0;

        //start the physics engine

        game.physics.startSystem(Phaser.Physics.ARCADE);

        //enable the hero for physics

        game.physics.enable(this.hero, Phaser.Physics.ARCADE);

        //set listeners

        game.input.onUp.add(this.mouseUp, this);

        game.input.onDown.add(this.mouseDown, this);

    },

    mouseDown: function() {

        this.timer = game.time.events.loop(Phaser.Timer.SECOND / 1000, this.increasePower, this);

    },

    mouseUp: function() {

        this.doJump();

        game.time.events.remove(this.timer);

        this.power = 0;

        this.powerBar.width = 0;

    },

    increasePower: function() {

        this.power++;

        this.powerBar.width = this.power;

        if (this.power &gt; 50) {

            this.power = 50;

        }

    },

    doJump: function() {

        this.hero.body.velocity.y = -this.power * 12;

    },

    update: function() {}

}


마우스를 놓을 때 볼 수 있듯이 영웅은 올라가서 거기 머물러 있습니다.





올라가는 것은 내려와야합니다.

영웅이 지구로 내려 오도록 우리는 그의 몸에 중력을 더해야합니다. 우리는 지구상의 중력을 설정할 수 있지만, 그것은 땅에도 영향을 미치고, 땅이 화면에서 떨어지게 만듭니다. 지면의 중력을 0으로 설정하여 무시할 수 있고, 영웅의 중력을 설정하는 것이 쉽습니다.

this.hero.body.gravity.y = 200;


지금 게임을 실행하면 주인공이 게임의 맨 아래를 빠져 나가는 것을 볼 수 있습니다. 위의 예에서도 영웅이 게임의 맨 위로 날아갈 수 있습니다.

다음 한 줄의 코드로 두 가지를 모두 멈출 수 있습니다.

this.hero.body.collideWorldBounds = true;


이제 영웅은 게임에 머물러 있지만 여전히 땅을 빠져 나옵니다. 그것을 수정합시다.

첫째, 우리는 지상 스프라이트가 나머지 주들에게 접근 가능하도록해야합니다.

코드를 수정합시다.

var ground = game.add.sprite(0, game.height * .9, "ground");

this.ground = game.add.sprite(0, game.height * .9, "ground");

으로 수정합니다.


그러면 로컬 스프라이트가 주 전체 스프라이트로 변경됩니다.

중요 : 또한 ground 변수를 this.ground로 변경해야합니다.


지면의 물리엔진을 활성화 합니다.

game.physics.enable(this.ground, Phaser.Physics.ARCADE);


다음으로 update 함수에서 collision 관계를 설정하십시오. 두 물체가 충돌 할 때 물리엔진이 서로 작용하게됩니다.

update: function() {

         game.physics.arcade.collide(this.hero, this.ground);

    }


지금 게임을 실행하면 주인공이 화면에서 벗어나게됩니다.


Phaser 에게 그 지면은 움직이지 않는다고 설정합니다.

this.ground.body.immovable = true;


정리된 stateMain.js 코드 입니다.

var StateMain = {

    preload: function() {

        game.load.image("ground", "images/ground.png");

        game.load.image("hero", "images/hero.png");

        game.load.image("bar", "images/powerbar.png");

        game.load.image("block", "images/block.png");

    },

    create: function() {

        this.power = 0;

        //turn the background sky blue

        game.stage.backgroundColor = "#00ffff";

        //add the ground

        this.ground = game.add.sprite(0, game.height * .9, "ground");

        //add the hero in

        this.hero = game.add.sprite(game.width * .2, this.ground.y - 25, "hero");

        //add the power bar just above the head of the hero

        this.powerBar = game.add.sprite(this.hero.x + 25, this.hero.y - 25, "bar");

        this.powerBar.width = 0;

        //start the physics engine

        game.physics.startSystem(Phaser.Physics.ARCADE);

        //enable the hero for physics

        game.physics.enable(this.hero, Phaser.Physics.ARCADE);

        game.physics.enable(this.ground, Phaser.Physics.ARCADE);

 

        this.hero.body.gravity.y = 200;

        this.hero.body.collideWorldBounds = true;

        this.ground.body.immovable = true;

        //set listeners

        game.input.onUp.add(this.mouseUp, this);

        game.input.onDown.add(this.mouseDown, this);

    },

    mouseDown: function() {

        this.timer = game.time.events.loop(Phaser.Timer.SECOND / 1000, this.increasePower, this);

    },

    mouseUp: function() {

        this.doJump();

        game.time.events.remove(this.timer);

        this.power = 0;

        this.powerBar.width = 0;

    },

    increasePower: function() {

        this.power++;

        this.powerBar.width = this.power;

        if (this.power &gt; 50) {

            this.power = 50;

        }

    },

    doJump: function() {

        this.hero.body.velocity.y = -this.power * 12;

    },

    update: function() {

         game.physics.arcade.collide(this.hero, this.ground);

    }

}


결과입니다.




Posted by 마스터킹
|


Runner게임은 모바일 게임에서 필수 요소가되었습니다. 게임 개발자는 조만간 비슷한 것을 만들어 달라고 요청할 것입니다. 게임 개발에서 수년간 일하면서 그래픽을 만들기 전에 종종 게임을 잘 코딩해야합니다. 예술가들이 멋진 예술을 끝내기를 기다리는 동안, 나는  추상적인 그래픽을 사용할것입니다. 내가 같이 일했던 한 디자이너는 이것을 "dev art"라고 불렀습니다.


다음은 내가 사용할 기본적인 기술입니다.

- 우리의 영웅, 주인공.

- 서있을 그라운드.

- 뛰어 넘을 블록.

- 점프 할 높이를 나타내는 파워메터.

첫 번째 부분에서는 기초를 설정하고 파워 미터가 작동하도록 하겠습니다.


기본 템플릿

나는 내 자신의 기본 템플릿을 사용하고 있습니다.이 기본 템플릿은 약간의 시간을 절약하기 위해 만든 기본 템플릿입니다.

여기에서 다운로드 할 수 있습니다.


이미지 추가하기

먼저 프리로드 기능에서 키와 경로를 제공하여 이미지를 미리 로드합니다.

game.load.image("ground","images/ground.png");

game.load.image("hero","images/hero.png");

game.load.image("bar","images/powerbar.png");

game.load.image("block","images/block.png");


하늘 만들기

영웅은 검은 색 사각형이므로 무대 색상을 하늘색으로 변경합시다.

이것을 create 함수에 두십시오.

//turn the background sky blue

game.stage.backgroundColor="#00ffff";


메인 파트 추가

이제 땅을 만들겠습니다. 모바일 크기가 다양하기 때문에 화면의 높이의 90 % 또는 바닥에서 10 %로 바닥을 둡니다. 나중에 조정할 수 있습니다. 주인공의 키는 25px이므로 지상과 같은 y 위치에 놓고 땅바닥에 서게 하려면 25를 빼면됩니다.

//add the ground

var ground=game.add.sprite(0,game.height*.9,"ground");

//add the hero

this.hero=game.add.sprite(game.width*.2,ground.y-25,"hero");


게임은 이제 다음과 같이 보일 것입니다.



파워 미터

파워미터는 전원이 바뀔 때마다 추가 할 수 있고 너비를 변경할 수있는 작은 비트 맵입니다. 플레이어는 마우스 또는 손가락을 누른 상태에서 mouseUp 이벤트가 호출되면 점프하여 전원을 변경합니다. 마우스가 길수록 높을수록 점프가 됩니다.


먼저 변수를 변경해야합니다.


다음 코드를 create 함수의 맨 위에 놓습니다.

this.power=0;


다음으로, 우리는 영웅의 머리 위 오른쪽에 25 픽셀, 영웅의 좌표보다 25 픽셀 위의 파워 바를 놓습니다.

//add the power bar just above the head of the hero

this.powerBar=game.add.sprite(this.hero.x+25,this.hero.y-25,"bar");

this.powerBar.width=0;


리스너에 mouseUp 및 mouseDown을 추가합니다.

//set listeners

game.input.onUp.add(this.mouseUp, this);

game.input.onDown.add(this.mouseDown, this);


mouseDown이 호출되면 타이머가 시작되어 전원이 계속 증가합니다. mouseUp이 호출 될 때 우리는 그 타이머를 멈춤니다.  우리는 Phaser.Timer.Second / 1000에 타이머를 설정할 것입니다. 이것은 타이머가 1 초에 1000 번 실행된다는 것을 의미합니다. 이렇게하면 부드러운 파워 바 효과를 얻을 수 있습니다.

mouseDown:function()

{

  this.timer=game.time.events.loop(Phaser.Timer.SECOND/1000, this.increasePower, this);

},

mouseUp:function()

{

   game.time.events.remove(this.timer);

   this.power=0;

   this.powerBar.width=0;

},


이 항목에서 마지막으로 다루는 것은 파워 변수를 증가시키고 파워 바의 너비를 변경하는 increasePower 함수를 설정하는 것입니다. 우리는 현재 50의 힘을 제한 할 것입니다.하지만 게임이 개발되면 나중에 변경할 수 있습니다.

increasePower:function()

    {

     this.power++;

     this.powerBar.width=this.power;

     if (this.power&gt;50)

     {

       this.power=50;

     }

    },


이 단원의 마지막 코드는 다음과 같습니다.

var StateMain = {

    preload: function() {

        game.load.image("ground", "images/ground.png");

        game.load.image("hero", "images/hero.png");

        game.load.image("bar", "images/powerbar.png");

        game.load.image("block", "images/block.png");

    },

    create: function() {

        this.power = 0;

        //turn the background sky blue

        game.stage.backgroundColor = "#00ffff";

        //add the ground

        var ground = game.add.sprite(0, game.height * .9, "ground");

        //add the hero in

        this.hero = game.add.sprite(game.width * .2, ground.y - 25, "hero");

        //add the power bar just above the head of the hero

        this.powerBar = game.add.sprite(this.hero.x + 25, this.hero.y - 25, "bar");

        this.powerBar.width = 0;

        //set listeners

        game.input.onUp.add(this.mouseUp, this);

        game.input.onDown.add(this.mouseDown, this);

    },

    mouseDown: function() {

        this.timer = game.time.events.loop(Phaser.Timer.SECOND / 1000, this.increasePower, this);

    },

    mouseUp: function() {

        game.time.events.remove(this.timer);

        this.power = 0;

        this.powerBar.width = 0;

    },

    increasePower: function() {

        this.power++;

        this.powerBar.width = this.power;

        if (this.power &gt; 50) {

            this.power = 50;

        }

    },

    update: function() {}

}


결과는 다음과 같습니다.










Posted by 마스터킹
|


Toast메시지는 페이드 인 및 페이드 아웃되는 문자 메시지입니다. 유리를 올린 사람과 축배를하고 유리를 다시 내리는 것과 비슷하기 때문에 토스트 메시지라는 이름이 붙어 있습니다.


여기에 예제가 있습니다.

var StateMain = {

    preload: function() {

        game.load.image("toastBack", "images/back.png");

        game.load.image("btnToast", "images/btnGenerate.png");

    },

    create: function() {

 

        //SET UP TEST BUTTON

     this.btnToast=game.add.sprite(game.world.centerX,game.height*.25,"btnToast");

     this.btnToast.anchor.set(0.5,0.5);

     this.btnToast.inputEnabled=true;

     this.btnToast.events.onInputDown.add(this.testToast,this);

    },

    testToast: function() {

        this.btnToast.visible=false;

        //get an instance of a toast object

        //and place it in postion

        var toast = this.makeToast("TEST!");

        toast.x = game.world.centerX;

        toast.y = game.world.height * .8;

        //fade in the toast object

        this.fadeIn(toast);

    },

    makeToast: function(message) {

        var toastGroup = game.add.group();

        var toastText = game.add.text(0, 0, message);

        var toastBack = game.add.sprite(0, 0, "toastBack");

        toastBack.width = game.width * .9;

        //

        //

        //SET ANCHORS

        toastText.anchor.set(0.5, 0.5);

        toastBack.anchor.set(0.5, 0.5);

        //

        //ADD THE TEXT AND SPRITE GRAPHIC TO THE TOAST GROUP

        toastGroup.add(toastBack);

        toastGroup.add(toastText);

        toastGroup.alpha = 0;

        //

        return toastGroup;

    },

    fadeIn: function(obj) {

        var tween = game.add.tween(obj).to({

            alpha: 1

        }, 1000, Phaser.Easing.Linear.None, true);

        tween.onComplete.add(this.delay, this);

    },

    delay:function(obj)

    {

        //WHEN TWEEN IS DONE PAUSE HERE FOR DELAY

        //SET A FADE OBJECT IN THE SCOPE OF THE STATE,

        //SINCE WE CAN NOT PASS THE OBJECT IN THE TIMER

     this.fadeObj=obj;

     game.time.events.add(Phaser.Timer.SECOND*2, this.delayDone, this);

    },

    delayDone:function()

    {

        //NOW THAT DELAY IS DONE CALL FADE OUT

     this.fadeOut(this.fadeObj);

    },

    fadeOut: function(obj) {

        var tween = game.add.tween(obj).to({

            alpha: 0

        }, 1000, Phaser.Easing.Linear.None, true);

        tween.onComplete.add(this.fadeDone, this);

    },

    fadeDone:function()

    {

        this.btnToast.visible=true;

    },

    update: function() {}

}



Posted by 마스터킹
|

최근에 나에게 매우 유용했던 것은 두 물체 사이의 각도를 구하거나 마우스와 중심 문자 사이의 각도를 얻을 수있게하는 것입니다. 예를 들어, 클릭하고 발사하려는 우주선이있는 게임에서 우주선을 돌릴 수있는 각도를 알아야합니다. 각도를 얻는 실제 수학은 약간 복잡하고 나는 그것을 이해하는 척하지는 않지만, 내가 가지고있는 매우 유용한 코드가 있습니다.

getAngle: function(obj1, obj2) {

// angle in radians

var angleRadians = Math.atan2(obj2.y - obj1.y, obj2.x - obj1.x);

// angle in degrees

var angleDeg = (Math.atan2(obj2.y - obj1.y, obj2.x - obj1.x) * 180 / Math.PI);

return angleDeg;

},


각도를 얻으려면이 두 객체를 전달해야합니다.

var angle = this.getAngle (goodGuy, monster);


다음은 우주선을 돌리기 위해 마우스와 우주선 사이의 각도를 사용하는 예입니다.

우주선이 마우스를 가리 키도록하려면 아무 곳이나 클릭하십시오.


그리고 예제 코드는 다음과 같습니다.

var StateMain = {

    preload: function() {

        game.load.image("ship", "images/ship.png");

    },

    create: function() {

        this.ship = game.add.sprite(game.world.centerX, game.world.centerY, "ship");

        this.ship.anchor.set(0.5, 0.5);

        game.input.onUp.add(this.clickCanvas, this);

    },

    clickCanvas: function() {

        //make an object from the mouse postion

        var obj1 = {

            x: game.input.x,

            y: game.input.y

        };

        //get the angle between the mouse and the ship and assign that to the

        //ship's angle

        this.ship.angle = this.getAngle(obj1, this.ship);

    },

    getAngle: function(obj1, obj2) {

        //I use the offset because the ship is pointing down

        //at the 6 o'clock position

        //set to 0 if your sprite is facing 3 o'clock

        //set to 180 if your sprite is facing 9 o'clock

        //set to 270 if your sprite is facing 12 o'clock

        //

        offSet = 90;

        // angle in radians

        var angleRadians = Math.atan2(obj2.y - obj1.y, obj2.x - obj1.x);

        // angle in degrees

        var angleDeg = (Math.atan2(obj2.y - obj1.y, obj2.x - obj1.x) * 180 / Math.PI);

        //add the offset

        angleDeg += offSet;

        return angleDeg;

    },

    update: function() {}

}





Posted by 마스터킹
|

이 비디오에서는 23 줄의 코드만으로 Phaser에서 미사일을 만들고 촬영하는 방법을 살펴 봅니다.


기본적인 생각은 그룹 내부에 스프라이트 미사일을 만드는 것입니다. 스프라이트를 마우스의 x 위치에 놓고 y 위치를 게임 높이 아래에 놓습니다. 그런 다음 업데이트 기능을 수행하는 동안 그룹을 반복하고 각 미사일의 y 위치에서 빼냅니다.




Posted by 마스터킹
|

자바스크립트 클래스


나는 바위 밑에서 살고있는 것처럼 느껴진다.

Javascript 클래스는 1 년 넘게 사용하었습니다. 이것은 모든 것을 바꿉니다! 설명하겠습니다.


지난 16 년 동안 게임을 만드는 대부분의 시간은 Flash를 사용하여 이루어졌습니다. Flash의 Actionscript 언어에 대한 좋은 점 중 하나는 그것이 매우 잘 구조화되어 있다는 것입니다. 클래스를 사용하고, 다른 클래스를 확장하고, 잘 관리 할 수있는 부분으로 분류 할 수 있습니다. 

html5로 옮기기에 대한 나의 가장 큰 반대 중 하나는 클래스가 없었기 때문입니다. 우리는 항상 javascript 클래스를 다음과 같이 사용할수 있습니다.

function Apple (type) {

    this.type = type;

    this.color = "red";

}

Apple.prototype.getInfo = function() {

    return this.color + ' ' + this.type + ' apple';

};

var myApple=new Apple("macintosh");

console.log(myApple.getInfo());


자, 이것은 완벽하게 잘 작동했습니다. 문제는 대부분 정리할 수있는 문제 중 하나였습니다. 이 방법으로 계속 작성했거나 복잡한 개체에 대한 이 게시물에서 사용한 것과 비슷한 개체를 반환하는 함수를 사용했습니다.


그러나, 방금 Javascript에서 실제 클래스를 사용할 수 있다는 것을 알았습니다. 기술적으로 내가 읽은 바로는 이것이 "Syntax Sugar"라는 것입니다. Javascript에서는 모든 것이 똑같이 작동하지만, 다르게 쓸 수는 있습니다. 그것은 나를 위해 충분합니다. 이제 우리는 apple 클래스를 다음과 같이 작성할 수 있습니다.

Class Apple

{

constructor(type)

{

    this.type=type;

    this.color=red;

}

getInfo()

{

   return this.color + ' ' + this.type + ' apple';

}

} 

var myApple=new Apple("macintosh");

console.log(myApple.getInfo());


Phaser 에서 클래스 사용하기

자, Phaser가 무엇을 의미합니까?

그것은 우리가 할 수있는 것을 의미합니다 :

- Phaser 클래스를 확장하는 클래스 작성

- 훨씬 더 재사용 가능한 코드 작성하기

- 코드를보다 효율적으로 정리


그리드 만들기!

예를 들어, 많은 게임과 프로젝트에 그리드가 필요합니다. 그리드에는 객체가 추가되어야하고 객체를 행과 열로 정렬해야합니다. Phaser에서는 그룹을 사용하여 각 개체를 그룹에 추가 한 다음 forEach를 사용하여 그룹 전체를 반복합니다. 그리드를 사용해야 할 때마다 프로젝트 폴더에 복사 할 수있는 클래스를 작성하고 싶습니다.


먼저 Grid라는 클래스를 만들어야합니다.

class Grid {

    constructor() {    

    }

}

두 번째 단계는 Grid 클래스가 Phaser의 그룹 클래스를 확장하도록 만드는 것입니다. 

extends 키워드를 사용하여이 작업을 수행 할 수 있습니다.


Phaser의 그룹 클래스는 Grid 클래스의 부모가됩니다.

class Grid extends Phaser.Group {

    constructor() {         

    }

}


마지막 단계는 super 명령을 사용하고 게임의 인스턴스를 매개 변수로 전달하여 부모 (Phaser.Group)의 생성자를 호출하는 것입니다. 이것은 "game"이라는 Phaser 게임 내에서 코드를 실행한다고 가정합니다. super가 생성자 내부에서 호출되는 첫 번째 것이 맞는지 확인하십시오.

class Grid extends Phaser.Group {

    constructor() {

     super(game);  

    }

}


현재 우리의 그리드는 Phaser 그룹보다 더 많은 것을하지 않습니다. 그런데 왜이 곤경에 가야합니까? 이제 우리는 우리 자신의 사용자 정의 함수와 변수를 추가 할 수 있습니다!


먼저 "cols"라는 생성자에 매개 변수를 전달하여 그리드에 포함시킬 열 수를 표시 한 다음이를 클래스 변수로 설정해 보겠습니다.

class Grid extends Phaser.Group {

    constructor(cols) {

     super(game);  

     this.cols=cols;

    }

}


항목을 추가 한 후에는 그리드에 정렬해야하므로 이제 해당 기능을 만들어 보겠습니다. 참고 : 이것은 동일한 크기의 항목에서만 잘 작동합니다.

arrange()

    {

     //current row count

     var row=0;

     //current column count

     var col=0;

     //use the group's built in for each to loop

     //through all the children

     this.forEach(function(item)

     {

     //set the position based

     //on the row, the column

     //and the height of the item

     item.y=row*item.height;

     item.x=col*item.width;

 

     //advance the column count

     col++;

     //if the column count is equal to the

     //number of columns set

     if (col==this.cols)

     {

     //go to the next row

     row++;

     //reset the column to 0

     col=0;

     }

     }.bind(this));

     //use bind(this) to keep the 'this' keyword

     //to mean the class

    }


이제 다음과 같이 그리드 클래스를 사용할 수 있습니다.

var grid=new Grid(3);

//add nine sprites

grid.add(sprite1);

....

grid.add(sprite9);

grid.arrange();


스프라이트를 확장하는 것은 어떨까요?


조금 더 많은 작업이 필요하지만 다음과 같이 할 수 있습니다.

class LetterBox extends Phaser.Sprite {

    constructor() {

     super(game,0,0,"letters",0);

 

     //add to stage right away

     game.add.existing(this);

    }

    }


스프라이트의 슈퍼 생성자에서 매개 변수는 다음과 같습니다.

super(game,x,y,"library_key",frame_number);


game.add.existing은 호출 한 후 곧바로 스프라이트를 스테이지에 추가합니다.

var letter=new LetterBox();


클래스의 인스턴스를 그룹에 추가하려는 경우 생략 할 수 있습니다.


다음은 Grid 및 LetterBox 클래스와 몇 가지 추가 함수가 추가 된 예제입니다.


StateMain.js

var StateMain = {

    preload: function() {

        //preload the sprite sheet

        game.load.atlasJSONHash('letters', "images/letters.png", "images/letters.json");

    },

    create: function() {

        //make a new grid

        //with 5 columns

        //

        var grid = new Grid(5);

        //

        //make 25 letter boxes

        //

        for (var a = 0; a &lt; 25; a++) {

            //

            //make a new letter box

            //

            var lb = new LetterBox();

            //

            //set a random letter

            //

            lb.setRand();

            //

            //add the letter to the grid

            //

            grid.add(lb);

        }

        //arrange the grid

        grid.arrange();

        //fit to the stage

        grid.fitStage();

        //put the grid in the center

        grid.center();

    },

    update: function() {}

}


The Grid Class – grid.js

class Grid extends Phaser.Group {

    constructor(cols) {

        super(game);

        this.cols = cols;

    }

    arrange() {

        //current row count

        var row = 0;

        //current column count

        var col = 0;

        //use the group's built in for each to loop

        //through all the children

        this.forEach(function(item) {

            //set the position based

            //on the row, the column

            //and the height of the item

            item.y = row * item.height;

            item.x = col * item.width;

            //advance the column count

            col++;

            //if the column count is equal to the

            //number of columns set

            if (col == this.cols) {

                //go to the next row

                row++;

                //reset the column to 0

                col = 0;

            }

        }.bind(this));

        //use bind(this) to keep the 'this' keyword

        //to mean the class

    }

    fitStage(per = 100) {

        this.width = game.width * (per / 100);

        this.scale.y = this.scale.x;

    }

    center() {

        this.x = game.width / 2 - this.width / 2;

        this.y = game.height / 2 - this.height / 2;

    }

}


The Letter Box Class – letterBox.js

class LetterBox extends Phaser.Sprite {

    constructor() {

        super(game, 0, 0, "letters", 0);

        //add to stage right away

        //game.add.existing(this);

    }

    /**

     * set by passing a lower case letter

     */

    setLetter(letter) {

        var index = letter.charCodeAt(0);

        this.frame = index - 97;

    }

    /**

     * set a random letter

     */

    setRand() {

        var index = game.rnd.integerInRange(0, 26);

        this.frame = index;

    }

}


Posted by 마스터킹
|