HTML5GAME :: '분류 전체보기' 카테고리의 글 목록 (165 Page)

달력

42025  이전 다음

  • 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

주의) 본 내용은https://itnext.io/making-a-multiplayer-blockchain-game-using-phaser-nodejs-and-ethereum-pt-1-d967aa714efe 를 구글번역기 및 개인적인 주관을 넣어 작성한 내용입니다.


Crypto Kitties는 훌륭합니다. 스마트 계약이 얼마나 강력하고 더 중요하게 재미 있는지 보여주는 첫 번째 dApp이었습니다.


Etheremon이라는 또 하나의 블록 체인 게임이 최근에 다소 생겨났습니다. 그것은 기본적으로 blockchain Pokemon입니다. 


이 게임의 유일한 문제는 비용입니다.


나는 Etheremon을 전투하기 위해 약 15 달러 소요 하였습니다.


이때 나에게 생각이 떠 올랐습니다. Ethereum 위에 재미있는 게임을 만드는 길은 있어야 합니다.  그러나 아직도 싸고 접근하기 쉽습니다.




게임 컨셉


게임은 베틀로얄 컨셉입니다. 플레이어는 캐릭터와 아이템 (각자 자신의 ERC721 토큰이 됨)을 가지고 게임에 참가하고 죽음에 맞서 싸웁니다.


그것을 균일하게 만들기 위해, 게임 플레이는 매우 기초적인 스킬이 될 것입니다. 이 게임은 2D 물리 기반 사이드 스크롤러가 될 것입니다. 


아이템은 플레이어에게 새로운 기술 세트를 제공하고 상태값 만 약간 늘립니다. 어쩌면 한 쌍의 부츠가 중력을 감소 시키거나 특정 샷건이 주위를 날아갈 수 있습니다.


훌륭한 아이템 세트로 게임에 참여하면 도움이되지만 전투를 이기기 위해 플레이어를 충분히 제어 할 수 있어야 합니다.


플레이어는 토큰이 될 세계의  상자를 찾을 수 있습니다. 이 상자는 게임 외부에서 열리며 플레이어에게 무작위로 하나 또는 두 개의 아이템을 생성합니다.


일단 플레이어가 무료로 게임을 즐길수 있어야 합니다. 그들이 거래에 돈을 쓸 필요가있는 유일한 시간은 상자를 열어 아이템을 획득하는 행위 입니다.




스택


클라이언트는 Phaser로 만들어집니다. 그러면 서버의 정보가 사용자에게 전달됩니다.


서버는 NodeJS로 만들어집니다. 사용자는 계정 자격 증명으로 작용하는 Ethereum 지갑을 사용하여 게임에 참여합니다. Web3를 사용하면 서버는 인벤토리 및 현재 로드와 같이 플레이어에 대한 모든 정보를 가져옵니다.


Smart Contract는 Solidity로 이루어질 것입니다. 이것은 모든 문자와 항목을 ERC721 토큰으로 처리합니다.  상자와 아이템 생성도 처리합니다.




이후에는 중요한 내용이 없습니다. 다음 회차로 이동하세요~



Posted by 마스터킹
|



이 튜토리얼에서는 아주 기본적인 실시간 멀티 플레이어 온라인 게임의 클라이언트와 서버를 프로그래밍하는 방법과 Socket.io를 사용하여 상호 작용하는 방법을 설명합니다. 이 작은 게임에서는 각 플레이어가지도를 클릭하여 캐릭터를 움직이게하고 다른 플레이어의 캐릭터는 화면에서 실시간으로 움직입니다. 이 기본 게임은별로 재미 있지는 않지만, 자신 만의 흥미 진진한 게임을 만들 수있는 기반이 될 것입니다.


대부분의 멀티 플레이어 온라인 게임 (특히 대규모 멀티 플레이어 게임)은 서버 - 클라이언트 아키텍처를 따릅니다. 각 플레이어는 클라이언트라는 소프트웨어를 실행하여 게임을 표시하고 플레이어의 입력을 처리하며, 각 클라이언트는 중앙의 신뢰할 수있는 서버와 데이터를 교환하여 플레이어의 동작을 확인하고 다른 클라이언트에 브로드 캐스트합니다.


우리의 경우 클라이언트는 Javascript로 Phaser로 작성되어 플레이어의 브라우저에서 실행됩니다. 이 튜토리얼은 Phaser (게임 상태 및 관련 함수, 입력 처리 ...)에 대한 기본 지식을 전제로합니다.

서버는 Javascript로 작성되었으며 Node.js와 Express 모듈을 사용합니다. 클라이언트와 서버는 Socket.io를 사용하여 통신합니다.


이 데모에 사용 된 자산은 GitHub 저장소에 있습니다. 지도는 Mozilla의 BrowserQuest지도의 작은 부분입니다. tileset과 플레이어 스프라이트는 BrowserQuest에서도 빌려 왔습니다.



클라이언트와 서버 설정하기

이 첫 번째 부분에서는 파일을 클라이언트에 제공하기 위해 기본 Node.js 서버를 프로그래밍합니다. 또한 기본 게임 파일을 설정합니다. Node.js / Express와 Phaser에 익숙하다면 이 부분을 건너 뛸 수 있습니다. 하지만 코드가 어떻게 구성되어 있는지 느껴지면 유용 할 것입니다. 이 튜토리얼의 초점이 게임 논리이므로 너무 많은 세부 사항을 입력하지 않고도 코드의이 부분이 무엇을하는지 간단히 설명 할 것입니다. 이것이 명확하지 않은 경우 관련 문서를 보거나 의견 섹션에서 설명을 요청하십시오. 명확하게 설명 드리겠습니다.



- 서버 Server.js -

먼저 필요한 모든 Node.js 모듈을 요구합니다. Node.js와 함께 사용하는 환경에 설치해야합니다. npm (예 :`npm install express`)을 사용하여 그렇게 할 수 있습니다. GitHub에서 코드를 실행하면`npm install`을 실행하여 package.json 파일을 읽고 필요한 모든 모듈을 설치합니다.

var express = require('express');
var app = express();
var server = require('http').Server(app);
var io = require('socket.io').listen(server);

Express는 클라이언트에게 파일을 제공하는 데 사용할 모듈입니다. app이라는 새 인스턴스를 만들고이를 http 모듈과 결합하여 Express 응용 프로그램이 http 서버로 작동하도록합니다. 마지막으로 socket.io 모듈이 필요하며 해당 서버에 대한 연결을 수신 대기하게 만듭니다. 이것은 단순한 앱을 시작하기위한 보편적 인 코드입니다.


다음 단계는 요청 된 경로에 따라 파일을 전달하는 것입니다.

app.use('/css',express.static(__dirname + '/css'));
app.use('/js',express.static(__dirname + '/js'));
app.use('/assets',express.static(__dirname + '/assets'));

app.get('/',function(req,res){
    res.sendFile(__dirname+'/index.html');
});

이러한 라인은 직접 액세스 할 수 없지만 게임에서 액세스해야하는 CSS 스타일 시트 또는 게임 애셋과 같은 정적 파일을 제공 할 수 있어야합니다. 편의상 app.use ()의 두 번째 인수는 리소스에 대한 실제 경로가 아닌 가상 경로를 지정할 수있게하지만 스크립트에 액세스하는 데 사용되는 경로가됩니다.


서버가 통신해야하는 포트를 지정하고 루트 페이지로 사용할 파일을 지정하여 서버 설정을 완료합니다.

app.get('/',function(req,res){
    res.sendFile(__dirname+'/index.html'); 
});

server.listen(8081,function(){ // Listens to port 8081
    console.log('Listening on '+server.address().port);
});



- 클라이언트 index.html -

Index.html은 서버에 연결할 때 표시되는 게이트웨이 페이지 이며 이 경우 게임이 표시 될 페이지입니다. 그러나 당신이 원하는대로 구조화하는 것은 당신에게 달려 있지만 적어도 id라는 게임 요소를 포함해야합니다. 또한 socket.io를 포함하여 게임에 필요한 Javascript 파일을 포함해야합니다. 포함시키는 방법은 다음과 같습니다.

<script src="/socket.io/socket.io.js"></script>

이는 socket.io를 설치할 때 가상 경로 /socket.io/socket.io.js가 자동으로 만들어지기 때문에 작동합니다.


이제 터미널에서 `node server.js`를 입력하여 서버를 실행하고 (예 : http://localhost:8081/ 에서 로컬로 실행하고 기본 포트를 유지하는 경우) 응용 프로그램을 탐색하면 index.html이 제공되는 내용 (여기서는 "Hello World"일 수도 있음)과 함께 이 위치에 표시된 내용을 볼 수 있습니다. 이제 game.js를 포함시켜 게임을 설정해 보겠습니다. 앞으로 이 프로세스를 "게임 실행"이라고합니다.



js/game.js -

이제 우리는 게임 캔버스를 설정하고 (게임 ID가 'div'인 div 블록이 있다고 가정) 동일한 이름의 Javascript 객체에 해당하는 'Game'이라는 단일 게임 상태를 선언 할 수 있습니다.

var game = new Phaser.Game(16*32, 600, Phaser.AUTO, document.getElementById('game'));
game.state.add('Game',Game);
game.state.start('Game');
var Game = {};


Game.init()에는 설정할 매개 변수가 하나뿐입니다.

Game.init = function(){
    game.stage.disableVisibilityChange = true;
};

이것은 필수는 아니지만 유용합니다. 게임 창에 포커스가 없는 경우 (대부분의 게임에서 원하는 동작) 서버가 보낸 메시지에 게임이 계속 반응하게 만듭니다.


Game.preload()에서 타일 맵을 포함한 JSON 형식의 타일을 포함하여 필요한 에셋을 로드합니다. Tile tilemaps를 만들고 다루는 것에 관해서는 여기서는 자세히 다루지 않겠지만, 이 부분을 다루기 위해 튜토리얼을 만들고 싶다면 주저하지 말고 알려주세요.

Game.preload = function() {
    game.load.tilemap('map', 'assets/map/example_map.json', null, Phaser.Tilemap.TILED_JSON);
    game.load.spritesheet('tileset', 'assets/map/tilesheet.png',32,32);
    game.load.image('sprite','assets/sprites/sprite.png'); // this will be the sprite of the players
};


Game.create()에서 지도를 만들고 표시하는 것으로 시작합니다.

Game.create = function(){
    var map = game.add.tilemap('map');
    map.addTilesetImage('tilesheet', 'tileset'); // tilesheet is the key of the tileset in map's JSON file
    var layer;
    for(var i = 0; i < map.layers.length; i++) {
        layer = map.createLayer(i);
    }
    layer.inputEnabled = true; // Allows clicking on the map
};



클릭이 지도에서 사용할수 있도록 설정되었지만, 현재 처리중인 코드가 없습니다. 이것은 서버가 가동되어 클라이언트와 서버 간의 통신이 작동하면 시작됩니다.


이 시점에서 게임을 실행할 때, 아무 일도 일어나지 않고 지도가 표시되어야합니다.



js/client.js -

index.html에는 서버와 게임 자체 사이의 인터페이스 역할을하는 Client 객체를 포함하는 새로운 Javascript 파일인 client.js가 포함됩니다.

var Client = {};
Client.socket = io.connect();

여기에서 중요한 것은 두 번째 줄입니다. 여기서는 서버 (괄호 사이에 달리 지정하지 않으면 localhost)에 대한 연결을 시작합니다. 플레이어가 앱을 탐색 할 때마다 서버와의 연결이 설정됩니다. 이렇게하면 소켓이 생성됩니다. 소켓은 서버와 클라이언트 간의 통신 흐름에서 끝점입니다. socket.io를 사용하면 클라이언트와 서버가 상호 작용할 수있는 기본적인 방법을 구성하는 소켓을 통해 메시지를 보내고받을 수 있습니다. 여기서 클라이언트의 소켓은 나중에 사용하기 위해 Client.socket에 저장됩니다.



실시간 상호 작용

이제 재미있는 부분이 시작됩니다. 우리는 서버가 플레이어의 행동을 인식 할뿐만 아니라 클라이언트가 서버에서 오는 메시지에 반응하도록 해야합니다. 플레이어가 액션 (연결, 연결 해제 또는 이동)을 수행하면 Socket.io API를 사용하여 서버에 메시지를 보내서 이 작업을 알립니다. 그 대가로 서버는 다른 플레이어의 동작을 알릴 필요가 있을 때 동일한 API를 사용하여 클라이언트에게 메시지를 보냅니다. 이 튜토리얼의 나머지 부분에서는 이러한 메시지를 보내고 받는 방법과 Phaser 게임과 통합하는 방법을 설명합니다.



- 연결된 플레이어 표시 -

새 플레이어가 연결되면 새로 연결된 스프라이트를 포함하여 연결된 모든 플레이어에 대해 새로운 스프라이트가 게임에 표시됩니다. 스프라이트의 좌표는 서버에 의해 무작위로 결정됩니다.


js/game.js -

먼저 game.js에서 Game.create()를 수정하여 클라이언트가 새 플레이어를 만들어야한다는 사실을 서버에 알리도록합시다. 이를 위해 Client.askNewPlayer()를 추가합니다. Game.create()의 처음에 Game.playerMap = {} 을 추가합니다.  이 빈 객체는 나중에 플레이어를 추적하는 데 유용합니다.

Game.create = function(){
    Game.playerMap = {};
    var map = game.add.tilemap('map');
    map.addTilesetImage('tilesheet', 'tileset'); // tilesheet is the key of the tileset in map's JSON file
    var layer;
    for(var i = 0; i < map.layers.length; i++) {
        layer = map.createLayer(i);
    }
    layer.inputEnabled = true; // Allows clicking on the map
    Client.askNewPlayer();
};


js/client.js -

이제 client.js에서 Client.askNewPlayer() 메서드를 정의해야합니다.

Client.askNewPlayer = function(){
    Client.socket.emit('newplayer');
};

이 메서드는 소켓 객체를 사용하여 서버에 메시지를 보냅니다. 이 메시지는 'newplayer'라는 레이블을 가지며 이는 자명합니다. 두 번째 인수는 추가 데이터를 전달하기 위해 추가 될 수 있지만이 경우에는 필요하지 않습니다.


server.js -

server.js에서 클라이언트의 메시지에 반응해야합니다. 다음 코드를 추가하십시오.

server.lastPlayderID = 0; // 새 플레이어에게 할당 된 마지막 ID를 추적합니다.

io.on('connection',function(socket){
    socket.on('newplayer',function(){
        socket.player = {
            id: server.lastPlayderID++,
            x: randomInt(100,400),
            y: randomInt(100,400)
        };
        socket.emit('allplayers',getAllPlayers());
        socket.broadcast.emit('newplayer',socket.player);
    });
});

function getAllPlayers(){
    var players = [];
    Object.keys(io.sockets.connected).forEach(function(socketID){
        var player = io.sockets.connected[socketID].player;
        if(player) players.push(player);
    });
    return players;
}

function randomInt (low, high) {
    return Math.floor(Math.random() * (high - low) + low);
}

우리는 Socket.io에게 클라이언트가 (io.connect()를 사용하여) 서버에 연결할 때마다 발생하는 'connection'이벤트를 듣도록 지시합니다. 이 경우 두 번째 인수로 지정된 콜백을 호출해야합니다. 이 콜백은 연결을 설정하는 데 사용 된 소켓을 첫 번째 인수로받습니다.이 소켓은 클라이언트 소켓과 마찬가지로 메시지를 전달하는 데 사용될 수 있습니다.


소켓 객체에서 socket.on() 메소드를 사용하면 콜백을 지정하여 다른 메시지를 처리 할 수 있습니다. 따라서 특정 클라이언트가 자신의 소켓을 통해 특정 메시지를 보낼 때마다 반응에서 특정 콜백이 호출됩니다. 이 경우 'newplayer'메시지에 반응 할 콜백을 정의합니다. 여기서 수행되는 작업을 분해합시다.

socket.on('newplayer',function(){
        socket.player = {
            id: server.lastPlayderID++,
            x: randomInt(100,400),
            y: randomInt(100,400)
        };
        socket.emit('allplayers',getAllPlayers());
        socket.broadcast.emit('newplayer',socket.player);
    });


먼저 플레이어를 나타내는 데 사용되는 새 사용자 지정 개체를 만들어 소켓 개체에 저장합니다. 보시다시피 소켓 객체에 임의의 클라이언트 특정 속성을 추가하여 액세스하기 쉽게 만들 수 있습니다. 이 객체에서 플레이어에게 고유 한 ID (클라이언트 측에서 사용됨)를 지정하고 임의로 스프라이트의 위치를 결정합니다. 그런 다음 이미 연결된 플레이어 목록을 새 플레이어에게 보내려고합니다.

socket.emit('allplayers',getAllPlayers());

Socket.emit()은 특정 소켓 하나에 메시지를 보낸다. 여기서는 새로 연결된 클라이언트에게 'allplayers'라는 메시지를 보내고 두 번째 인수로 현재 연결된 플레이어의 배열이 될 Client.getAllPlayers()의 리턴값을 보냅니다. 이렇게 하면 새로 연결된 플레이어가 이미 연결된 유저수와 위치를 알 수 있습니다. Client.getAllPlayers()를 간단히 살펴 보겠습니다.

function getAllPlayers(){
    var players = [];
    Object.keys(io.sockets.connected).forEach(function(socketID){
        var player = io.sockets.connected[socketID].player;
        if(player) players.push(player);
    });
    return players;
}

io.sockets.connected는 현재 서버에 연결된 소켓의 Socket.io 내부 배열입니다. 모든 소켓을 반복 처리하고, 추가 한 플레이어 속성 (있는 경우)을 가져 와서 목록에 추가하여 연결된 플레이어를 효과적으로 나열 할 수 있습니다. 그리고 마지막으로:

socket.broadcast.emit('newplayer',socket.player);

socket.emit.broadcast()는 콜백을 트리거 한 소켓을 제외한 모든 연결된 소켓에 메시지를 보냅니다. 클라이언트에서 시작 클라이언트로 이벤트를 반향하지 않고 다른 모든 클라이언트로 이벤트를 브로드 캐스트 할 수 있습니다. 여기서 우리는 'newplayer'메시지를 방송하고 새로운 플레이어 객체를 데이터로 보냅니다.


마지막 몇 단계에서 수행 한 작업을 요약하면 다음과 같습니다.

- 클라이언트로부터의 연결신호를 받고 소켓을 통해 전송 된 메시지를 처리하기위한 콜백을 정의합니다.

- 클라이언트로부터 'newplayer'메시지를 받으면 우리는 클라이언트의 소켓에 저장 한 작은 플레이어 객체를 생성합니다

- 새 클라이언트에게 우리는 모든 다른 플레이어의 목록을 보내서 표시 할 수 있습니다.

- 다른 유저에게 우리는 새로운 유저에 대한 정보를 보냅니다.


지금까지 우리 서버는 클라이언트의 메시지 하나에 반응합니다. 우리는 이제 클라이언트가 서버에서 'allplayers' 및  'newplayer' 메시지를 처리 할 수 있도록 조정해야 하므로 '연결 - 여기에있는 서버에 알림 - 돌아 가기 정보 표시 - 표시' 루프를 완료해야합니다. 클라이언트가 보낸 'newplayer'메시지와 서버가 보낸 메시지는 같지 않습니다. 같은 종류의 정보를 전달하기 때문에 동일한 레이블을 제공하기로했지만, 다른 끝점 (전자는 서버, 후자는 클라이언트)이 있으므로 별도로 처리됩니다.


js/client.js -

client.js에 다음 코드를 추가하십시오.

Client.socket.on('newplayer',function(data){
    Game.addNewPlayer(data.id,data.x,data.y);
});

Client.socket.on('allplayers',function(data){
    console.log(data);
    for(var i = 0; i < data.length; i++){
        Game.addNewPlayer(data[i].id,data[i].x,data[i].y);
    }
});

보시다시피 메시지를 처리하는 동일한 구문을 클라이언트 측에서 사용할 수 있습니다. 데이터가 메시지를 따라 전송되면 수신 측에서 콜백의 첫 번째 인수로 검색 할 수 있습니다. 따라서 'newplayer'콜백에 제공되는 'data'객체는 서버가 보낸 socket.player 데이터에 해당합니다. 'allplayers'메시지의 경우 socket.player 객체의 목록입니다. 두 경우 모두이 데이터는 Game.addNewPlayer()를 호출하여 처리됩니다. 이제 game.js에 정의 할 수 있습니다.


js/game.js -

Game.addNewPlayer = function(id,x,y){
    Game.playerMap[id] = game.add.sprite(x,y,'sprite');
};

이 메서드는 지정된 좌표에 새 스프라이트를 만들고 제공된 ID를 키로 사용하여 Game.create()에 선언 된 연관 배열에 해당 Sprite 객체를 저장합니다. 이렇게 하면 특정 플레이어에 해당하는 스프라이트에 쉽게 액세스 할 수 있습니다 (예 : 이동하거나 제거해야하는 경우) (아래 참조).


이 시점에서 서버를 다시 시작하여 마지막 수정 사항을 고려하면 게임으로 이동하면 스프라이트에 해당하는 작은 문자가 표시됩니다. 다른 브라우저와 연결하면 추가 문자가 화면에 나타납니다.



연결 해제 처리

플레이어가 연결을 끊으면 스프라이트가 다른 플레이어의 화면에 남아 있기 때문에 바람직하지 않습니다. 이는 클라이언트가 능동적으로 연결을 끊거나 시간 초과 될 때 서버가 자동으로 수신하는 'disconnet' 메시지를 처리하여 해결할 수 있습니다. 이 메시지는 'newplayer' 처럼 'io.on()'메서드 내에서 콜백을 바인딩하여 다음과 같이 처리 할 수 있습니다.

io.on('connection',function(socket){

    socket.on('newplayer',function(){
        socket.player = {
            id: server.lastPlayderID++,
            x: randomInt(100,400),
            y: randomInt(100,400)
        };
        socket.emit('allplayers',getAllPlayers());
        socket.broadcast.emit('newplayer',socket.player);

        socket.on('disconnect',function(){
            io.emit('remove',socket.player.id);
        });
    });
});

'disconnect' 메시지에 대한 응답으로 io.emit()을 사용하여 연결된 모든 클라이언트에게 메시지를 보냅니다. 'remove'라는 메시지를 보내고 분리 된 플레이어의 ID를 보내 제거합니다.

참고 : 'disconnect' 콜백은 'newplayer' 콜백 내에 등록되어야한다는 Kaundur의 지적에 감사드립니다. 그렇지 않은 경우 'disconnect'가 'newplayer'전에 호출되는 경우 서버가 중단됩니다.


js/client.js -

client.js에서는 다음과 같이 처리됩니다.

Client.socket.on('remove',function(id){
    Game.removePlayer(id);
});


그리고 game.js에서 :

js/game.js -

Game.removePlayer = function(id){
    Game.playerMap[id].destroy();
    delete Game.playerMap[id];
};

이것은 Game.playerMap 데이터 구조의 사용법을 보여줍니다. 스프라이트를 반복 할 필요가 없습니다. id를 사용하면 즉시 가져올 수 있습니다. 이제는 플레이어의 움직임을 처리하고 Broadcast 하는 것만 남았습니다.



플레이어의 움직임 처리하기

js/game.js -

이제 Game.create()를 완료 할 차례입니다. 기본적으로 지도를 클릭하면 좌표가 서버로 전송되므로 클릭 한 플레이어의 위치를 모든 사용자가 업데이트 할 수 있어야 합니다. Game.create()에 다음 행을 추가합니다.

layer.events.onInputUp.add(Game.getCoordinates, this);

이제 지도는 Game.getCoordinates() 메소드를 호출하여 클릭에 반응합니다.이 메소드는 다음과 같이 정의 할 수 있습니다.

Game.getCoordinates = function(layer,pointer){
    Client.sendClick(pointer.worldX,pointer.worldY);
};

Phaser의 onInputUp 이벤트 콜백은 해당 포인터 객체를 두 번째 인수로받습니다.이 포인터 객체에는 worldX와 worldY 속성이 포함되어 있으며 게임지도에서 클릭이 발생하는 위치를 파악하는 데 사용할 수 있습니다. 다음 좌표를 client.js의 Client.sendClick()에 전달할 수 있습니다.


js/client.js -

Client.sendClick = function(x,y){
  Client.socket.emit('click',{x:x,y:y});
};

단순히 좌표를 서버에 보내고 레이블은 'click'입니다. 소켓은 클라이언트마다 다르기 때문에 플레이어 ID를 보낼 필요가 없습니다. 단 하나의 플레이어 만 연결할 수 있습니다.


server.js -

server.js에서 메시지 콜백의 최종 목록은 다음과 같습니다.

io.on('connection',function(socket){

    socket.on('newplayer',function(){
        socket.player = {
            id: server.lastPlayderID++,
            x: randomInt(100,400),
            y: randomInt(100,400)
        };
        socket.emit('allplayers',getAllPlayers());
        socket.broadcast.emit('newplayer',socket.player);

        socket.on('click',function(data){
            console.log('click to '+data.x+', '+data.y);
            socket.player.x = data.x;
            socket.player.y = data.y;
            io.emit('move',socket.player);
        });

        socket.on('disconnect',function(){
            io.emit('remove',socket.player.id);
        });
    });
});

소켓의 플레이어 속성의 x 및 y 필드는 새 좌표로 업데이트 된 다음 변경 사항을 볼 수 있도록 모든 사용자에게 즉시 브로드 캐스트됩니다. 이제는 다른 플레이어가 이동중인 플레이어의 ID를 알아야하기 때문에 완전한 socket.player 객체가 전송됩니다. 화면에 적절한 스프라이트를 이동하려면 (이 미니멀리스트 게임에서는 실제 방법이 없습니다 선수를 구별하기 위해).


js/client.js -

client.js로 돌아가서, 클라이언트가 움직이는 다른 플레이어에게 반응 할 수 있도록 서버에서 'move'메시지를 처리해야합니다.

Client.socket.on('move',function(data){
    Game.movePlayer(data.id,data.x,data.y);
});

이제 프로세스가 익숙해지기 시작합니다. game.js에서 :


js/game.js -

Game.movePlayer = function(id,x,y){
    var player = Game.playerMap[id];
    var distance = Phaser.Math.distance(player.x,player.y,x,y);
    var duration = distance*10;
    var tween = game.add.tween(player);
    tween.to({x:x,y:y}, duration);
    tween.start();
};

우리는 다시 Game.playerMap 구조를 사용하여 적절한 스프라이트를 검색 한 다음 트위닝하여 점진적으로 움직임을 만듭니다.



결론

이제 게임을 실행하면 다른 클라이언트의 동작에 따라 실시간으로 스프라이트가 이동하고 표시되는지 확인할 수 있습니다. 그리고 많은 개선이 가능합니다. 나는 당신에게 연습 문제로 남겨 둡니다. 몇 가지 예를 들면 다음과 같습니다.

- 스프라이트 시트를 사용하여 스프라이트의 움직임을 애니메이션하십시오.

- "충돌", 즉 플레이어가 이동할 수없는 영역 지정

- 자신 만의 더 큰지도를 사용하고 카메라가 플레이어를 따라 가게하십시오.

- 플레이어가 캐릭터의 이름을 지정하거나 스프라이트를 변경하도록 허용


그것에 대한 수요가 있다면, 나는 이러한 가능한 향상을 위해 튜토리얼을 만드는 경향이있을 것이다. 관심이 있다면 저에게 연락 주시기 바랍니다.


내가했던 것처럼 Heroku에서이 예제 프로젝트를 배포하는 방법을 배우고 싶다면이 튜토리얼을 확인해 보시면 됩니다.


Phaser로 만든 멀티 플레이어 온라인 게임의보다 복잡한 예를 보려면 Phaser Quest를 살펴보십시오!

Posted by 마스터킹
|

일반 전략

Pong은 간단한 게임이므로 컴퓨터 AI에 간단한 2 단계 전략을 제공 할 수 있습니다.

모델 반응 시간 - 의사 결정을 하기 전에 일정 기간 기다리기

모델 정확도 - 컴퓨터가 볼의 착륙 지점을 정확하게 알고 컴퓨터에 오류가없는 것처럼 보이는 임의의 오류 요인을 추가합니다.

우리는 게임이 느슨해지기 시작하면 컴퓨터 기술을 더 잘 발달 시키거나 컴퓨터가 지배적 일 경우 더 나쁘게 만들어 게임의 균형을 유지하려고 할 수 있습니다.

Levels: [
  {aiReaction: 0.2, aiError:  40}, // 0:  ai is losing by 8
  {aiReaction: 0.3, aiError:  50}, // 1:  ai is losing by 7
  {aiReaction: 0.4, aiError:  60}, // 2:  ai is losing by 6
  {aiReaction: 0.5, aiError:  70}, // 3:  ai is losing by 5
  {aiReaction: 0.6, aiError:  80}, // 4:  ai is losing by 4
  {aiReaction: 0.7, aiError:  90}, // 5:  ai is losing by 3
  {aiReaction: 0.8, aiError: 100}, // 6:  ai is losing by 2
  {aiReaction: 0.9, aiError: 110}, // 7:  ai is losing by 1
  {aiReaction: 1.0, aiError: 120}, // 8:  tie
  {aiReaction: 1.1, aiError: 130}, // 9:  ai is winning by 1
  {aiReaction: 1.2, aiError: 140}, // 10: ai is winning by 2
  {aiReaction: 1.3, aiError: 150}, // 11: ai is winning by 3
  {aiReaction: 1.4, aiError: 160}, // 12: ai is winning by 4
  {aiReaction: 1.5, aiError: 170}, // 13: ai is winning by 5
  {aiReaction: 1.6, aiError: 180}, // 14: ai is winning by 6
  {aiReaction: 1.7, aiError: 190}, // 15: ai is winning by 7
  {aiReaction: 1.8, aiError: 200}  // 16: ai is winning by 8
],

패들 예측

패들 AI는이 전략을 따릅니다.

- 공이 떨어져 나가면 아무것도하지 마라.

- 그렇지 않으면 공이 코트의 패들 가장자리와 만나는 지점을 예측하십시오.


- 우리가 예측을 한다면 위아래로 움직여 그것을 만난다.

ai: function(dt, ball) {
  if (((ball.x < this.left) && (ball.dx < 0)) ||
      ((ball.x > this.right) && (ball.dx > 0))) {
    this.stopMovingUp();
    this.stopMovingDown();
    return;
  }
  this.predict(ball, dt);
  if (this.prediction) {
    if (this.prediction.y < (this.top + this.height/2 - 5)) {
      this.stopMovingDown();
      this.moveUp();
    }
    else if (this.prediction.y > (this.bottom - this.height/2 + 5)) {
      this.stopMovingUp();
      this.moveDown();
    }
    else {
      this.stopMovingUp();
      this.stopMovingDown();
    }
  }
},

예측 코드는 약간 까다 롭습니다.

첫째, 반응 시간에 도달 할 때까지 아무것도 하지 않습니다.

둘째, 무한히 큰 코트를 가정 한 공의 절편을 계산하십시오.

셋째, 패들쪽에 도달 할 때까지 위쪽 및 아래쪽 벽에서 반복적으로 예측을 바운스합니다.


마지막으로 우리의 오차 요인을 적용하여 이것이 '추측'합니다.

predict: function(ball, dt) {
  // only re-predict if the ball changed direction, or its been some amount of time since last prediction
  if (this.prediction &&
      ((this.prediction.dx * ball.dx) > 0) &&
      ((this.prediction.dy * ball.dy) > 0) &&
      (this.prediction.since < this.level.aiReaction)) {
    this.prediction.since += dt;
    return;
  }
  var pt  = Pong.Helper.ballIntercept(ball, {left: this.left, right: this.right, top: -10000, bottom: 10000}, ball.dx * 10, ball.dy * 10);
  if (pt) {
    var t = this.minY + ball.radius;
    var b = this.maxY + this.height - ball.radius;
    while ((pt.y < t) || (pt.y > b)) {
      if (pt.y < t) {
        pt.y = t + (t - pt.y);
      }
      else if (pt.y > b) {
        pt.y = t + (b - t) - (pt.y - b);
      }
    }
    this.prediction = pt;
  }
  else {
    this.prediction = null;
  }
  if (this.prediction) {
    this.prediction.since = 0;
    this.prediction.dx = ball.dx;
    this.prediction.dy = ball.dy;
    this.prediction.radius = ball.radius;
    this.prediction.exactX = this.prediction.x;
    this.prediction.exactY = this.prediction.y;
    var closeness = (ball.dx < 0 ? ball.x - this.right : this.left - ball.x) / this.pong.width;
    var error = this.level.aiError * closeness;
    this.prediction.y = this.prediction.y + Game.random(-error, error);
  }
},

이 게임은 이제 이 데모가있는 1 인 및 2 인 게임으로 거의 완벽합니다.




Posted by 마스터킹
|

공이동

게임을 하기 위해서는 공을 가속화 해야합니다. 위치, 속도, 가속도 및 시간 간격 dt가 주어지면 볼의 새로운 위치와 속도는 다음과 같이 계산 될 수 있습니다.

accelerate: function(x, y, dx, dy, accel, dt) {
  var x2  = x + (dt * dx) + (accel * dt * dt * 0.5);
  var y2  = y + (dt * dy) + (accel * dt * dt * 0.5);
  var dx2 = dx + (accel * dt) * (dx > 0 ? 1 : -1);
  var dy2 = dy + (accel * dt) * (dy > 0 ? 1 : -1);
  return { nx: (x2-x), ny: (y2-y), x: x2, y: y2, dx: dx2, dy: dy2 };
},

가속화가되면 ball의 update () 메소드는 다음을 수행해야 합니다.

- 공의 새로운 위치와 속도를 계산하십시오.

- 상단 또는 하단 벽에서 튀어 나오면 감지 (단순 경계 검사)

- 패들에서 튀었는지 탐지합니다 (다음 섹션 참조).


- 공이 움직이는 외륜을 치면 스핀을 시뮬레이트하기 위해 y 속도를 조정하십시오.

update: function(dt, leftPaddle, rightPaddle) {
  pos = Pong.Helper.accelerate(this.x, this.y, this.dx, this.dy, this.accel, dt);

  if ((pos.dy > 0) && (pos.y > this.maxY)) {
    pos.y = this.maxY;
    pos.dy = -pos.dy;
  }
  else if ((pos.dy < 0) && (pos.y < this.minY)) {
    pos.y = this.minY;
    pos.dy = -pos.dy;
  }
  var paddle = (pos.dx < 0) ? leftPaddle : rightPaddle;
  var pt     = Pong.Helper.ballIntercept(this, paddle, pos.nx, pos.ny);
  if (pt) {
    switch(pt.d) {
      case 'left':
      case 'right':
        pos.x = pt.x;
        pos.dx = -pos.dx;
        break;
      case 'top':
      case 'bottom':
        pos.y = pt.y;
        pos.dy = -pos.dy;
        break;
    }
    // add/remove spin based on paddle direction
    if (paddle.up)
      pos.dy = pos.dy * (pos.dy < 0 ? 0.5 : 1.5);
    else if (paddle.down)
      pos.dy = pos.dy * (pos.dy > 0 ? 0.5 : 1.5);
  }
  this.setpos(pos.x,  pos.y);
  this.setdir(pos.dx, pos.dy);
},

볼 및 패들 교차점

위 update () 메서드에서 사용 된 ballIntercept () 메서드는 프레임 간격 (dt) 동안 볼이 패들에 충돌하는지 여부를 정확하게 감지해야 합니다.

공이 시간 간격 (dt) 동안 p1에서 p2로 이동하고 패들 가장자리가 p3에서 p4로 늘어나고 충돌하는지 확인해야합니다.

우리는 공이 배트의 명백한면에 충돌 하는지 알 수 있기를 원합니다.

- 공이 왼쪽으로 움직이는 경우 플레이어 1의 오른쪽 가장자리를 확인하십시오.


- 공이 오른쪽으로 움직이는 경우 플레이어 2의 왼쪽 가장자리를 확인하십시오.

그러나 우리는 또한 공이 '빗나 갔다'는 눈에 띄지 않는 가장자리가 있는지를 알기 위해 위쪽과 아래쪽과 같은 명백하지 않은 부분을 확인하려고 하지만 목표를 향해 나가기 전에 위쪽이나 아래쪽으로 튀어 나옵니다. 대부분의 pong 게임은 이것으로 귀찮게 하지 않지만, 우리는 완성을 위해 노력하고 있습니다. 그래서 간단한 라인 intercept() 메서드가 있다고 가정하면 ballIntercept ()는 다음과 같이 구현 될 수 있습니다 :

ballIntercept: function(ball, rect, nx, ny) {
  var pt;
  if (nx < 0) {
    pt = Pong.Helper.intercept(ball.x, ball.y, ball.x + nx, ball.y + ny,
                               rect.right  + ball.radius,
                               rect.top    - ball.radius,
                               rect.right  + ball.radius,
                               rect.bottom + ball.radius,
                               "right");
  }
  else if (nx > 0) {
    pt = Pong.Helper.intercept(ball.x, ball.y, ball.x + nx, ball.y + ny,
                               rect.left   - ball.radius,
                               rect.top    - ball.radius,
                               rect.left   - ball.radius,
                               rect.bottom + ball.radius,
                               "left");
  }
  if (!pt) {
    if (ny < 0) {
      pt = Pong.Helper.intercept(ball.x, ball.y, ball.x + nx, ball.y + ny,
                                 rect.left   - ball.radius,
                                 rect.bottom + ball.radius,
                                 rect.right  + ball.radius,
                                 rect.bottom + ball.radius,
                                 "bottom");
    }
    else if (ny > 0) {
      pt = Pong.Helper.intercept(ball.x, ball.y, ball.x + nx, ball.y + ny,
                                 rect.left   - ball.radius,
                                 rect.top    - ball.radius,
                                 rect.right  + ball.radius,
                                 rect.top    - ball.radius,
                                 "top");
    }
  }
  return pt;
}

선 교차점

ballIntercept () 메서드는 일반적인 intersect 메서드를 사용합니다.

공이 (x1, y1)에서 (x2, y2)로 이동하고 패들 가장자리가 (x3, y3)에서 (x4, y4)로 이동한다고 가정하면 intercept () 메서드는 다음과 같이 구현할 수 있습니다.

intercept: function(x1, y1, x2, y2, x3, y3, x4, y4, d) {
  var denom = ((y4-y3) * (x2-x1)) - ((x4-x3) * (y2-y1));
  if (denom != 0) {
    var ua = (((x4-x3) * (y1-y3)) - ((y4-y3) * (x1-x3))) / denom;
    if ((ua >= 0) && (ua <= 1)) {
      var ub = (((x2-x1) * (y1-y3)) - ((y2-y1) * (x1-x3))) / denom;
      if ((ub >= 0) && (ub <= 1)) {
        var x = x1 + (ua * (x2-x1));
        var y = y1 + (ua * (y2-y1));
        return { x: x, y: y, d: d};
      }
    }
  }
  return null;
},

게임은 이제 이 데모와 함께 2 인 게임으로 즐길 수 있습니다.



Posted by 마스터킹
|

Game.Runner는 update () + draw ()에서 60fps 프레임 루프를 제공합니다. 해당 프로세스 내에서 (이 경우 퐁에서) 게임 자체, 기본 메뉴로 시작, 게임을 시작이 승자, 또는 사용자가 이를 포기 때까지 게임을 실행하는 사용자 입력을 기다립니다, 그런 다음 메뉴로 돌아가서 반복하십시오.



초기화

Pong 게임의 경우 사용자에게 다음과 같은 정보를 제공하는 '메뉴'를 표시하기 위해 일부 이미지를 사용합니다.

- 싱글 플레이어 게임의 경우 '1'을 누르십시오.


- 더블 플레이어 게임의 경우 '2'를 누르십시오.

이것은 GameRunner와 Pong 관계에 미묘한 비틀기를 도입합니다. 이미지로드는 비동기 프로세스이므로 이미지가 로드 될 때까지 이미지를 표시 할 수 없으므로 이미지가 완전히로드 될 때까지 GameRunner를 루프에 넣지 않아도됩니다.

이 문제를 해결하기 위해 Pong initialize 메서드는 콜백 패턴을 사용하여 이미지로드가 완료된 시점을 알 수 있으며 해당 프로세스가 완료 될 때까지 GameRunner에 start ()를 알리지 않습니다.

initialize: function(runner, cfg) {
  Game.loadImages(Pong.Images, function(images) {
    this.cfg         = cfg;
    this.runner      = runner;
    this.width       = runner.width;
    this.height      = runner.height;
    this.images      = images;
    this.playing     = false;
    this.scores      = [0, 0];
    this.menu        = Object.construct(Pong.Menu,   this);
    this.court       = Object.construct(Pong.Court,  this);
    this.leftPaddle  = Object.construct(Pong.Paddle, this);
    this.rightPaddle = Object.construct(Pong.Paddle, this, true);
    this.ball        = Object.construct(Pong.Ball,   this);
    this.runner.start();
  }.bind(this));
},

키보드 입력


키보드 이벤트가 발생하면 GameRunner는 게임의 onkeydown () 또는 onkeyup () 메소드 (있는 경우)를 자동으로 호출하므로 Pong 게임이 적절한 키를 감지하고 적절하게 게임을 시작하거나 중지 할 수 있습니다.

onkeydown: function(keyCode) {
  switch(keyCode) {
    case Game.KEY.ZERO: this.startDemo();            break;
    case Game.KEY.ONE:  this.startSinglePlayer();    break;
    case Game.KEY.TWO:  this.startDoublePlayer();    break;
    case Game.KEY.ESC:  this.stop(true);             break;
    case Game.KEY.Q:    this.leftPaddle.moveUp();    break;
    case Game.KEY.A:    this.leftPaddle.moveDown();  break;
    case Game.KEY.P:    this.rightPaddle.moveUp();   break;
    case Game.KEY.L:    this.rightPaddle.moveDown(); break;
  }
},
onkeyup: function(keyCode) {
  switch(keyCode) {
    case Game.KEY.Q: this.leftPaddle.stopMovingUp();    break;
    case Game.KEY.A: this.leftPaddle.stopMovingDown();  break;
    case Game.KEY.P: this.rightPaddle.stopMovingUp();   break;
    case Game.KEY.L: this.rightPaddle.stopMovingDown(); break;
  }
},

또한 사용자가 패들의 위나 아래를 움직일 수있는 입력을 감지합니다.



게임 시작하기


이제 키보드 입력을 감지 할 수 있으므로 게임을 시작할 수 있습니다.

startDemo:         function() { this.start(0); },
startSinglePlayer: function() { this.start(1); },
startDoublePlayer: function() { this.start(2); },
start: function(numPlayers) {
  if (!this.playing) {
    this.scores = [0, 0];
    this.playing = true;
    this.ball.reset();
    this.runner.hideCursor();
  }
},

경기 도중


게임이 진행되는 동안 update () 메소드는 채점 된 시점과 승자를 선언하고 게임을 중단할 시기를 감지해야합니다.

update: function(dt) {
  this.leftPaddle.update(dt, this.ball);
  this.rightPaddle.update(dt, this.ball);
  if (this.playing) {
    var dx = this.ball.dx;
    var dy = this.ball.dy;
    this.ball.update(dt, this.leftPaddle, this.rightPaddle);
    if (this.ball.left > this.width)
      this.goal(0);
    else if (this.ball.right < 0)
      this.goal(1);
  }
},
goal: function(playerNo) {
  this.scores[playerNo] += 1;
  if (this.scores[playerNo] == 1) {
    this.menu.declareWinner(playerNo);
    this.stop();
  }
  else {
    this.ball.reset(playerNo);
  }
},

게임 중지


우승자가 선언되면 게임이 중지되고 ESC 키를 치는 사용자에 대한 응답으로 게임을 중지 할 수도 있습니다.

stop: function(ask) {
  if (this.playing) {
    if (!ask || this.runner.confirm('Abandon game in progress ?')) {
      this.playing = false;
      this.runner.showCursor();
    }
  }
},

여기에서 데모를 통해 진행중인 게임 루프를 볼 수 있습니다.



Posted by 마스터킹
|

Part1에서 GameRunner를 사용하는것을 보았습니다. 이제 튀어 나오는 공을 그리는 것 외에는 아무것도 하지 않는 Pong 게임 객체를 정의 할 수 있습니다. 우리가해야 할 일은 다음을 구현하는 것입니다.

- initialize()

- update()

- draw()

Pong = {
  initialize: function(runner, cfg) {
    this.cfg    = cfg;
    this.runner = runner;
    this.width  = runner.width;
    this.height = runner.height;
    this.court  = Object.construct(Pong.Court,  this);
    this.ball   = Object.construct(Pong.Ball,   this)
    this.runner.start();
  },
  update: function(dt) {
    this.ball.update(dt);
  },
  draw: function(ctx) {
    this.court.draw(ctx);
    this.ball.draw(ctx);
  },
  Court: {
    // <snip>
  },
  Ball: {
    // <snip>
  }

Pong.Court


Pong.Court 객체는 벽을 그리는 방법을 알수 있습니다.

Court: {
  initialize: function(pong) {
    var w  = pong.width;
    var h  = pong.height;
    var ww = pong.cfg.wallWidth;
    this.walls = [];
    this.walls.push({x: 0,    y: 0,      width: w,  height: ww});
    this.walls.push({x: 0,    y: h - ww, width: w,  height: ww});
    this.walls.push({x: 0,    y: 0,      width: ww, height:  h});
    this.walls.push({x: w-ww, y: 0,      width: ww, height:  h});
  },
  draw: function(ctx) {
    ctx.fillStyle = 'white';
    for(var n = 0 ; n < this.walls.length ; n++)
      ctx.fillRect(this.walls[n].x, this.walls[n].y, this.walls[n].width, this.walls[n].height);
  }
}

Pong.Ball


볼 오브젝트는 움직이는 방법과 그려지는 방법을 알수 있습니다.

Ball: {
  initialize: function(pong) {
    this.pong    = pong;
    this.radius  = pong.cfg.ballRadius;
    this.minX    = pong.cfg.wallWidth + this.radius;
    this.minY    = pong.cfg.wallWidth + this.radius;
    this.maxX    = pong.width  - pong.cfg.wallWidth - this.radius;
    this.maxY    = pong.height - pong.cfg.wallWidth - this.radius;
    this.x       = Game.random(this.minX, this.maxX);
    this.y       = Game.random(this.minY, this.maxY);
    this.dx      = (this.maxX - this.minX) / (Game.random(1, 10) * Game.randomChoice(1, -1));
    this.dy      = (this.maxY - this.minY) / (Game.random(1, 10) * Game.randomChoice(1, -1));
  },
  update: function(dt) {
    this.x = this.x + (this.dx * dt);
    this.y = this.y + (this.dy * dt);
    if ((this.dx > 0) && (this.x > this.maxX)) {
      this.x = this.maxX;
      this.dx = -this.dx;
    }
    else if ((this.dx < 0) && (this.x < this.minX)) {
      this.x = this.minX;
      this.dx = -this.dx;
    }
    if ((this.dy > 0) && (this.y > this.maxY)) {
      this.y = this.maxY;
      this.dy = -this.dy;
    }
    else if ((this.dy < 0) && (this.y < this.minY)) {
      this.y = this.minY;
      this.dy = -this.dy;
    }
  },
  draw: function(ctx) {
    var w = h = this.radius * 2;
    ctx.fillStyle = 'white';
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, 2*Math.PI, true);
    ctx.fill();
    ctx.closePath();
  }
}

멀티 볼 데모는 여기에서 찾을 수 있습니다.

Posted by 마스터킹
|

게임 러너 루프

GameRunner는 매우 일반적입니다. 자바 스크립트 사전 요구 사항 (아래 참조)과 기본 게임 루프를 제공합니다.

- 주자 초기화

- 캔버스 front, back 버퍼 생성

- 게임 인스턴스 생성

- 60fps 루프 시작


루프 반복마다 :

- game.update () 호출 - 마지막 프레임 이후 dt 타이머 간격 제공

- game.draw () 호출 - 그리기 위한 버퍼 캔버스 컨텍스트 제공

- 플립 백 및 프론트 버퍼

- 프레임 속도 통계 업데이트

그리고 그게 전부입니다. 여기에서 데모를 찾을 수 있습니다.


Javascript 사전 요구 사항

제 3 자 라이브러리가 필요없는 프레임 워크를 만들고 싶습니다. 이 게임은 HTML <canvas>를 기반으로하고 최신 브라우저에서만 지원되기 때문에 jQuery 나 prototype.js와 같은 전체 라이브러리를 포함 할 필요는 없습니다.

그러나 우리가 필요로하는 몇 가지 전제 조건이 있습니다.

- 캔버스

- Function.bind

- Object.create

- Object.extend

- addEventListener

- DOMContentLoaded

- 이미지 로더


캔버스

우리는 HTML5 <canvas> 요소를 사용하여 게임을 제작하고 있습니다. 우리는 HTML5 <canvas> 요소가 필요합니다! 이것은 모든 최신 브라우저에서 지원되므로 아무런 문제가 없어야합니다.

IE의 이전 버전은 대개 제 3 자 라이브러리 (excanvas.js)를 필요로하지만, IE9는 기본 캔버스를 가지고 있기 때문에 이전 버전의 IE에 대한 지원을 중단하게됩니다.


Function.bind

이 메소드는 ECMAScript 5의 표준입니다. 이것은 자바 스크립트 개발에서 가장 중요한 메소드 중 하나이며, 콜백 (예 : 이벤트 핸들러)으로 사용되는 인스턴스 메소드가 올바른 세트를 갖도록하기 위해 사용됩니다. 그것도 메서드를 합성 할 때 인자를 사용합니다.

이 방법은 대부분의 최신 브라우저에서 제공되지만 구형 브라우저를 제공하기에 쉽기 때문에 샘플 구현은 mozilla에서 제공합니다

if (!Function.prototype.bind) {
  Function.prototype.bind = function(obj) {
    var slice = [].slice,
        args  = slice.call(arguments, 1),
        self  = this,
        nop   = function () {},
        bound = function () {
          return self.apply(this instanceof nop ? this : (obj || {}), args.concat(slice.call(arguments)));
        };
    nop.prototype   = self.prototype;
    bound.prototype = new nop();
    return bound;
  };
}

Object.create

또 다른 ECMAScript5 표준은 특정 프로토 타입을 사용하여 객체의 구성을 캡슐화합니다.

자바 스크립트에서 OOP 클래스 구조 패턴을 모방하기 위한 많은 시도가 있었지만, 많은 경우에 이들을 완전히 피하고 자바 스크립트의 원형을 받아들이는 것이 더 간단 할 수 있습니다.

다시 말하지만, 현대의 브라우저는이 방법을 제공 할 것이지만 필요할 때 쉽게 구현할 수 있습니다.

if (!Object.create) {
  Object.create = function(base) {
    function F() {};
    F.prototype = base;
    return new F();
  }
}

Object.create에서 빠진 것은 어떤 종류의 생성자 함수에 인수를 전달하는 기능입니다. 나는 표준 Object.create 에 이 추가 동작을 제공하는 Object.construct를 추가하려고합니다.

if (!Object.construct) {
  Object.construct = function(base) {
    var instance = Object.create(base);
    if (instance.initialize)
      instance.initialize.apply(instance, [].slice.call(arguments, 1));
    return instance;
  }
}

Object.extend


표준은 아니지만 일반적으로 jQuery 또는 프로토 타입과 같은 라이브러리에서 제공합니다. 한 개체의 모든 속성을 다른 개체의 속성으로 복사하는 기능은 매우 유용 할 수 있습니다.

if (!Object.extend) {
  Object.extend = function(destination, source) {
    for (var property in source) {
      if (source.hasOwnProperty(property))
        destination[property] = source[property];
    }
    return destination;
  };
}

addEventListener


물론 우리는 DOM 이벤트에 응답 할 수 있어야합니다. 오래된 브라우저를 지원하기위한 여러 가지 방법이 있지만 <canvas>를 사용하여 최신 브라우저 만 지원하므로 표준 addEventListener를 지원할 수 있습니다 (예 : Internet Explorer 9에서도 지원)



DOMContentLoaded


페이지를 로드 할 준비가되면 jQuery 및 prototype.js와 같은 타사 라이브러리에서도 문제가 해결되지만 최신 브라우저 만 지원하기 때문에 이러한 브라우저가 DOMContentLoaded 이벤트를 지원함을 알 수 있습니다 (예. IE9는 이것을 지원합니다)



이미지 로더


<canvas> 기반 게임의 한 가지 문제는 이미지가 로드 될 때까지 이미지를 사용할 수 없다는 것입니다. 로드가 완료 될 때까지 이미지를 사용하지 않으려면 여러 이미지를로드하고 마지막 이미지 로드가 완료되면 콜백을 수행하는 도우미 메서드가 필요합니다.

loadImages: function(sources, callback) { /* load multiple images and callback when ALL have finished loading */
  var images = {};
  var count = sources ? sources.length : 0;
  if (count == 0) {
    callback(images);
  }
  else {
    for(var n = 0 ; n < sources.length ; n++) {
      var source = sources[n];
      var image = document.createElement('img');
      images[source] = image;
      Game.addEvent(image, 'load', function() { if (--count == 0) callback(images); });
      image.src = source;
    }
  }
}


Posted by 마스터킹
|

어렸을 때, 나는 게임을하는 것을 좋아했습니다. 나이가 들어감에 따라 직접 만들어 보고 싶었습니다. 그리고 성인이 되어 게임 제작을 직업으로 하고 있습니다.

나는 내 일자리를 정말로 즐깁니다. 그러나 나는 여전히 게임을 만드는 것에 대해 생각합니다. 그러면 간단한 퐁게임을 자바스크립트로 만들어 보겠습니다.

여기서 데모게임을 할수 있습니다.

여기서 게임코드를 찾을수 있습니다.


나는 각각을 위한 데모와 함께 5 개의 섹션으로 게임개발 내용을 나누었습니다.


GameRunner


Part1에서는 GameRunner를 소개합니다. 캔버스 게임을 만드는 데 필요한 최소한의 프레임 워크로 60fps 이중 버퍼 캔버스 게임 루프를 제공하므로 update () 메서드가 호출 될 때마다 게임 상태를 업데이트하고 draw () 메서드가 호출 될 때마다 게임 자체를 렌더링하는 데 집중할 수 있습니다.


Bouncing Balls


Part 2에서는 GameRunner 프레임 워크를 사용하여 간단한 튀는 공 예제를 표시하고 GameRunner와 Pong 게임 자체의 분리를 보여줍니다.


GameLoop


Part 3에서는 GameLoop를 소개합니다. 기본 메뉴로 시작하여 사용자 입력으로 게임을 시작하거나, 승자가있을 때까지 게임을 실행하거나, 사용자가 포기할 때까지 기다린 다음 메뉴로 돌아가서 반복합니다.


충돌 감지


Part 4에서는 패들과 볼 사이의 충돌 감지 기능을 구현하여 처음으로 게임을 실행하게하여 게임 자체의 문제점을 해결했습니다. 컴퓨터 AI는 없지만 이제는 2 명이 즐길 수 있습니다.


컴퓨터 인공 지능


Part 5에서는 컴퓨터 플레이어 용 기본 AI를 구현합니다. 싱글 플레이어 게임을하거나 심지어 컴퓨터 게임을 보는 것도 허용합니다.


결론

Pong은 상당히 사소한 게임이지만 여전히 제대로 작동하려면 인프라가 약간 필요합니다. 이것은 다소 지나치게 엔지니어링 된 버전이지만 여전히 추가 될 수있는 것이 더 있습니다 :


터치 이벤트 - 모바일 브라우저의 경우 사용자 입력에 대해 다른 디자인이 필요합니다. 아마도 패들 이동에 대한 터치 이벤트를 지원할 수 있습니다.


CSS @media 쿼리 - 우리는 아마도 사용자 화면 영역을 기반으로 퐁 게임 캔버스를 확장해야합니다. (업데이트 : 이것은 마지막 순간에 추가되었으며, 브라우저 창 크기로 놀고 무슨 일이 일어나는 지 봅니다)


requestAnimationFrame - 이중 버퍼링 및 60fps로 잠긴 경우에도 애니메이션에 약간의 자국이 남아 있습니다. 가비지 콜렉션 또는 제어가 불가능한 vsync 문제 일 수도 있지만 setInterval과 같은 충실도가 낮은 타이밍 메커니즘을 사용하는 것도 효과가 없습니다. 최신 브라우저에서는 requestAnimationFrame을 구현하여 게임 루프를 통해 미세한 입자 제어가 가능하지만 아직 주요 시간대에는 준비가되어 있지 않습니다.


이 구현은 미래의 다른 간단한 게임으로 성장할 수있는 기본 프레임 워크를 제공합니다. 앞으로 몇 개월 동안 좀 더 작은 게임을 개발하고 싶습니다. 꽤 작 으면 무언가를 완성 할 가능성이 가장 높다고 가정하면 브레이크 아웃, 뱀, 테트리스, 영웅, 돌격, 돌 발치 등 오래된 고전 게임의 캔버스 버전을 만들 수 있다고 상상합니다.



Posted by 마스터킹
|

캐주얼하고 단순한 2D 게임이 때로는 백만 달러가 넘는 복잡 한 게임보다 더 인기가 있습니다. 이것은 좋은 것이 든 나쁜 것이 든, 사람들이 게임 시간을 보내는 것을 선택하는 것입니다.


HML5는 내가 좋아하기 때문에뿐만 아니라, 기존의 웹 개발 기술을 활용하여 단일 코드 기반으로 다양한 플랫폼을 타겟팅 할 수 있기 때문에 집중적으로 다룹니다.


이 튜토리얼에서는 간단한 사이드 스크롤러 모바일 / 크로스 플랫폼 게임을 처음부터 만들어 Phaser 라이브러리를 사용하여 HTML5 모바일 게임 개발의 기초를 배웁니다.


제공된 코드와 Assets는 여러분의 모바일 프로젝트에 자유롭게 사용셔도 됩니다.



소스 코드 파일

소스 코드와 게임 저작물을 zip 파일로 다운로드 하십시오. Github에서 가져올 수도 있습니다. 여기에서 완성 된 게임을 플레이 할 수 있습니다.



튜토리얼 목표

이 튜토리얼을 따르면, Phaser로 2D 모바일 HTML5 게임을 만들 수있는 기초가 있어야합니다.

1. 2 차원 사이드 스크롤러를 만들어 Phaser의 기초를 배웁니다.

2. 무료 Tiled 레벨 편집기를 사용하여 HTML5 게임용 레벨을 만드는 방법을 배웁니다.

3. 기본적인 2D 게임 메카니즘을 배우십시오.



튜토리얼 요구 사항

- JavaScript 에 대한 기초 지식부터 중간 지식이 필요하며, 코드 편집기 또는 IDE, Sublime Text, Netbeans 또는 Gean와 같은 편집기가 필요합니다.

GitHub 레포에서 Phaser를 다운로드하십시오. repo를 복제하거나 ZIP 파일을 다운로드 할 수 있습니다.

- 로컬 또는 원격 웹 서버를 사용하여 코드 및 Phaser 예제를 실행해야합니다. 몇 가지 일반적인 옵션은 Apache (Windows의 경우 WAMP, Mac의 경우 MAMP)입니다. 가벼운 대안은 몽구스 웹 서버와 파이썬의 HTTP 서버입니다. 자세한 내용은이 안내서를 참조하십시오.

- Phaser 설명서와 예제 페이지를 준비하십시오. 또한 Phaser의 소스 코드를 보면서 언제나 해답을 찾을 수 있다는 것을 잊지 마십시오.

- 우리는 Windows, Mac 및 Linux와 호환되는 무료 및 Open Source Tiledmap 편집기를 사용할 타일 기반 게임을 제작할 것입니다.


- 터치 컨트롤의 경우 HTML5 Virtual Game Controller라는 라이브러리를 사용합니다. gamecontroller.js 파일은 게임 코드에 포함되어 있지만 최신 버전을 구해서 설명서를 읽어보십시오.



새 프로젝트

새 폴더, 색인 HTML 파일 및 Phaser 파일로 시작하십시오. 다운로드 한 Phaser zip 또는 복제 된 저장소의 "build"디렉토리에서 가져올 수 있습니다.


개발 모드 (전 세계에 최종 목적지로 게임을 배포하는 생산 방식과 달리)에서는 phaser.js 파일 (phaser.min.js와 반대)을 포함하지 않는 phaser.js 파일을 포함하는 것이 좋습니다. 그 이유는 게임 엔진을 블랙 박스로 취급하는 것은 좋은 습관이 아닙니다. 파일의 내용을 탐색하고 올바르게 디버그 할 수 있기를 원합니다.


모든 답변이 Google이나 StackOverflow에있는 것은 아니므로 가장 좋은 방법은 무엇이 진행되고 있는지를 이해하기 위해 원본 소스 코드를 읽는 것입니다. Google의 index.html 파일은 다음과 같습니다. 자습서가 진행됨에 따라 여기에 표시되는 파일을 추가하고 설명합니다.

<!DOCTYPE html>

<html>

  <head>

    <meta charset="utf-8" />

    <title>Learn Game Development at ZENVA.com</title>

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <meta name="mobile-web-app-capable" content="yes">

    <meta name="apple-mobile-web-app-capable" content="yes">

    <script type="text/javascript" src="js/gamecontroller.js"></script>

    <script type="text/javascript" src="js/phaser.min.js"></script>

    <script type="text/javascript" src="js/Boot.js"></script>

    <script type="text/javascript" src="js/Preload.js"></script>

    <script type="text/javascript" src="js/Game.js"></script>

    <style>

      body {

        padding: 0px;

        margin: 0px;

      }

      </style>

  </head>

  <body> 

    <!-- include the main game file -->

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

  </body>


</html>


뷰포트 메타 태그를 사용하여 사용자가 화면을 꼬집어 재생하면 확대 / 축소를 방지합니다 (재생시 실수로 할 수 있음). 모바일 웹 앱을 사용할 수 있고 Apple 모바일 웹 앱을 추가하면 휴대 전화 사용자가 휴대 전화의 바로 가기를 webapp에 추가 할 수 있습니다. 아이콘을 추가하고 iOS 및 Android에 대한 자세한 내용을 볼 수도 있습니다).


main.js 파일이 게임을 시작합니다.



States

코드로 진행하기 전에 Phaser의 중요한 개념 인 States에 대해 이야기하고 싶습니다.


페이저에서는 모든 동작이 States에서 발생합니다. 당신은 그것들을 게임의 주요 순간으로 생각할 수 있습니다. 축구 경기를 생각하면 문이 열리고 사람들이 들어 오기 시작할 때가 있습니다. 그러면 게임 전 쇼가 개최되는 States가 있습니다. 그런 다음 경기 전 경기 자료가 현장에서 제거 된 상태입니다. 그런 다음 게임이 시작되는 States 등이 있습니다.


Phaser는 당신이 가질 수있는 States만큼 많은 유연성을 제공하지만, 많은 게임과 튜토리얼에서 사용되는 사실상의 컨벤션이 있습니다. 이름은 조금씩 다를 수 있지만 일반적으로 다음과 같습니다.


Boot States : 일반 게임 설정이 정의되고 사전로드 화면의 자산이 로드됩니다 (예 :로드 바). 사용자에게 아무 것도 표시되지 않습니다.


Preload States : 게임 Assets (이미지, 스프라이트 시트, 오디오, 텍스처 등)이 메모리에 로드됩니다 (디스크에서). PreLoad 화면은 사용자에게 보여지며 일반적으로 진행 상황을 보여주는 로드 바를 포함합니다.


MainMenu State : 게임의 시작 화면. Preload States 후에는 모든 게임 이미지가 이미 메모리에 로드되어 있으므로 빠르게 액세스 할 수 있습니다.


Game State : 게임이 실제로 작동되는 States 입니다.


투토리얼에서는 Boot, Preload 및 Game의 세 가지 States 만 사용합니다. 



게임을 시작하다


다시 코딩으로 돌아갑니다! 게임의 진입 점인 main.js를 살펴 보겠습니다.

var SideScroller = SideScroller || {};

SideScroller.game = new Phaser.Game(746, 420, Phaser.AUTO, '');

SideScroller.game.state.add('Boot', SideScroller.Boot);

SideScroller.game.state.add('Preload', SideScroller.Preload);

SideScroller.game.state.add('Game', SideScroller.Game);


SideScroller.game.state.start('Boot');


우리가 할 첫 번째 일은 우리 게임의 모든 데이터를 유지할 객체를 만드는 것입니다. 이 객체를 SideScroller라고합니다.


게임이라는 변수를 만든 다음 각 파일에서 정의 할 다른 객체에 대한 새 변수를 만드는 것이 어떻습니까?


진실은 당신이 그렇게 할 수 있다는 것입니다. 그러나 당신은 그렇게 선호합니다.하지만 모든 것을 하나의 단일 객체로 유지하는 것이 좋습니다. 이를 네임 스페이스 패턴이라고 합니다. 일반적으로 원하지 않는 것은 응용 프로그램의 전역 범위를 오염시키는 것입니다. 내가 "game"이라는 변수를 정의하는 외부 라이브러리를 포함한다고 가정 해보십시오. 전역 범위에서 작성하는 변수가 많을수록 더 많은 변수가 발생할 수 있습니다. 이로 인해 게임이 오작동을 일으킬 수 있습니다. 따라서 우리는 일반적으로 "SideScroller"와 같은 다른 곳에서 사용하기는 거의 불가능한 이름을 선택하고 그 안에 모든 것을 넣습니다.


문장 : var SideScroller = SideScroller || {}; SideScroller가 이전에 정의되어 있지 않으면 빈 객체로 만들어집니다.

SideScroller.game = new Phaser.Game(746, 420, Phaser.AUTO, ”);


우리는 화면 해상도로 Phaser 게임을 시작합니다. 게임을 렌더링하기 위해 Phaser는 CANVAS 요소를 사용하거나 WebGL (대부분의 최신 브라우저에있는 OpenGL과 웹용으로 작동하는 사양)에서 사용할 수 있습니다. 렌더링을 Phaser.AUTO로 설정하여 Phaser가 사용 가능한 것을 따라 무엇을 사용할 지 결정합니다.


States를 정의한 후에 (외부 파일로 생성 된 개체) Phaser가 Boot 상태를 시작하도록 지시합니다.



States 메서드

States는 특정 목적에 부합하는 일부 예약 된 방법을 가지고 있습니다. 이것들은 우리가 이 튜토리얼에서 사용할 것들입니다. 여기에서 전체 목록을 찾을 수 있습니다.


- init : States 초기화시 호출됩니다. States에 매개 변수를 전송해야하는 경우 여기에 액세스 할 수 있습니다 (자세한 내용은 나중에 설명합니다)


-preload : Assets이 로드되는 곳입니다.


-create : Assets 로드가 완료되면 호출됩니다.


-update : 이것은 모든 게임 틱에서 호출됩니다. 기본적으로 "초당 여러 번" 호출 되므로 충돌 감지와 같이 지속적으로 테스트해야하는 항목을 포함시키고 자합니다.



Boot State

Boot State에 대해 이야기하기 전에 이론적으로 모든 게임이 수행되는 상태를 사용할 수는 없지만 States를 사용하면 코드를 더 잘 구성 할 수 있다고 말하고 싶습니다.


Boot State는 어두운 곳입니다. 여기서 우리는 화면 크기와 우리가 사용할 물리 엔진과 같은 일반적인 게임 구성 옵션을 정의합니다 (Phaser는 그 중 세 가지가 있으며 우리는 Arcade라는 가장 간단한 것을 사용합니다). 또한 Boot State에서 Preload State로 표시 할 Assets을 로드합니다.


그래서 Boot State는 Preload State의 Assets을 로드하고 Preload State는 게임 Assets을 로드합니다. 왜 게임 Assets을 거기에서 로드하지 않는 것이 좋을까요? 아무 것도 그 일을 막지는 못하지만, 게임 Assets은 미리로드 된 화면 애셋보다 로드하는 데 시간이 오래 걸릴 가능성이 높습니다. 로딩 바 및 로고 일뿐입니다.


프리 로딩 화면 에셋을 먼저 로드하면 (즉, 가벼워 야 함) 빈 화면이있는 시간을 최소화합니다 (블루 스크린보다 좋지만 사용자는 이를 좋아하지 않습니다). 그런 다음 Preload 상태에서 눈을 바쁘게하는 멋지고 매력적인 프리로드 스크린으로 다른 모든 것을 로드 할 시간이 있습니다.


Boot.js의 내용 :

var SideScroller = SideScroller || {};

SideScroller.Boot = function(){};

//setting game configuration and loading the assets for the loading screen

SideScroller.Boot.prototype = {

  preload: function() {

    //assets we'll use in the loading screen

    this.load.image('preloadbar', 'assets/images/preloader-bar.png');

  },

  create: function() {

    //loading screen will have a white background

    this.game.stage.backgroundColor = '#fff';

    //scaling options

    this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;

    //have the game centered horizontally

    this.scale.pageAlignHorizontally = true;

    this.scale.pageAlignVertically = true;

    //screen size will be set automatically

    this.scale.setScreenSize(true);

    //physics system

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

    this.state.start('Preload');

  }


};


main.js와 같은 패턴을 따라 시작합니다. SideScroller가 이미 정의 된 경우이를 사용하고 그렇지 않으면 새 객체를 시작합니다.


SideScroller.Boot는 Boot 상태를 포함 할 객체입니다. 이것은 게임에 상태를 추가 할 때 main.js에서 전달한 것과 같은 객체입니다.

SideScroller.game.state.add('Boot', SideScroller.Boot);


preload 메소드에서 우리는 진행 상태를 위한 스프라이트를 로드하고 있습니다. 이것은 단순히 녹색 사각형입니다. 다음과 같이 이미지를 로딩하면 :

this.load.image('preloadbar', 'assets/images/preloader-bar.png');


우리는 게임의 어디에서든 이미지 자산을 참조 할 수 있습니다 (이 경우 "preloadbar").


(이미지는 HTML5 foriOS 및 Android로 만들어진 레트로 게임 Huungree RPG의 스크린 샷입니다.)


우리는 우리 게임이 수평 및 수직 가운데에 위치하기를 원합니다.

this.scale.pageAlignHorizontally = true;

this.scale.pageAlignVertically = true;

// Now, the game area we defined in main.js:


SideScroller.game = new Phaser.Game(746, 420, Phaser.AUTO, '');


게임의 픽셀을 나타냅니다. 또한 화면의 픽셀을 나타낼 수도 있지만 게임을 전체 화면에 맞추기를 원하는 대부분의 경우에 이 줄을 추가하는 이유는 다음과 같습니다.

this.scale.setScreenSize(true);

// Lastly, we initiate the physics engine and launch the Preload state:

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


this.state.start('Preload');



Preload State

Preload 상태는 모든 게임 에셋이 로드되는 곳입니다. 여기에는 게임에서 사용할 수있는 이미지, 오디오, 타일 맵 및 기타 파일이 포함됩니다.

왜 자산을 미리로드해야합니까?

웹 또는 하드 드라이브에서 파일을 로드하는 경우 시간이 오래 걸립니다. 플레이어는 총알을 쏘고 총알 스프라이트가로드 될 때까지 잠시 시간을 내어 게임이 멈추거나 표시해야하는 이미지가 표시되지 않도록합니다. 미리 로드함으로써, 모든 파일을 장치의 RAM에 로드하므로 게임에서 필요에 따라 이미지 / 리소스를 빠르게 액세스 할 수 있습니다. 처음부터 플레이어가 끔찍한 경험을 하는 것보다 기다리는 것이 가장 좋습니다!

var SideScroller = SideScroller || {};

//loading the game assets

SideScroller.Preload = function(){};

SideScroller.Preload.prototype = {

  preload: function() {

    //show loading screen

    this.preloadBar = this.add.sprite(this.game.world.centerX, this.game.world.centerY, 'preloadbar');

    this.preloadBar.anchor.setTo(0.5);

    this.preloadBar.scale.setTo(3);

    this.load.setPreloadSprite(this.preloadBar);

    //load game assets

    this.load.tilemap('level1', 'assets/tilemaps/level1.json', null, Phaser.Tilemap.TILED_JSON);

    this.load.image('gameTiles', 'assets/images/tiles_spritesheet.png');

    this.load.image('player', 'assets/images/player.png');

    this.load.image('playerDuck', 'assets/images/player_duck.png');

    this.load.image('playerDead', 'assets/images/player_dead.png');

    this.load.image('goldCoin', 'assets/images/goldCoin.png');

    this.load.audio('coin', 'assets/audio/coin.wav');

  },

  create: function() {

    this.state.start('Game');

  }


};


첫 번째 부분은 "preloadBar"라는 스프라이트의 생성입니다.이 스프라이트는 게임 영역의 중앙에 배치되며 이전에 Boot 상태로 로드 한 키 "preloadbar"가 있는 이미지를 사용합니다. 

그런 다음 이미지의 앵커 포인트를 중간으로 설정합니다. 이미지가 커지고 축소되거나 회전한다고 가정 해보십시오. 고정점이 변형이 발생한 후에 화면의 같은 부분에 남아있는 점입니다.


막대가 조금 작기 때문에 크기를 3 배로 합니다.

this.preloadBar.scale.setTo(3);


Phaser는 우리가 스프라이트를 프리로드 바 (preload bar)로 지정할 수있게 해줍니다. 기본적으로 게임 요소가 로드되면 풀 사이즈가 될 때까지 커집니다.

this.load.setPreloadSprite(this.preloadBar);


그 후에 모든 요소를로드합니다. 로드가 완료되면 게임 상태로 이동합니다.

main.js에서 SideScroller.Game에 대한 참조를 주석 처리하면 이 시점에서 로딩 막대가 보입니다.



Game State

타일 기반 게임과 Tiled 맵 편집기에 대해서도 이야기 할 필요가 있지만 Game.js에 대한 해골을 넣어 콘솔 오류없이 게임을 완벽하게 실행할 수 있게하십시오.

var SideScroller = SideScroller || {};

SideScroller.Game = function(){};

SideScroller.Game.prototype = {

  preload: function() {

      this.game.time.advancedTiming = true;

    },

  create: function() {

    //create player

    this.player = this.game.add.sprite(100, 300, 'player');

 }, 

  update: function() {

  },

  render: function()

    {

        this.game.debug.text(this.game.time.fps || '--', 20, 70, "#00ff00", "40px Courier");  

    }


};

우리는 여기에서 두 가지 작업을 수행합니다. 

첫번째 우리는 Preload에서 "player"라는 이미지를 사용하여 플레이어에 대한 스프라이트를 생성합니다.

두번째로, 우리는 이니셔티브를 통해 게임의 초당 프레임 수 (fps)를 여기에서 보여줍니다.

this.game.time.advancedTiming = true;

그런 다음 render 메서드에 디버그 텍스트를 표시합니다. Phaser는 60fps로 실행하는 것이 가장 좋습니다. 오늘날 HTML5 게임은 상당히 괜찮은 휴대 전화 및 컴퓨터에서도 이 속도로 실행되는 것이 어렵지만 점점 더 많이 개선되고 있습니다. 나는 성능에 주시하고 그 수가 떨어지는 상황 (나쁜 것)을 감지하기 위해이 숫자를 유지하기를 원합니다.

설명서에서 디버깅에 대해 자세히 읽어보십시오.

더 이상 이동하려면 Tiled에 대해 이야기해야합니다.



Get Tiled Face

Tiled는 Thorbjørn Lindeijer가 만든 정말 멋진 레벨 편집기로, 게임에 타일 기반 레벨을 시각적으로 만들 수 있습니다. 이 레벨에서 사용하는 주요 파일 형식을 TMX 및 XML이라고 합니다. Phaser에서 이러한 레벨을 사용하기 위해 이들을 JSON 파일로 내보낼 것입니다.


"타일"과 "타일 기반 게임"에 관해 이야기 할 때, 레벨이란 개별적인 작은 블록이나 조각으로 구성된 게임을 의미합니다. 이 개념에 100 % 익숙하지 않다면 Wikipedia 정의를 확인하십시오.


타일형식의 새지도를 만들려면 파일 -> 새로 만들기 :


레벨의 너비는 50 타일이고 높이는 6 타일입니다. 이 타일의 크기는 70 x 70 픽셀입니다. 오리엔테이션이 "직교"로 설정되고 레이어 포맷이 "CSV"로 설정되었는지 확인하십시오.


성과에 관한 조언 :지도가 커지고 성능이 최악입니다. 지도가 불필요하게 커지지 않도록하십시오. 또한 타일 크기가 작을수록 좋습니다. 심지어 70도 조금 큽니다. 게임 영역의 크기와 동일합니다. 알아야 할 유일한 방법은 게임을 밖으로 시도하는 것입니다. 느린 경우 게임이 잘 돌아갈 때까지 줄입니다.


다음 단계는 레이어를 만드는 것입니다. 레벨은 서로 위에있는 서로 다른 레이어를 가지게 됩니다. 레이어의 이름은 나중에 코드에서 참조해야하므로 중요합니다.


두 가지 유형의 레이어가 있습니다.

- 타일 레이어 : 타일 / 블록으로 이루어진 레이어.


- 객체 레이어 : 메타 데이터를 포함 할 수있는 벡터 객체를 만드는 레이어입니다.


레이어 아래에있는 "+" 버튼을 사용하여 레이어를 만들 수 있으며 새로 만든 레이어의 이름을 클릭하면 이름을 변경할 수 있습니다. 순서가 중요합니다. 화살표를 사용하여 순서를 재정렬 할 수 있습니다.


이 예에서는 backgroundLayer라는 배경 (플레이어와 충돌하지 않음)과 blockedLayer (바닥, 벽)라는 차단 요소를위한 두 개의 타일 레이어가 있습니다. 또한 게임 요소를 나타내는 하나의 오브젝트 레이어 (objectsLayer) (이 경우 동전)가 있지만 원수, 문, 플레이어의 시작 위치가 될 수도 있습니다.

이것은 모두 하나의 제안이며, 내가 정상적으로 그것을 하는 방법은 결코 관습이나 틀의 규칙이 아닙니다!



타일셋 로드하기

타일셋은 개별 타일 집합으로 이미지에 표시됩니다. 타일 맵에 많은 타일 세트를 사용할 수 있습니다. asset / images / tiles_spritesheet.png 중 하나만 사용합니다. 이 타일셋은 공개 도메인이므로 (자신의 상용 프로젝트에 사용할 수 있음) Kenney가 제작했습니다.


타일 세트를 로드하려면 : Map -> New Tileset

타일의 너비와 높이는 70 픽셀입니다 (가능한 경우 작게하려고하면 성능이 더 좋아집니다.). 타일 간격은 2 픽셀이므로 간격에 대해 2를 입력했습니다.


이름에 대해서는 tiles_spritesheets로 남겨 두었습니다. Phaser 내의 타일 시트를 참조 할 때이 이름을 사용하십시오.



레벨 만들기

이제 레벨을 만들 수 있습니다. 이제 당신에게 달려 있습니다! 내가 한 것은 외계인의 하늘을 표현하기 위해 분홍색 타일로 backgroundLayer를 채운 다음 (양동이 도구 사용) 차단 된 타일로 변경하고 일부 바닥과 장애물을 그렸습니다.



오브젝트 레이어

이 튜토리얼에서는 한 가지 유형의 객체만 추가 할 것입니다. 그러나 비슷한 접근 방식으로 원하는 만큼 객체를 추가 할 수 있습니다.


이 객체들은 타일셋의 스프라이트로 표현 될 것입니다. 그러나 그것은 우리의 단순성을 위한 것입니다. Phaser는 Sprite 객체를 만들 때 Sprite를 표시합니다.


쉽게 설명을 하자면, tiles_spritesheet.png 파일에 나는 동전을 넣었고 이 이미지를 사용하는 물체를 레벨에 배치 할 것입니다. 동전 이미지를 추가하는 대신 문자 "C"가있는 타일을 추가 할 수있었습니다. Phaser는 당신이 말한 것을 로드 할 것이지만 레벨이 실제로 어떻게 보이는지를 시각화 할 수 있다면 더 쉽습니다.


새 오브젝트를 삽입하려면 : objectsLayer 레이어를 선택하고 타일 삽입 버튼 (일몰 사진을 표시하는 버튼)을 클릭 한 다음 표시 할 타일을 클릭 한 다음, 지도에서 오브젝트의 위치를 클릭하십시오.



객체에 속성 추가하기

Phaser는 이러한 요소가 무엇을 나타내며 스프라이트를 사용하여 표시해야하는지 어떻게 알 수 있을까요? 이는 객체에 속성을 추가하면 알수 있습니다.


메뉴에서 "개체 선택"단추를 클릭 한 다음 개체를 두 번 클릭하면 속성을 입력 할 수있는 대화 상자가 나타납니다.

값이 "coin"인 "type"이라는 속성을 입력하십시오. "sprite"라는 또 다른 속성을 설정하고 "coin"을 입력하십시오. 이것은 Phaser에게이 오브젝트에 어떤 이미지를 사용해야하는지 알려줄 것입니다 (Preload에서 동전 이미지를로드하고 "동전"이라고 부르므로 여기에 "coin"을 입력하는 이유입니다).


게임에 따라 더 많은 속성을 입력 할 수 있습니다 (실제로 당신에게 달려 있습니다!). 예를 들어, "value"을 사용하여 다른 동전에 다른 값을 주거나 여기에 다른 "sprite"값을 지정하면 동전 오브젝트에 다른 스프라이트를 사용할 수 있습니다.


여러 요소를 만들려면 생성 한 동전을 마우스 오른쪽 버튼으로 클릭하고 "duplicate"를 클릭하면 해당 속성과 함께 개체를 빠르게 복제 할 수 있습니다. 여러 동전을 복제하여 레벨 주위로 끌 수 있습니다.


더 나아 가기 전에,이 튜토리얼 (및 다른 튜토리얼의 일부)으로 구축하고있는 게임 템플릿으로 동작 하는것이 디폴트의 Phaser 기능이 아닌 것을 분명히 하고 싶습니다. Phaser는 동일한 스프라이트 키를 사용하여 객체로부터 여러 스프라이트를 생성하는 기능 만 제공합니다. Phaser가 이 부분에서 제공하는 것을 배우려면 Tilemap 문서를보십시오.


일단 JSON 파일로 레벨을 내보내십시오. 파일 -> 다른 이름으로 내보내기 -> Json 파일, /assets/tilemaps/level1.json에 저장



타일 맵을 게임에 로드하기

이 프로세스 개요는 다음과 같습니다.

- 타일셋 PNG 파일과 레벨 JSON 파일을 미리로드하십시오 (이 시점에서 Phaser는 이들이 함께 속하는지 알지 못합니다).

- Game 구문에 타일 맵 만들기 추가하기

- 타일셋을 타일 맵에 추가합니다 (기본적으로 JSON 데이터를 실제 이미지와 병합하여 Phaser가 함께 사용한다는 것을 알 수 있습니다)

- 타일 레이어를 타일 맵에 추가합니다 (backgroundLayer 및 blockedLayer).

blockedLayer를 충돌 레이어로 만듭니다.

- 바둑판 식 기반으로 만든 레벨과 같은 크기로 게임 세계 크기를 조정하십시오.


- 레이어 objectsLayer에있는 것을 기반으로 게임 개체를 만듭니다.


타일 맵과 관련하여 프리로드 상태에서 로드 한 내용을 검토해 보겠습니다.

this.load.tilemap('level1', 'assets/tilemaps/level1.json', null, Phaser.Tilemap.TILED_JSON);


this.load.image('gameTiles', 'assets/images/tiles_spritesheet.png');


로더의 문서 페이지에서 더 자세히 살펴보십시오. 머리 속에 "레벨 1"과 "gameTiles"라는 키를 사용하십시오. 이 키는 완전히 임의적이며 나중에이 애셋을 나타 내기 위해 사용할 것입니다.


이제 플레이어 생성 전에 Game.js에 create 메소드의 다음을 추가합니다.

this.map = this.game.add.tilemap('level1');

    //the first parameter is the tileset name as specified in Tiled, the second is the key to the asset

this.map.addTilesetImage('tiles_spritesheet', 'gameTiles');

    //create layers

this.backgroundlayer = this.map.createLayer('backgroundLayer');

this.blockedLayer = this.map.createLayer('blockedLayer');

    //collision on blockedLayer

this.map.setCollisionBetween(1, 100000, true, 'blockedLayer');

    //resizes the game world to match the layer dimensions


this.backgroundlayer.resizeWorld();


처음 세 문장은 단계 설명 다음에 설명이 필요합니다. 충돌에 대한 설명 :

this.map.setCollisionBetween(1, 100000, true, 'blockedLayer');


처음 두 매개 변수는 타일 ID의 범위를 지정합니다. 타일을 사용하여 지도를 저장하면 각 타일마다 고유 한 ID가 지정됩니다. level1.json을 열면이 숫자가 표시됩니다. setCollisionBetween을 사용하면 범위 내의 모든 ID에 대해 충돌을 지정할 수 있습니다. 1에서 100000과 같이 큰 숫자를 지정하면 blockedLayer의 모든 타일에 충돌이 발생합니다.


우리는 게임 세계를 배경 레이어의 크기로 조정하고 있습니다. 동일한 크기이므로 차단 된 레이어 일 수도 있습니다. 중요하지 않습니다.


이제는 목록의 8 단계 (객체 계층에서 객체 생성)를 무시합니다.


이것을 실행하면 브라우저에 두 레이어가 모두 표시됩니다. 이제 플레이어에게 생명을 불어 넣을 시간입니다!



플레이어 추가하기

우리 플레이어는 이미 화면에 있지만 많이하지는 않습니다! 우리는 중력을 줌으로써 바보처럼 주위를 떠 다니는 것이 아닙니다.


이를 위해 우리는 물리엔진을 가능하게 해야합니다. 일단 물리엔진이 가능 해지면, 플레이어 스프라이트에는 body라는 속성이 생기고, 모든 물리 속성과 동작을 갖게됩니다.

//create player

this.player = this.game.add.sprite(100, 300, 'player');

//enable physics on the player

this.game.physics.arcade.enable(this.player);

//player gravity

this.player.body.gravity.y = 1000;

//the camera will follow the player in the world


this.game.camera.follow(this.player);

우리는 플레이어에게 육체뿐 아니라 중력도 주었습니다! 이제 플레이어는 땅에 떨어져야하고, 카메라는 플레이어를 따라 레벨을 따라 가야합니다. 플레이어가 지나가는 대신에 땅에 닿도록 해 봅시다.



충돌 감지

충돌 감지는 두 게임 요소가 서로 교차할때 이를 감지하여 이에 따라 조치를 취할 수 있음을 의미합니다.

Phaser는 충돌과 중첩의 두 가지 옵션을 제공합니다. 두 객체 사이에 충돌 메소드를 지정하면 물리적으로 충돌에 도달합니다. 예를 들어 속도가 멈출 것입니다. 반면에 겹치는 동작을 지정하면 서로 영향을주지 않습니다. 마치 홀로그램을 건드린 것과 같습니다.

우리 게임에서는 플레이어와 바닥 사이에 충돌이 발생하기를 원하며, 플레이어와 동전 사이에 겹치는 동작을 원합니다 (그렇지 않으면 동전을 만질 때처럼 벽에 충돌하는 것처럼 될것입니다).

update 메소드는 모든 게임 틱 ( "초당 여러 번")에서 트리거되기 때문에 충돌 감지를 처리 ​​할 장소입니다.

성능이 너무 낮으면 게임에서 충돌 감지가 제 시간에 일어나지 않고 벽과 바닥을 통과 할 가능성이 있습니다. 그러므로 fps를 주시하십시오 !!

update ()에 다음을 추가 할 수 있습니다.

//collision


this.game.physics.arcade.collide(this.player, this.blockedLayer, this.playerHit, null, this);

게임 상태에 playerHit 메서드를 추가합니다. 이 메소드는 플레이어가 blockedLayer와 충돌 할 때 실행됩니다. 지금은 비어있을 것입니다.

playerHit: function(player, blockedLayer) {}

나중에 코드를 추가하면 됩니다.

this.game.physics.arcade.collide(this.player, this.blockedLayer)

우리 플레이어는 이제 타격을 입어야합니다.



기본 사이드 스크롤링

이제 x에서 속도를 주어 오른쪽으로 이동하고 UP 키를 사용하여 점프하는 기능을 추가합니다.

Phaser 및 내가 알고있는 모든 HTML5 게임 프레임 워크에서 X 축은 오른쪽으로 증가하고 왼쪽으로 감소합니다. Y 축은 내려갈 때 증가하고 올라갈 때 감소합니다. 좌표계의 원점은 왼쪽 상단 구석에 있습니다.

create 메소드가 끝나면 커서 키 (화살표 키)를 시작합니다.

[javscript] // 커서 키로 플레이어 이동

this.cursors = this.game.input.keyboard.createCursorKeys (); [/ javascript]


업데이트 메소드에서 다음을 추가하십시오.

this.player.body.velocity.x = 300; 

if(this.cursors.up.isDown) {

    this.playerJump();


}

우리가 하는 첫 번째 작업은 플레이어에게 x의 속도를 300으로 지정하는 것입니다. 플레이어의 물리 엔진을 활성화 했기 때문에 이 작업을 수행 할 수 있습니다.

위쪽 화살표를 누르면 playerJump라는 메서드가 실행됩니다. 게임 상태에서이 메서드를 추가해 보겠습니다.

playerJump: function() {

    if(this.player.body.blocked.down) {

      this.player.body.velocity.y -= 700;

    }   


  },

플레이어가 바닥에 닿아 있는지 여부를 확인하고 있습니다. 그렇다면 우리는 뛰어 넘을 수 있습니다. 점프는 Y에 속도를 설정하는 것으로 구성됩니다 (올라갈 때 음수).

이제 캐릭터를 레벨로 이동시키고 장애물을 뛰어 넘을 수 있어야합니다. 우리는 여전히 오리를 먹을 수는 없지만 다음에 그것을 추가 할 것입니다.



Ducking

점핑 외에도 우리는 장애물을 피할 수 있기를 원합니다. 우리는 점프와 비슷한 접근법을 따를 것입니다. 유일한 차이점은 플레이어가 이미지 "player_duck.png"( "Preload"의 key "playerDuck")로 향할 때 스프라이트 이미지를 변경하려는 것입니다.

create 메서드에서 플레이어의 중력을 설정 한 후

//properties when the player is ducked and standing, so we can use in update()

var playerDuckImg = this.game.cache.getImage('playerDuck');

this.player.duckedDimensions = {width: playerDuckImg.width, height: playerDuckImg.height};

this.player.standDimensions = {width: this.player.width, height: this.player.height};


this.player.anchor.setTo(0.5, 1);

각 코드 행을 살펴보기 전에 우리가하는 일을 설명하겠습니다. 플레이어가 오리를 잡을 때 이미지를 "playerDuck"로 변경하고 싶습니다. 이미지를 변경하는 것도 한 가지이지만, 우리는 또한 물리엔진 객체를 나타내는 플레이어의 바디를 업데이트 해야합니다. 이미지를 더 작은 이미지로 변경하더라도 명시 적으로 말하지 않으면 본문이 변경되지 않습니다.

더욱이, 우리가 오리를 나타낸 후에는 플레이어가 다시 일어나기를 원하기 때문에 플레이어의 몸을 한 번 더 업데이트해야합니다.

첫 번째 줄은 플레이어의 이미지를 가져 와서 크기를 읽을 수 있도록하는 것입니다.

var playerDuckImg = this.game.cache.getImage('playerDuck');

그런 다음 이 이미지의 크기와 서있는 플레이어의 크기를 저장하여 나중에 다시 사용할 수 있습니다.

this.player.duckedDimensions = {width: playerDuckImg.width, height: playerDuckImg.height};


this.player.standDimensions = {width: this.player.width, height: this.player.height};

플레이어가 수축 할 때 발이 바닥에 있어야합니다 (오리를 잡을 때 시도하는 방식입니다). 따라서 앵커 포인트를 스프라이트 맨 아래에 설정해야합니다.

this.player.anchor.setTo(0.5, 1);

이제 update 함수에서 DOWN 화살표를 읽고 플레이어가 오리가 되게합니다. 또한 플레이어가 더킹 된 상태에서 더 이상 DOWN 키를 누르고 있지 않은 경우, 그 사람이 다시 일어 서기를 바랍니다.

this.player.body.velocity.x = 300; 

if(this.cursors.up.isDown) {

this.playerJump();

}

else if(this.cursors.down.isDown) {

this.playerDuck();

}

if(!this.cursors.down.isDown && this.player.isDucked) {

//change image and update the body size for the physics engine

this.player.loadTexture('player');

this.player.body.setSize(this.player.standDimensions.width, this.player.standDimensions.height);

this.player.isDucked = false;


}

그리고 playerDuck 메소드 :

playerDuck: function() {

//change image and update the body size for the physics engine

this.player.loadTexture('playerDuck');

this.player.body.setSize(this.player.duckedDimensions.width, this.player.duckedDimensions.height);

//we use this to keep track whether it's ducked or not

this.player.isDucked = true;


},

이제 이 방법으로 물체를 피할 수 있습니다!



게임 오버

이 게임에서는 절벽에서 떨어지거나 벽 / 장애물을 피하는 대신 패를 당하면 잃게됩니다. 낙하로 인한 사망을 구현하기 위해 업데이트 방법에 새로운 체크를 추가 할 것입니다. 플레이어의 위치가 게임 세계의 가장자리보다 크거나 같으면 게임을 다시 시작하는 gameOver () 메소드를 실행합니다.

//restart the game if reaching the edge

if(this.player.x >= this.game.world.width) {

    this.game.state.start('Game');


}

플레이어가 죽을 수있는 다른 방법은 장애물을 부딪치는 것입니다. 기본적으로 플레이어의 오른쪽에 차단 된 레이어 사이에 충돌이있을 때. 이는 플레이어가 오른쪽으로 이동하기 때문입니다. 이전에 생성 한 playerHit 메소드에서 이 로직을 구현합니다. 다음과 같습니다.

playerHit: function(player, blockedLayer) {

    //if hits on the right side, die

    if(player.body.blocked.right) {

      //set to dead (this doesn't affect rendering)

      this.player.alive = false;

      //stop moving to the right

      this.player.body.velocity.x = 0;

      //change sprite image

      this.player.loadTexture('playerDead');

      //go to gameover after a few miliseconds

      this.game.time.events.add(1500, this.gameOver, this);

    }


  },

우리가 "alive" 속성을 어떻게 확인하는지보십시오. 이 속성은 페이저의 모든 스프라이트에 존재하지만 렌더링에는 영향을 미치지 않지만 게임 요소를 라이브 또는 죽게하는 것이 대부분의 게임에서 공통적이기 때문에 편의상 배치되었습니다.


플레이어가 오른쪽 (player.body.blocked.right)에 도달하면 움직임을 멈추고 플레이어를 "not alive"로 설정하고 이미지를 preloaded에 로드 한 playerDead 이미지로 변경 한 후 게임을 다시 시작합니다. 1.5 초 (바닥에 닿을 시간을 줄 수 있습니다!).

하지만, 만약 플레이어가 죽었다 할지라도 키를 사용한다면 우리는 여전히 뛰어 넘을 수 있습니다. 플레이어가 점프 또는 오리를 만들기 전에 플레이어가 살아 있는지 확인해야합니다. 업데이트를 수정하면 다음과 같이 보입니다.

update: function() {

    //collision

    this.game.physics.arcade.collide(this.player, this.blockedLayer, this.playerHit, null, this);

    this.game.physics.arcade.overlap(this.player, this.coins, this.collect, null, this);

    //only respond to keys and keep the speed if the player is alive

    if(this.player.alive) {

      this.player.body.velocity.x = 300; 

      if(this.cursors.up.isDown) {

        this.playerJump();

      }

      else if(this.cursors.down.isDown) {

        this.playerDuck();

      }

      if(!this.cursors.down.isDown && this.player.isDucked) {

        //change image and update the body size for the physics engine

        this.player.loadTexture('player');

        this.player.body.setSize(this.player.standDimensions.width, this.player.standDimensions.height);

        this.player.isDucked = false;

      }

      //restart the game if reaching the edge

      if(this.player.x >= this.game.world.width) {

        this.game.state.start('Game');

      }

    }


  },



타일형식으로 배열 된 레이어에서 객체로드

지금까지 타일링 된 맵에서 타일 레이어 만 로드했습니다. 우리도 객체 레이어를 만들었던 것을 잊지 마십시오. 이러한 객체를 로드하려면 Tilemap 클래스와 함께 제공되는 메서드를 사용할 수 있지만 그렇게 하려면 원하는 각 객체 유형에 대해 객체 레이어를 만들어야합니다 (각 스프라이트 이미지에 대해서도 마찬가지 임) .

내가 선호하는 또 다른 옵션은 게임의 모든 개체에 대해 단일 개체 계층을 사용하고 각 개체의 개체 유형 및 사용할 스프라이트 이미지를 지정하는 것입니다. 우리가 그렇게 할 수 있도록 게임 상태에 추가 할 두 가지 도우미 메서드를 만들었습니다.

첫 번째 메소드 인 findObjectsByType을 사용하면 특정 유형과 일치하는 객체 레이어의 모든 객체가 있는 배열을 얻을 수 있습니다 (객체 계층을 타일로 만들 때 유형을 어떻게 지정했는지 기억하십시오).

//find objects in a Tiled layer that containt a property called "type" equal to a certain value

  findObjectsByType: function(type, map, layerName) {

    var result = new Array();

    map.objects[layerName].forEach(function(element){

      if(element.properties.type === type) {

        //Phaser uses top left, Tiled bottom left so we have to adjust

        //also keep in mind that some images could be of different size as the tile size

        //so they might not be placed in the exact position as in Tiled

        element.y -= map.tileHeight;

        result.push(element);

      }     

    });

    return result;


  },

두 번째 방법 인 createFromTiledObject는 객체에서 스프라이트를 만드는 것입니다. 예를 들어 첫 번째 방법을 사용하여 모든 "coin"을 가져온 다음 두 번째 방법을 사용하여 실제로 스프라이트를 만듭니다.

//create a sprite from an object

createFromTiledObject: function(element, group) {

    var sprite = group.create(element.x, element.y, element.properties.sprite);

//copy all properties to the sprite

Object.keys(element.properties).forEach(function(key){

sprite[key] = element.properties[key];

});


  },



Loading Coin

두 가지 도우미 메서드를 사용하여 게임에서 모든 동전을 만듭니다. create 메소드에서 world resizing 문 다음에 다음을 추가하십시오.

this.createCoins();

Let's implement the createCoins method:

//create coins

  createCoins: function() {

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

    this.coins.enableBody = true;

    var result = this.findObjectsByType('coin', this.map, 'objectsLayer');

    result.forEach(function(element){

      this.createFromTiledObject(element, this.coins);

    }, this);


  },


여기에 무슨 일이 일어날까요? :

우리는 동전이라는 새로운 스프라이트 그룹을 만듭니다. Phaser에서는 그룹을 사용하여 동일하거나 유사한 속성 및 동작을 가진 요소 집합과 쉽게 작업 할 수 있습니다. 예를 들어, 적, 수집품, 아이템 등에 대한 그룹을 만들 수 있습니다.

우리는 전체 그룹을 대상으로 물리 시스템을 활성화합니다.

우리는 objectsLayer에서 모든 동전 오브젝트를 찾습니다.


우리가 가진 것은 일련의 사물들이었습니다. 이제 우리는 이 객체들 각각에 대해 실제 스프라이트를 만들고 싶습니다.

우리는 플레이어가 동전을 수집하기를 원합니다. 플레이어가 동전에 닿으면 플레이어의 속도가 동전의 영향을 받지 않아야 하므로 충돌 대신 겹침 유형을 사용합니다. 업데이트 방법 :

this.game.physics.arcade.overlap(this.player, this.coins, this.collect, null, this);

다음에 collect 메소드를 구현해 보겠습니다.



소리


Phaser에서 사운드를 재생하는 것은 매우 간단하며 다른 애셋을 로드 / 사용하는 것과 동일한 방식을 따릅니다. Preloader에서 먼저 로드해야합니다.

this.load.audio('coin', 'assets/audio/coin.wav');

그런 다음 게임 상태의 create 메소드에서 오디오 객체를 만듭니다.

//sounds


this.coinSound = this.game.add.audio('coin');

사운드를 재생하려면 사운드 객체의 play () 메서드를 사용하면 됩니다. 우리는 collect 메서드에서 동전 사운드를 재생합니다 (사용자가 동전을 쥐었을 때).

collect: function(player, collectable) {

    //play audio

    this.coinSound.play();

    //remove sprite

    collectable.destroy();


  },

이상적으로 동전을 추적하고 총계를 화면에 표시하고 싶지만,이 예에서는 그 부분을 생략합니다. 



사운드 관련 추가 노트

Phaser는 사용 가능한 경우 WebAudio API를 사용하고, 그렇지 않으면 AUDIO 태그를 사용하여 사운드를 나타냅니다. 이것은 자동으로 처리됩니다.

서로 다른 브라우저는 오디오 형식에 대한 지원이 다릅니다. Phaser에서는 동일한 오디오에 대해 여러 개의 오디오 파일을 전달할 수 있으므로 엔진이 브라우저에 맞는 형식으로 재생됩니다. 


가능한 한 오디오 파일을 압축하십시오. 무료 사운드 유틸리티 인 Audacity를 사용하면됩니다. 나는 일반적으로 프로젝트 속도 (HZ)를 가능한 한 많이 내리고 품질은 좋습니다. 그런 다음 파일을 OGG 및 MP3로 내 보냅니다.



터치 컨트롤러

이 섹션에서는 HTML5 Virtual Game Controller라는 외부 라이브러리를 사용하여 터치 컨트롤러를 추가 할 것입니다. 페이지를 체크 아웃하여 다양한 컨트롤러 유형에 대한 스크린 샷과 코드 예제를 확인하십시오.

우리 게임에서 우리는 단지 두 개의 버튼을 원할뿐입니다. 하나는 점핑을 위한 것이고 다른 하나는 더킹을 위한 것입니다. 이 게임을 게시하려면 이 작업을 위해 화면을 스와이프 하거나 터치하는 것을 선호하지만 이 튜토리얼에서는 터치 컨트롤러 사용법을 포함하기를 원했습니다.

우리가 한 첫번째 일은 우리의 인덱스 페이지에 gamecontroller.js (또는 gamecontroller.min.js)를 포함 시켰습니다. GameController라는 전역 개체가 만들어집니다.

이 컨트롤러는 CANVAS에 그려집니다. 우리는 상태를 다시 시작할 때마다 다르게 그려지기를 원합니다. 다시 그려집니다. 게임 상태의 create 메소드에서 add :

//init game controller

this.initGameController();

Lets implement this method:

initGameController: function() {

    if(!GameController.hasInitiated) {

      var that = this;     

      GameController.init({

          left: {

              type: 'none',

          },

          right: {

              type: 'buttons',

              buttons: [

                false,

                {

                  label: 'J',

                  touchStart: function() {

                    if(!that.player.alive) {

                      return;

                    }

                    that.playerJump();

                  }

                },

                false,

                {

                  label: 'D',

                  touchStart: function() {

                    if(!that.player.alive) {

                      return;

                    }

                    that.pressingDown = true; that.playerDuck();

                  },

                  touchEnd: function(){

                    that.pressingDown = false;

                  }

                }

              ]

          },

      });

      GameController.hasInitiated = true;

    }


  },

GameController.hasInitiated는 컨트롤러가 시작되었는지 여부를 추적하여 두 번하지 않도록 추적하는 데 사용할 변수입니다.

우리가 키보드로 했던 것과 비슷한 방식으로 player.alive를 확인하고 어떻게 작동하는지 보십시오.

업데이트에서 변경하고자 하는 한 가지 사항은 다운 키가 눌러지지 않았지만 오리 버튼이 눌려지고있는 경우 플레이어가 일어 서기를 원하지 않기 때문에 다음을 대체하십시오.

//instead of: if(!this.cursors.down.isDown && this.player.isDucked) {


if(!this.cursors.down.isDown && this.player.isDucked && !this.pressingDown) {

이젠 모바일에서도 키보드를 사용하지 않고도 사용할수 잇습니다.


이 튜토리얼의 게임 소스는 링크로 접속하여 다운 받으시면됩니다.

Posted by 마스터킹
|

Phaser는 tweens와 파티클 덕택에 타이틀 스크린에 생명을 불어 넣을 수있게 해줍니다. 그리고 몇 줄의 코드로 우리는 약 1 분 안에 간단하고 심플한 타이틀 화면을 다이나믹한 화면으로 만들수 있습니다.


왼쪽에는 정적인 타이틀 화면이며, 오른쪽은 트윈과 파티클을 이용한 다이나믹한 화면입니다.


 


어느 것이 더 전문적인 것 같습니까? 게임 타이틀을 왼쪽에서 오른쪽으로 바꾸는데 걸리는 시간은 오래 걸리지 않습니다.


다음 코드는 왼쪽 화면을 생성하는 코드 입니다.

var GlobezGame = GlobezGame || {};

GlobezGame.GameTitle = function(){

startGame = false;

};

GlobezGame.GameTitle.prototype = {

   create: function(){

   console.log("%cStarting game title", "color:white; background:red");

this.add.image(0,0,"background");

          //

var gameTitleSeaLife = this.add.image(160,70,"gametitle_sealife");

          gameTitleSeaLife.anchor.setTo(0.5,0.5);

//

var gameTitleVs = this.add.image(190,120,"gametitle_vs");

          gameTitleVs.anchor.setTo(0.5,0.5);

//

var gameTitleMines = this.add.image(160,160,"gametitle_mines");

          gameTitleMines.anchor.setTo(0.5,0.5);

//

   var playButton = this.add.button(160,320,"playbutton",this.playTheGame,this)

playButton.anchor.setTo(0.5,0.5);

},

playTheGame: function(){

if(!startGame){

startGame = true

alert("Start the game!!");

}

}


}


다음 코드는 오른쪽 화면을 생성하는 코드 입니다.

var GlobezGame = GlobezGame || {};

GlobezGame.GameTitle = function(){

startGame = false;

};

GlobezGame.GameTitle.prototype = {

   create: function(){

   console.log("%cStarting game title", "color:white; background:red");

this.add.image(0,0,"background");

          //

          var bubblesEmitter = this.add.emitter(160, 500, 50);

          bubblesEmitter.makeParticles("bubble");

          bubblesEmitter.maxParticleScale = 0.6;

          bubblesEmitter.minParticleScale = 0.2;

          bubblesEmitter.setYSpeed(-30, -40);

          bubblesEmitter.setXSpeed(-3, 3);

          bubblesEmitter.gravity = 0;

          bubblesEmitter.width = 320;

          bubblesEmitter.minRotation = 0;

          bubblesEmitter.maxRotation = 40;

          bubblesEmitter.flow(15000, 2000)

//

var gameTitleSeaLife = this.add.image(160,70,"gametitle_sealife");

          gameTitleSeaLife.anchor.setTo(0.5,0.5);

          gameTitleSeaLife.angle = (2+Math.random()*5)*(Math.random()>0.5?1:-1);

          var seaLifeTween = this.add.tween(gameTitleSeaLife);

seaLifeTween.to({

angle: -gameTitleSeaLife.angle

},5000+Math.random()*5000,Phaser.Easing.Linear.None,true,0,1000,true);

//

var gameTitleVs = this.add.image(190,120,"gametitle_vs");

          gameTitleVs.anchor.setTo(0.5,0.5);

          gameTitleVs.angle = (2+Math.random()*5)*(Math.random()>0.5?1:-1);

          var vsTween = this.add.tween(gameTitleVs);

vsTween.to({

angle: -gameTitleVs.angle

},5000+Math.random()*5000,Phaser.Easing.Linear.None,true,0,1000,true);

//

var gameTitleMines = this.add.image(160,160,"gametitle_mines");

          gameTitleMines.anchor.setTo(0.5,0.5);

          gameTitleMines.angle = (2+Math.random()*5)*(Math.random()>0.5?1:-1);

          var minesTween = this.add.tween(gameTitleMines);

minesTween.to({

angle: -gameTitleMines.angle

},5000+Math.random()*5000,Phaser.Easing.Linear.None,true,0,1000,true);

//

   var playButton = this.add.button(160,320,"playbutton",this.playTheGame,this)

playButton.anchor.setTo(0.5,0.5);

          playButton.angle = (2+Math.random()*5)*(Math.random()>0.5?1:-1);

          var playTween = this.add.tween(playButton);

playTween.to({

angle: -playButton.angle

},5000+Math.random()*5000,Phaser.Easing.Linear.None,true,0,1000,true);

          //

          var blackFade = this.add.sprite(0,0,"blackfade");

          var fadeTween = this.add.tween(blackFade);

fadeTween.to({

alpha:0

},2000,Phaser.Easing.Cubic.Out,true);

},

playTheGame: function(){

if(!startGame){

startGame = true

alert("Start the game!!");

}

}


}


오른쪽 화면 생성 코드에서 볼 수 있듯이 입자 방아쇠와 일부 컷 / 과거 트윈을 추가 했으므로 코드를 원하는대로 변경할 수 있습니다. 일부 좌표 만 변경하면됩니다.


그 결과가 더 좋고, 노력이 최소한이며, 정적 타이틀 화면이 더 이상 보이지 않기를 바랍니다. 두 프로젝트의 소스 코드를 다운로드 할 수 있습니다.



Posted by 마스터킹
|