HTML5GAME :: 'PROGRAMMING/Phaser Js' 카테고리의 글 목록 (2 Page)

달력

52024  이전 다음

  • 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
  • 31

이것은 JavaScript 및 Phaser로 Slither.io를 만드는 튜토리얼 시리즈의 네 번째 파트입니다! 


예제를 살펴보고 이 부분의 소스 코드를 살펴보십시오.


충돌 개념

이 부분에서는 snake.js를 수정합니다. 본질적으로, 우리가 하고 싶은 것은 다른 뱀의 섹션과 충돌 할 수 있는 뱀의 전면에 포인트를 추가하는 것입니다. 다음과 같이 보입니다.

각 뱀의 앞쪽에서 원형 물리 바디 "포인트"를 볼 수 있습니다. 게임에서 이 피직스 바디를 보려면 뱀의 디버그 속성을 true로 설정하십시오.


Part 2의 뱀 섹션에 이미 물리 바디를 추가 했으므로 뱀 앞에 서클 본문을 만드는 것이 필요합니다. 이 원을 뱀의 "가장자리"라고 할 것 이므로 뱀의 가장자리가 섹션 바디와 충돌 할 때 관심이 있습니다. 가장자리를 머리 앞쪽에 유지하려면 잠금 제한을 사용하여 뱀이 회전하거나 움직일 수 있도록하고 이 가장자리는 앞으로 유지됩니다.


가장자리 만들기

먼저 snake.js 소스의 Snake 함수에 다음 줄을 추가했습니다.


// 가장자리는 다른 뱀과 충돌 할 수있는 앞쪽 몸체입니다.

// 이 뱀의 머리에 고정되어있다.

this.edgeOffset = 4;

this.edge = this.game.add.sprite (x, y - this.edgeOffset, this.spriteKey);

this.edge.name = "edge";

this.edge.alpha = 0;

this.game.physics.p2.enable (this.edge, this.debug);

this.edge.body.setCircle (this.edgeOffset);


// 가장자리를 머리 앞쪽으로 제한합니다.

this.edgeLock = this.game.physics.p2.createLockConstraint (

     this.edge.body, this.head.body, [0, -this.head.width * 0.5-this.edgeOffset]

);


this.edge.body.onBeginContact.add (this.edgeContact, this);


우리는 스프라이트를 보이지 않게 만들고, 그런 다음 가장자리에 원형 물리바디를 줍니다. 이 바디를 특정 오프셋에서 머리 앞에 고정시키는 잠금 구속 조건을 만듭니다. 이 가장자리가 다른 본문과 접촉하기 시작할 때 콜백을 추가합니다.


에지 연결

가장자리가 무언가와 충돌 할 때 우리가 하는 일을 살펴 봅시다.


edgeContact : function (phaserBody) {

     // 가장자리가 다른 뱀의 섹션을 치는 경우이 뱀을 파괴합니다.

     if (phaserBody && this.sections.indexOf (phaserBody.sprite) == -1) {

         this.destroy ();

     }

     // 가장자리가이 뱀 자신의 섹션에 닿는 경우 글리치를 피하는 간단한 해결책은

     // 가장자리를 중심으로 이동하는 것입니다.

     // 그러면 잠금 제한으로 인해 맨 앞으로 이동합니다

     else if (phaserBody) {

         this.edge.body.x = this.head.body.x;

         this.edge.body.y = this.head.body.y;

     }

}


우리는 가장자리가 다른 뱀의 단면 또는이 뱀의 단면을 공격했는지 알아 낼수 있습니다. 그것이 다른 뱀을 치면 우리는 파괴 방법을 사용하여 이 뱀을 파괴합니다. 이 뱀을 때렸다면, 우리는 머리를 단순히 재설정하기 위해 머리의 중심으로 이동시킵니다. 이 솔루션은 충돌 그룹을 사용하여 이 뱀의 가장자리가 자신의 섹션을 치는 것을 방지하기 위한 대안입니다. 우리가 사용하는 솔루션은 간단하지만 구현하기가 훨씬 쉽습니다.


파괴

뱀이 파괴되면, 우리는 또한 가장자리를 파괴해야합니다. destroy 메소드에 이 코드를 추가했습니다.


this.game.physics.p2.removeConstraint (this.edgeLock);

this.edge.destroy ();


그들이 제한하는 스프라이트 바디와 함께 제약 조건을 파괴하는 것이 항상 중요합니다.


스케일링

뱀의 머리가 커지면 그 엣지는 더욱 더 제약을 받아야 합니다. 이 코드를 setScale 메서드에 추가합니다.


// p2 physics로 엣지 잠금 위치 업데이트

this.edgeLock.localOffsetB = [

     0, this.game.physics.p2.pxmi (this.head.width * 0.5 + this.edgeOffset)

];


순수한 P2 물리를 사용하여 잠금 구속 조건을 업데이트하고 새로운 헤드 스케일을 기반으로 헤드에서 추가 오프셋에 에지를 배치합니다. P2 잠금 제한 등록 정보는 여기에서 읽을 수 있습니다.


그리고 그것은 게임에서 뱀 충돌을 구현하는 데 필요한 모든 것입니다. 우리의 작업은 대부분 물리학 자들로 철저한 스네이크 수업을 만들고 방법을 파괴함으로써 이미 완료되었습니다. 제 5 부에서는 우리가 뱀에게 눈을 보충하기 위해 노력할 것입니다.





Posted by 마스터킹
|

이것은 JavaScript 및 Phaser로 Slither.io를 만드는 튜토리얼 시리즈의 세 번째 파트입니다! Part 1을 처음 보시거나 Part 2로 돌아 가시면됩니다.


예제를 살펴보고 이 부분의 소스 코드를 살펴보십시오.


스네이크 확장하기

제 2 부에서 우리의 뱀은 앞으로 나아갈 수 있습니다. 이제 우리는 뱀 머리를 왼쪽과 오른쪽으로 돌리는 작업을해야합니다. Snake 클래스에서 이 기능을 추가하는 대신, Snake를 확장 할 BotSnake 및 PlayerSnake에 추가 할 것입니다. 왜 우리는 이것을 갈라 놓아야 합니까? 플레이어의 뱀은 커서 위치 또는 화살표 키로 제어되기 때문에 봇은 단순히 임의로 회전합니다. 이러한 차별화는 코드에 들어갈 때 명확 해집니다.



BOT SNAKE

botSnake.js의 BotSnake 클래스를 먼저 살펴 보겠습니다. 우리는 함수를 생성 한 다음 뱀을 상속받습니다.


BotSnake = function (게임, spriteKey, x, y) {

     Snake.call (this, game, spriteKey, x, y);

     this.trend = 1;

}

BotSnake.prototype = Object.create (Snake.prototype);

BotSnake.prototype.constructor = BotSnake;


함수 호출 메서드를 사용하여 Snake를 상속받습니다. 우리는 BotSnake에 고유 한 trend라는 필드를 추가합니다. 이 상속에는 Snake 프로토 타입이 포함되어 있지 않으므로 Prototype을 Object.create ()로 복제하고 BotSnake 프로토 타입과 동일한 값으로 설정해야합니다.


이제 BotSnake는 Snake와 똑같은 기능을하므로 기능을 확장해야 합니다. 특히 update 메서드에 추가하려고합니다. 우리가 어떻게 하는지 보시죠:


BotSnake.prototype.tempUpdate = BotSnake.prototype.update;

BotSnake.prototype.update = function () {

     this.head.body.setZeroRotation ();


     // 로봇이 한 방향으로 계속 회전하도록합니다.

     // 길 찾기 전환 전의 상당한 시간

     if (Util.randomInt (1,20) == 1) {

         this.trend * = -1;

     }

     this.head.body.rotateRight (this.trend * this.rotationSpeed);

     this.tempUpdate ();

}


먼저 tempUpdate라는 원본 업데이트 메서드의 복사본을 만듭니다. 우리는 실제 업데이트 방법에 추가 할 수 있도록이 작업을 수행 한 다음 끝에 tempUpdate를 호출합니다. Part 2에서 보았 듯이, Snake update 메소드는 우리가 잃고 싶지 않은 중요한 기능을 가지고 있습니다.


새로운 업데이트 방법에서는 뱀의 머리를 왼쪽이나 오른쪽으로 돌린 다음 특정 시간 후에 회전 방향을 전환하는 것입니다.


Player Snake

이제 playerSnake.js에서 PlayerSnake를 살펴 보겠습니다.


PlayerSnake = function (game, spriteKey, x, y) {

     Snake.call (this, game, spriteKey, x, y);

     this.cursors = game.input.keyboard.createCursorKeys();


     // 플레이어의 뱀이 속도를 낼 수 있도록 스페이스 키를 처리합니다.

     var spaceKey = this.game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);

     var self = this;

     spaceKey.onDown.add(this.spaceKeyDown, this);

     spaceKey.onUp.add(this.spaceKeyUp, this);

     this.addDestroyedCallback(function () {

         spaceKey.onDown.remove(this.spaceKeyDown, this);

         spaceKey.onUp.remove(this.spaceKeyUp, this);

     }, this);

}


PlayerSnake.prototype = Object.create (Snake.prototype);

PlayerSnake.prototype.constructor = PlayerSnake;


// 스페이스 키가 눌려지면 이 뱀을 켜고 속도를 높이십시오.

PlayerSnake.prototype.spaceKeyDown = function () {

     this.speed = this.fastSpeed;

}

// 스페이스 키가 다시 올라 오면 뱀을 느리게 만듭니다.

PlayerSnake.prototype.spaceKeyUp = function () {

     this.speed = this.slowSpeed;

}


우리가 BotSnake에서 했던 것처럼, 우리는 Snake를 상속 받고 프로토 타입을 복제합니다. 그런 다음 공간 키가 작아지면 이 플레이어의 뱀이 더 빨리 움직 이도록 만듭니다. 뱀에 대해 Part 2에서 작성한 addDestroyedCallback 메소드를 사용하는 방법에 주목하십시오.


이제 우리는 봇 스네이크에서했던 것처럼 새 업데이트 메서드를 만듭니다.


PlayerSnake.prototype.tempUpdate = PlayerSnake.prototype.update;

PlayerSnake.prototype.update = function () {

    // 머리가 회전해야하는 각도를 찾습니다.

    // 마우스를 마주 치기 위해 통과

    var mousePosX = this.game.input.activePointer.worldX;

    var mousePosY = this.game.input.activePointer.worldY;

    var headX = this.head.body.x;

    var headY = this.head.body.y;

    var angle = (180 * Math.atan2 (mousePosX-headX, mousePosY-headY) /Math.PI);

    if (angle> 0) {

        angle = 180- angle;

    }

    else {

        angle = -180-angle;

    }

    var dif = this.head.body.angle - angle;

    this.head.body.setZeroRotation ();

    // 화살표 키 사용 허용

    if (this.cursors.left.isDown) {

        this.head.body.rotateLeft (this.rotationSpeed);

    }

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

        this.head.body.rotateRight (this.rotationSpeed);

    }

    // 왼쪽 또는 오른쪽으로 회전하면 머리를 기울일 지 여부를 결정합니다.

    // 화살표 키를 사용하지 않으면 마우스가 빨라집니다.

    else if (dif <0 && dif> -180 || dif> 180) {

        this.head.body.rotateRight (this.rotationSpeed);

    }

    else if (dif> 0 && dif <180 || dif <-180) {

        this.head.body.rotateLeft (this.rotationSpeed);

    }


    // 원래 뱀 업데이트 메서드를 호출합니다.

    this.tempUpdate ();

}


플레이어 뱀의 업데이트 메소드에 추가 한 것은 화살표 키를 기준으로 처음으로 전환하는 기능입니다. 화살표 키가 눌러지지 않은 경우, 뱀은 커서를 향해 더 빨리 가리키는 방향으로 회전합니다. 본질적으로, 그것은 뱀의 현재 방향과 머리와 커서가 형성하는 선 사이의 각도가 작은면을 찾습니다.


game.js

마지막으로, 우리는 게임에 이 뱀을 추가해야합니다! 우리는 게임 상태의 생성 메소드에서 이것을 해야합니다 :


// 플레이어를 만듭니다.

var snake = new PlayerSnake (this.game, 'circle', 0, 0);

this.game.camera.follow (snake.head);


// 봇을 만듭니다.

new BotSnake (this.game, 'circle', -200, 0);

new BotSnake (this.game, 'circle', 200, 0);


그리고 우리 선수와 로봇이 작동합니다! Part 4에서는 뱀 사이의 충돌을 처리 할 것입니다.


Posted by 마스터킹
|

이 프로젝트의 첫 번째 단계는 서버입니다. 아주 간단한 노드 환경을 설정하고 planck-js, socket.io 및 web3을 설치했습니다.


첫 번째 단계는 Imports 를 초기화 하는 것입니다.





var pl = require('planck-js'); var Vec2 = pl.Vec2
var server = require('http').createServer();
var io = require('socket.io')(server);

var Web3 = require('web3');

step1.js hosted with ❤ by GitHub

이것은 우리에게 중요한 플랙 변수 (우리의 물리 엔진), 우리의 서버, websocket.io(socket.io) 및 Web3(Ethereum)에 대한 액세스를 제공합니다.


이제 우리는 물리 엔진을 설정하고 Web3을 초기화하고 클라이언트를 추적 할 수있는 변수를 만들어야합니다.



// Initialize Web3 var web3 = new Web3(new Web3.providers.HttpProvider('https://ropsten.infura.io/'));
// The time step for the game loop and physics
var FIXED_TIME_STEP = 1/20
// Create a physics world, where bodies and constraints live
var world = new pl.World({
gravity : Vec2(0, 10)
});
// Create an infinite ground plane body
var ground = world.createBody();
ground.createFixture(pl.Edge(Vec2(0, 400), Vec2(500, 400)));
// Default stats for players
var DEFAULT_STATS = {
maxVel: 20
}
// Object to hold client information
var clients = {}

step2.js hosted with ❤ by GitHub


먼저 Ropsten testnet을 사용하여 Web3을 초기화합니다. 이것으로 실제로 Ethereum을 쓰지 않고 테스트 할 수 있습니다.


그런 다음 서버에 고정 된 시간 단계를 설정하고 물리 엔진을 초기화합니다. 


중력은 10으로 설정되지만 Planck의 기본값은 -9입니다. 우리는 Phaser와 함께 훌륭하게 연주하려면 긍정적 이어야 합니다.


우리는 클라이언트 정보를 보유 할 수있는 객체를 생성합니다.. 


이것은 연결 ID (socket.io에 의해 우리에게 주어진)를 Key로, 클라이언트 객체를 Value 로 사용하는 Map으로 작동합니다. 


따라서 특정 클라이언트와 상호 작용하는 것이 훨씬 쉬워집니다.


이제 게임에서 몇몇 플레이어를 얻고 Web3를 설정하십시오!


// Listen for connections io.on('connection', function(client){
// Let us know someone connected
console.log("Client connected!")
// When a player sends the join message
client.on('join', function(data) {
// TODO 1: Do something about a player joining
})
// TODO 2: Listen for more packets
});

part3.js hosted with ❤ by GitHub


누군가가 우리 서버에 연결할 때 io.on ( 'connection')이 호출됩니다. 


일단 접속 이벤트가 발생하면 접속된 클라이언트와 관련된 특정 패킷에 대해 호출하는 함수를 설정할 수 있습니다. 


이 중 첫 번째 메시지는 Join 메시지입니다.


플레이어가 조인 할 때 클라이언트 객체에 정보를 추가하기만 하면됩니다. 


이제 플레이어가 단순히 연결될 때, 이 모든 작업을 수행 할 수 있지만 기다려야 할 것이 있습니다. 


특히 우리는 접속된 클라이언트가 유효한 이더리움 (ethereum) 계정을 갖고 있는지 확인하고, 


실제로 계정 정보를 보내어 게임에 참여하도록 해야 합니다.




클라이언트가 Join 메시지를 보낼 때


나머지는 TODO 1이 있는 곳으로 갑니다.


먼저, 플레이어의 상태 및 물리 엔진을 설정할 수 있습니다.


// Set the player's stats client.stats = DEFAULT_STATS
// Setup client body
client.body = world.createBody({fixedRotation: true}).setDynamic();
client.body.createFixture(pl.Box(1, 1), {friction: 2});
client.body.setPosition(Vec2(50, 50));
client.body.setMassData({
mass : 2,
center : Vec2(),
I : 1
})

statsAndPhysics.js hosted with ❤ by GitHub


일단 클라이언트가 Join 하면 우리는 위에서 설정 한 기본값으로 상태를 설정하고 (아직 스마트 계약을 설정하지 않았으므로) ,


새로운 물리 엔진을 설정합니다.


// Setup eth account info client.ethAccount = data.ethAccount
// test balance
web3.eth.getBalance(client.ethAccount).then( balance => {
var value = web3.utils.fromDecimal(balance);
var balanceInEther = web3.utils.fromWei(value, "ether")
client.balance = balanceInEther
})

ethBalance.js hosted with ❤ by GitHub


클라이언트가 Ethereum 계좌 주소를 보내면 속성으로 추가합니다.


우리의 Web3 기능성을 테스트하기 위해 계좌잔액을 속성으로 추가합니다. 나중에 우리는 클라이언트에게 보내어 표시 할 수 있습니다.


// add client to clients object clients[client.id] = client


// broadcast connection to players
client.broadcast.emit('playerConnected', {id: client.id});

addAndBroadcast.js hosted with ❤ by GitHub


접속된 클라이언트는 클라이언트 객체에 추가 한 다음, 다른 모든 클라이언트에 브로드 캐스팅합니다. 


그러면 다른 클라이언트에게 방금 접속된 플레이어가 표시됩니다.


client.broadcast.emit를 호출하면 socket.io는 현재 접속한 클라이언트를 제외하고 연결된 모든 클라이언트에게 메시지를 보냅니다.



다음 단계


다음 단계는 TODO 2가 있는 곳으로 갑니다.



입력


// add listener to input client.on('input', function(data){
clients[client.id].input = data
});

input.js hosted with ❤ by GitHub


이렇게하면 입력 패킷에 대한 데이터 처리가 추가됩니다. 이것은 각 방향 키 (WASD)에 대한 Bool 객체입니다. 


클라이언트는 입력이 변경 될 때만이 패킷을 보냅니다.  


이를 통해 우리는 매 20 분의 1 초마다 플레이어로부터 어떤 키가 눌려 있는지 추적 할 수 있습니다.



플레이어 상태 요청


// add listener to player state request client.on('playerStateRequest', function(data){
client.emit('requestedPlayerState', {id: client.id, state: {x: client.body.getPosition().x, y: client.body.getPosition().y}})
});

playerStateRequest.js hosted with ❤ by GitHub


일부 플레이어는 다른 플레이어 보다 나중에 접속하게 됩니다.  


우리는 플레이어의 위치 같은 것을 업데이트하기 위해 플레이어의 전체 상태를 끊임없이 보내고 싶지는 않습니다. 


클라이언트가 연결되면 다른 플레이어가 연결되어 있음을 볼 수 있지만 정보가 없습니다. 


클라이언트는 정보가없는 플레이어를 감지하면 playerStateRequest 패킷을 보냅니다.


이렇게 하면 해당 플레이어의 상태가 클라이언트에 전송되어 최신 상태가 될 수 있습니다.




플레이어 접속 해제


// add listener to disconnect client.on('disconnect', function(){
// broadcast player disconnect
client.broadcast.emit('playerDisconnected', {id: client.id});
// safely remove player
world.destroyBody(client.body)
delete clients[client.id]
console.log("Client disconnected!")
});

disconnect.js hosted with ❤ by GitHub


플레이어가 연결을 끊으면 몇 가지 작업을 수행해야합니다. 먼저, 다른 모든 플레이어에게 연결이 끊어 졌음을 알릴 필요가 있습니다. 


이것은 클라이언트에 의해 일종의 타임 아웃을 추가하여 추측 할 수 있었습니다 (플레이어가 잠시 동안 업데이트되지 않았을 수도 있음). 


플레이어가 잠시 동안 메시지를받지 못하면 단순히 플레이어를 삭제하지 말고 각 클라이언트의 접속유지 신호(Heart Beat)를 확인후 처리해 합니다.


이것은 UDP를 사용할 때 분명히 유용하지만 WebSocket (본질적으로 TCP)에서는 필요 없습니다.


우리가 보내는 모든 메시지는 믿을만 하므로 모든 플레이어를 신뢰할 수있게 만듭니다!


역자주) WebSocket 에서는 Keep Alive, Heart Beat 가 필요 없을 만큼 신뢰한다는 내용인것 같습니다. 


          그래도 모바일 환경에서 처럼 잦은 접속 변경 및 끊김에서는 신뢰 하기 힘들겠죠?




게임 루프


이 코드는 이제 io.on ( 'connection') 함수 외부에 있습니다.


function gameLoop() { // Add horizontal spring force

var clientIds = Object.keys(clients)
for (i = 0; i < clientIds.length; i++) {
// If we are trying to access a client who's recently been removed, do nothing
if (!clients[clientIds[i]] || !clients[clientIds[i]].body) {
continue
}
// Get the client and their input
var client = clients[clientIds[i]]
var { input } = client
// Update client with input
if (input.w) {
var f = client.body.getWorldVector(Vec2(0, -10));
var p = client.body.getWorldPoint(Vec2(0.0, 0.0));
client.body.applyLinearImpulse(f, p, true); }
if (input.a) {
var f = client.body.getWorldVector(Vec2(-0.5, 0));
var p = client.body.getWorldPoint(Vec2(0.0, 0.0));
client.body.applyLinearImpulse(f, p, true);
}
if (input.d) {
var f = client.body.getWorldVector(Vec2(0.5, 0));
var p = client.body.getWorldPoint(Vec2(0.0, 0.0));
client.body.applyLinearImpulse(f, p, true);
}
// Limit the players velocity
var linVel = client.body.getLinearVelocity()
if (Math.abs(linVel.x) > client.stats.maxVel) {
linVel.x = 10 * Math.sign(linVel.x)
}
if (Math.abs(linVel.y) > client.stats.maxVel) {
linVel.y = 10 * Math.sign(linVel.y)
}
// Emit the updated player's state to everyone
io.sockets.emit('updatePlayerState', {id: client.id, state: {...client.body.getPosition(), balance: client.balance} });
}
// Move physics forward in time
world.step(FIXED_TIME_STEP/2);
}

gameLoop.js hosted with ❤ by GitHub



이것이 우리 서버의 강점입니다. 1/20 초마다 실행됩니다 (FIXED_TIME_STEP). 


그것은 아주 간단합니다. 그래서 나는 단지 그것을 간단히 보겠습니다.



Lines 4-5 번 라인 모든 클라이언트 객체의 모든 키를 얻고 루프를 반복합니다.


Lines 7-9 클라이언트가 시간 내에 잠재적으로 제거되었을 수 있으므로  클라이언트가 여전히 존재하는지 확인합니다.


Lines 14-27 현재의 입력 상태에 따라 플레이어 물리 엔진을 조정합니다.


Lines 30-36 모든 플레이어가 플래시가 아닌지 확인하기 위해 속도를 제한합니다.


Lines 39 모든 클라이언트에게 새로운 상태를 전송합니다.  여기에는 우리가 방금 사용했던 클라이언트도 포함됩니다. 

           우리는 클라이언트의 속도를 높여야하기 때문입니다!


Line 42 물리엔진의 속도를 절반으로 줄여 게임을 조금 느리게 했다. 이 부분은 단지 환경 설정입니다.




마지막



setInterval(gameLoop, FIXED_TIME_STEP); server.listen(2217);

finalTouches.js hosted with ❤ by GitHub


우리가 작성한 모든 작업은 매 1/20 초마다 gameLoop 함수를 호출하도록 만든 다음 서버를 시작합니다!



결론


이제 우리는 Web3를 사용하여 Ethereum에 연결된 게임 서버를 갖게 되었습니다!


Part 3에서는 클라이언트를 설정하고 게임을 업로드하여 실제로 유저가 사용하는 방법을 보여줍니다.

Posted by 마스터킹
|

주의) 본 내용은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 마스터킹
|