Socket.io로 Phaser 3에서 기본 멀티 플레이어 게임 만들기 - 2 부


Socket.io로 Phaser 3에서 기본 멀티 플레이어 게임 만들기 - 2 부

이 튜토리얼의 파트 1에서는 Node.js 서버를 만들고 기본 Phaser 게임을 설정하고이 둘 사이의 통신을 허용하는 Socket.io를 추가했습니다. 당신이 그것을 놓친다면, 당신은 여기서 하나를 찾을 수 있습니다. 이 튜토리얼에서는 게임에서 플레이어를 추가 및 제거하고 플레이어 입력을 처리하며 플레이어가 수집 물을 픽업 할 수있게하는 클라이언트 측 코드를 추가하는 데 집중할 것입니다.


우리는 아래에서 완료 될 내용을 볼 수 있습니다.



플레이어 추가 - 클라이언트

플레이어를 추가하는 서버 코드를 사용하여 클라이언트 측 코드를 작성합니다. 가장 먼저해야 할 일은 플레이어에게 사용될 자산을로드하는 것입니다. 이 튜토리얼에서는 Kenny 's Space Shooter Redux 자산 팩의 일부 이미지를 사용합니다. 게임의 자산은 여기에서 다운로드 할 수 있습니다.

공용 폴더에서 assets이라는 새 폴더를 만들고 여기에 이미지를 저장합니다. 게임에서 이미지를로드하려면 game.js의 preload 함수에 다음 행을 추가해야합니다.
this.load.image('ship', 'assets/spaceShips_001.png');

우주선 이미지가 로드되면 이제 게임에서 플레이어를 만들 수 있습니다. 이 튜토리얼의 제 1 부에서는 새 플레이어가 게임에 연결될 때마다 currentPlayers 이벤트를 발생하도록 Socket.io 연결을 설정했으며, 이 경우 모든 현재 플레이어가 포함 된 플레이어 개체도 전달했습니다. 이 이벤트를 사용하여 플레이어를 만듭니다.

game.js의 create 함수를 다음과 일치하도록 업데이트하십시오.
function create() {
  var self = this;
  this.socket = io();
  this.socket.on('currentPlayers', function (players) {
    Object.keys(players).forEach(function (id) {
      if (players[id].playerId === self.socket.id) {
        addPlayer(self, players[id]);
      }
    });
  });
}
방금 추가 한 코드를 살펴 보겠습니다.

- 우리는 currentPlayers 이벤트를 수신하기 위해 socket.on을 사용했으며,이 이벤트가 트리거되면 우리가 제공 한 함수가 서버에서 전달한 players 객체로 호출됩니다.
- 이 함수가 호출되면 각 플레이어를 반복하고 해당 플레이어의 ID가 현재 플레이어의 소켓 ID와 일치하는지 확인합니다.
- 플레이어를 통해 반복하기 위해 우리는 Object.keys ()를 사용하여 전달 된 Object의 모든 키 배열을 만듭니다. 반환되는 배열에서 forEach () 메서드를 사용하여 배열의 각 항목을 반복합니다 .
- 마지막으로 addPlayer () 함수를 호출하여 현재 플레이어의 정보와 현재 장면에 대한 참조를 전달했습니다.

이제 game.js에 addPlayer 함수를 추가해 보겠습니다. 파일의 맨 아래에 다음 코드를 추가하십시오.
function addPlayer(self, playerInfo) {
  self.ship = self.physics.add.image(playerInfo.x, playerInfo.y, 'ship').setOrigin(0.5, 0.5).setDisplaySize(53, 40);
  if (playerInfo.team === 'blue') {
    self.ship.setTint(0x0000ff);
  } else {
    self.ship.setTint(0xff0000);
  }
  self.ship.setDrag(100);
  self.ship.setAngularDrag(100);
  self.ship.setMaxVelocity(200);
}
위 코드에서 우리는 :

- 우리 서버 코드에서 생성 한 x와 y 좌표를 사용하여 플레이어의 배를 만들었습니다.
- 플레이어의 배를 만들기 위해 self.add.image를 사용하는 대신 self.physics.add.image를 사용하여 게임 객체가 아케이드 물리를 사용할 수있게했습니다.
- setOrigin ()을 사용하여 게임 객체의 원점을 왼쪽 상단 대신 객체의 중간에 설정했습니다. - 우리가 게임 객체를 회전시킬 때 원점을 중심으로 회전하기 때문에 이렇게했기 때문입니다.
- setDisplaySize ()를 사용하여 게임 객체의 크기와 크기를 변경했습니다. 원래 배 이미지는 106x80 픽셀이었습니다.이 게임은 우리 게임에 약간 컸습니다. setDisplaySize ()를 호출 한 후 이미지가 이제 게임에서 53x40 픽셀입니다.
- 우리는 setTint ()를 사용하여 배 게임 객체의 색상을 변경했으며, 서버에 플레이어 정보를 생성 할 때 생성 된 팀에 따라 색상을 선택합니다.
- 마지막으로 setDrag, setAngularDrag 및 setMaxVelocity를 사용하여 게임 객체가 아케이드 물리에 반응하는 방식을 수정했습니다. setDrag 및 setAngularDrag는 객체가 움직일 때 마주 치게 될 저항의 양을 제어하는 ​​데 사용됩니다. setMaxVelocity는 게임 개체가 도달 할 수있는 최대 속도를 제어하는 ​​데 사용됩니다.

브라우저를 새로 고침하면 플레이어의 배가 화면에 표시됩니다. 서버가 실행 중이 아니면 명령 줄에서 프로젝트 폴더로 이동하여 다음과 같이 명령을 실행할 수 있습니다. 
node server.js.
또한 게임을 새로 고침하면 우주선이 다른 위치에 나타나고 우주선이 빨간색이나 파란색으로 무작위로 표시되어야합니다.


다른 플레이어 추가

이제 우리 플레이어가 게임에 등장 했으므로 다른 플레이어를 게임에 표시하기위한 로직을 추가 할 것입니다. 튜토리얼의 파트 1에서는, newPlayer 및 disconnect 이벤트를 발행하는 Socket.io도 설정합니다. 이 두 가지 이벤트와 currentPlayers 이벤트에 대한 현재 논리를 사용하여 다른 플레이어를 추가하거나 제거합니다. 이렇게하려면 다음과 일치하도록 만들기 함수를 업데이트하십시오.
function create() {
  var self = this;
  this.socket = io();
  this.otherPlayers = this.physics.add.group();
  this.socket.on('currentPlayers', function (players) {
    Object.keys(players).forEach(function (id) {
      if (players[id].playerId === self.socket.id) {
        addPlayer(self, players[id]);
      } else {
        addOtherPlayers(self, players[id]);
      }
    });
  });
  this.socket.on('newPlayer', function (playerInfo) {
    addOtherPlayers(self, playerInfo);
  });
  this.socket.on('disconnect', function (playerId) {
    self.otherPlayers.getChildren().forEach(function (otherPlayer) {
      if (playerId === otherPlayer.playerId) {
        otherPlayer.destroy();
      }
    });
  });
}
방금 추가 한 코드를 살펴 보겠습니다.

- otherPlayers라는 새 그룹을 만들었습니다.이 그룹은 게임에서 다른 모든 플레이어를 관리하는 데 사용됩니다. Phaser의 그룹에 익숙하지 않은 경우, 유사한 게임 개체를 관리하고 하나의 단위로 제어 할 수있는 방법입니다. 한 가지 예는 각 게임 개체의 충돌을 개별적으로 확인하지 않고 그룹과 다른 게임 개체 간의 충돌을 검사 할 수 있다는 것입니다.
currentPlayers 이벤트가 발생할 때 호출되는 함수를 업데이트하여 플레이어가 현재 플레이어가 아닌 경우 플레이어 객체를 반복 할 때 addOtherPlayers 함수를 호출합니다.
- 우리는 socket.on ()을 사용하여 newPlayer를 수신하고 이벤트 연결을 끊습니다.
- newPlayer 이벤트가 발생하면 addOtherPlayers 함수를 호출하여 새 플레이어를 게임에 추가합니다.
- 연결 해제 이벤트가 발생하면 해당 플레이어의 ID를 가져와 해당 플레이어의 배를 게임에서 제거합니다. 우리는 otherPlayers 그룹에서 getChildren () 메서드를 호출하여이 작업을 수행합니다. getChildren () 메서드는 해당 그룹에있는 모든 게임 객체의 배열을 반환하며 여기에서 forEach () 메서드를 사용하여 해당 배열을 반복합니다.
- 마지막으로 destroy () 메서드를 사용하여 해당 게임 객체를 게임에서 제거합니다.

이제 우리 게임에 addOtherPlayers 함수를 추가합시다. game.js의 아래쪽에 다음 코드를 추가하십시오 :
function addOtherPlayers(self, playerInfo) {
  const otherPlayer = self.add.sprite(playerInfo.x, playerInfo.y, 'otherPlayer').setOrigin(0.5, 0.5).setDisplaySize(53, 40);
  if (playerInfo.team === 'blue') {
    otherPlayer.setTint(0x0000ff);
  } else {
    otherPlayer.setTint(0xff0000);
  }
  otherPlayer.playerId = playerInfo.playerId;
  self.otherPlayers.add(otherPlayer);
}
이 코드는 addPlayer () 함수에서 추가 한 코드와 매우 비슷합니다. 가장 큰 차이점은 otherPlayers 그룹에 다른 플레이어의 게임 개체를 추가했다는 것입니다.

다른 플레이어를 추가하기 위해 게임 논리를 테스트하기 전에 마지막으로해야 할 일은 우리가이 플레이어를 위해 자산을로드해야한다는 것입니다. 그 자산을 여기서 찾을 수 있습니다.

이 이미지는 assets 폴더에 저장해야합니다. 이미지가 있으면 이미지를 게임에로드 할 수 있습니다. 프리로드 함수에 다음 행을 추가하십시오.
this.load.image('otherPlayer', 'assets/enemyBlack5.png');

이제 브라우저에서 게임을 새로 고침하면 플레이어의 배가 계속 표시됩니다. 다른 탭이나 브라우저를 열고 게임으로 이동하면 게임 창에 여러 개의 스프라이트가 나타나야하며, 해당 게임 중 하나를 닫으면 다른 게임에서 스프라이트가 사라지는 것을 볼 수 있습니다.


플레이어 입력 처리

게임에서 모든 플레이어를 표시하기위한 논리를 사용하여 플레이어 입력을 처리하고 플레이어가 움직 이도록 할 것입니다. Phaser의 내장 키보드 관리자를 사용하여 플레이어 입력을 처리 할 것입니다. 이렇게하려면 game.js의 create 함수 맨 아래에 다음 행을 추가하십시오.
this.cursors = this.input.keyboard.createCursorKeys();
이렇게하면 커서 객체를 네 개의 기본 Key 객체 (위쪽, 아래쪽, 왼쪽 및 오른쪽)로 채우고 키보드의 해당 화살표에 바인딩합니다. 그런 다음, 업데이트 기능에서 이러한 키가 눌러져 있는지 확인하면됩니다.

game.js의 업데이트 함수에 다음 코드를 추가합니다.
if (this.ship) {
    if (this.cursors.left.isDown) {
      this.ship.setAngularVelocity(-150);
    } else if (this.cursors.right.isDown) {
      this.ship.setAngularVelocity(150);
    } else {
      this.ship.setAngularVelocity(0);
    }
  
    if (this.cursors.up.isDown) {
      this.physics.velocityFromRotation(this.ship.rotation + 1.5, 100, this.ship.body.acceleration);
    } else {
      this.ship.setAcceleration(0);
    }
  
    this.physics.world.wrap(this.ship, 5);
  }
위의 코드에서 우리는 다음을 수행했습니다.

- 왼쪽, 오른쪽 또는 위로 키를 눌렀는지 확인하고 있습니다.
- 왼쪽 또는 오른쪽 키를 누르면 setAngularVelocity ()를 호출하여 플레이어의 각속도를 업데이트합니다. 각속도는 선박이 좌우로 회전하도록합니다.
- 왼쪽 또는 오른쪽 키를 누르지 않으면 각속도가 다시 0으로 재설정됩니다.
- 위로 키를 누르면 배의 속도가 업데이트되고 그렇지 않으면 0으로 설정됩니다.
- 마지막으로, 배가 화면에서 벗어나면 화면의 다른면에 나타납니다. 우리는 physics.world.wrap ()을 호출하여이 작업을 수행하고 랩하려는 게임 객체와 오프셋에 전달합니다.

게임을 새로 고침하면 우주선을 화면에서 움직일 수있게되었습니다.


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

이제 플레이어가 움직일 수있게되었으므로 게임에서 다른 플레이어의 움직임을 처리 할 것입니다. 다른 플레이어의 움직임을 추적하고 게임에서 스프라이트를 이동하려면 플레이어가 움직일 때마다 새로운 이벤트를 내 보내야합니다. game.js의 업데이트 기능에서 if 문 안에 다음 코드를 추가하십시오.
2
3
4
5
6
7
8
9
10
11
12
13
14
// emit player movement
var x = this.ship.x;
var y = this.ship.y;
var r = this.ship.rotation;
if (this.ship.oldPosition && (x !== this.ship.oldPosition.x || y !== this.ship.oldPosition.y || r !== this.ship.oldPosition.rotation)) {
  this.socket.emit('playerMovement', { x: this.ship.x, y: this.ship.y, rotation: this.ship.rotation });
}
 
// save old position data
this.ship.oldPosition = {
  x: this.ship.x,
  y: this.ship.y,
  rotation: this.ship.rotation
};
방금 추가 한 코드를 살펴 보겠습니다.

- 우리는 세 가지 새로운 변수를 생성하여 플레이어에 대한 정보를 저장합니다.
- 그런 다음이 변수를 플레이어의 이전 회전 및 위치와 비교하여 플레이어의 회전 또는 위치가 변경되었는지 확인합니다. 플레이어의 위치 나 회전이 변경되면 playerMovement라는 새 이벤트가 발생하고 플레이어의 정보가 전달됩니다.
- 마지막으로 플레이어의 현재 회전 및 위치를 저장합니다.

다음으로 server.js의 Socket.io 코드를 업데이트하여 새 playerMovement 이벤트를 수신해야합니다. server.js에서 socket.io ( 'disconnect') 코드 아래에 다음 코드를 추가합니다.
// when a player moves, update the player data
socket.on('playerMovement', function (movementData) {
  players[socket.id].x = movementData.x;
  players[socket.id].y = movementData.y;
  players[socket.id].rotation = movementData.rotation;
  // emit a message to all players about the player that moved
  socket.broadcast.emit('playerMoved', players[socket.id]);
});
playerMovement 이벤트가 서버에서 수신되면 서버에 저장되어있는 해당 플레이어의 정보를 업데이트하고 다른 모든 플레이어에게 playerMoved라는 새 이벤트를 내 보낸 다음이 이벤트에서 업데이트 된 플레이어의 정보를 전달합니다.

마지막으로이 새 이벤트를 수신 대기하도록 클라이언트 측 코드를 업데이트해야하며이 이벤트가 발생하면 게임에서 해당 플레이어의 스프라이트를 업데이트해야합니다. game.js의 create 함수에서 다음 코드를 추가하십시오.
this.socket.on('playerMoved', function (playerInfo) {
  self.otherPlayers.getChildren().forEach(function (otherPlayer) {
    if (playerInfo.playerId === otherPlayer.playerId) {
      otherPlayer.setRotation(playerInfo.rotation);
      otherPlayer.setPosition(playerInfo.x, playerInfo.y);
    }
  });
});

이제 서버를 다시 시작하고 두 개의 다른 탭이나 브라우저에서 게임을 열고 플레이어 중 하나를 이동하면 해당 플레이어가 다른 게임으로 이동하는 것을 볼 수 있습니다.

별 수집하기

이제 다른 플레이어의 움직임을 다루는 게임에서 우리는 플레이어에게 목표를 제공해야합니다. 이 튜토리얼에서는 플레이어가 수집 할 수 있도록 게임에 수집 할 수있는 별표를 추가하고 팀이 10 점을 얻게됩니다. 이를 위해 몇 가지 Socket.io 이벤트를 게임에 추가해야하며 먼저 서버 로직부터 시작합니다.

먼저 서버에 두 개의 새로운 변수 인 별표와 점수를 추가합니다. 스타 변수는 스타 수집품의 위치를 저장하는 데 사용되며 점수 변수는 두 팀의 점수를 추적하는 데 사용됩니다. server.js에 var players = {}; 아래에 다음 코드를 추가합니다.
var star = {
  x: Math.floor(Math.random() * 700) + 50,
  y: Math.floor(Math.random() * 500) + 50
};
var scores = {
  blue: 0,
  red: 0
};

다음으로, 새로운 플레이어가 게임, starLocation 및 scoreUpdate에 연결하면 두 개의 새로운 이벤트가 발생합니다. 이 두 가지 이벤트를 사용하여 새로운 플레이어에게 스타 수집품의 위치와 두 팀의 현재 점수를 보냅니다. server.js에서 socket.emit ( 'currentPlayers', players) 아래에 다음 코드를 추가하십시오.
// send the star object to the new player
socket.emit('starLocation', star);
// send the current scores
socket.emit('scoreUpdate', scores);

마지막으로 우리는 starCollected라는 새로운 이벤트를 듣게됩니다.이 이벤트는 플레이어가 스타를 수집 할 때 트리거됩니다. 이 이벤트가 접수되면 올바른 팀의 점수를 업데이트하고 별의 새 위치를 생성하고 각 플레이어에게 업데이트 된 점수와 새 별을 알릴 필요가 있습니다.

server.js에서 socket.on ( 'playerMovement') 코드 아래에 다음 코드를 추가합니다.
socket.on('starCollected', function () {
  if (players[socket.id].team === 'red') {
    scores.red += 10;
  } else {
    scores.blue += 10;
  }
  star.x = Math.floor(Math.random() * 700) + 50;
  star.y = Math.floor(Math.random() * 500) + 50;
  io.emit('starLocation', star);
  io.emit('scoreUpdate', scores);
});

이제 서버 로직이 업데이트되었으므로 game.js의 클라이언트 측 코드를 업데이트해야합니다. 두 팀 점수를 표시하기 위해 Phaser의 텍스트 게임 개체를 사용합니다. create 함수에서 함수의 맨 아래에 다음 코드를 추가하십시오.
this.blueScoreText = this.add.text(16, 16, '', { fontSize: '32px', fill: '#0000FF' });
this.redScoreText = this.add.text(584, 16, '', { fontSize: '32px', fill: '#FF0000' });
  
this.socket.on('scoreUpdate', function (scores) {
  self.blueScoreText.setText('Blue: ' + scores.blue);
  self.redScoreText.setText('Red: ' + scores.red);
});
방금 추가 한 코드를 살펴 보겠습니다.

- this.add.text ()를 호출하여 두 개의 새로운 텍스트 게임 객체를 만들었습니다. 이 두 객체를 만들 때 객체 배치 위치, 객체의 기본 텍스트, 텍스트 객체에 사용할 글꼴 크기 및 채우기를 전달했습니다.
- scoreUpdate 이벤트가 수신되면 setText () 메서드를 호출하여 게임 개체의 텍스트를 업데이트하고 팀의 점수를 각 개체에 전달합니다.

게임을 새로 고침하면 각 팀의 점수가 표시됩니다.

마지막으로 추가 할 필요가있는 것은 스타 수집품에 대한 논리입니다. 우선, 프리로드 기능에서 스타 콜렉터의 자산을로드해야합니다. 게임의 자산은 여기에서 다운로드 할 수 있습니다.

프리로드 함수에 다음 코드를 추가하십시오.
this.load.image('star', 'assets/star_gold.png');

그런 다음 create 함수에서 방금 추가 한 점수 업데이트 코드 아래에 다음 코드를 추가합니다.
this.socket.on('starLocation', function (starLocation) {
  if (self.star) self.star.destroy();
  self.star = self.physics.add.image(starLocation.x, starLocation.y, 'star');
  self.physics.add.overlap(self.ship, self.star, function () {
    this.socket.emit('starCollected');
  }, null, self);
});
위의 코드에서 우리는 starLocation 이벤트를 수신했으며 수신되면 다음을 수행합니다.

- 우리는 별의 존재 여부를 확인하고, 존재한다면 별을 파괴합니다.
- 플레이어의 게임에 새로운 스타 게임 객체를 추가하고 이벤트에 전달 된 정보를 사용하여 위치를 채 웁니다.
- 마지막으로 우리는 플레이어의 배와 스타 오브젝트가 중첩되어 있는지 확인하기 위해 체크를 추가했으며, 그렇다면 starCollected 이벤트를 내 보냅니다. physics.add.overlap을 호출하면 Phaser가 자동으로 오버랩을 확인하고 오버랩이있을 때 제공된 기능을 실행합니다.

게임을 새로 고침하면 화면에 스타 수집품이 표시됩니다.


결론

스타 수집품을 게임에 추가하면이 가이드가 끝납니다. 요약하면이 튜토리얼에서는 Socket.io 및 Node.js를 사용하여 Phaser에서 기본 멀티 플레이어 게임을 만드는 방법을 보여줍니다.

이 튜토리얼을 즐겁게 사용하여 도움이 되었기를 바랍니다. 질문이나 제안 사항에 대한 의견이 있으시면 아래 의견에 저희에게 알려주십시오.

댓글 없음:

댓글 쓰기