첫 번째 단계는 초기화하는 것입니다.
var pl = require('planck-js'); | |
var Vec2 = pl.Vec2 | |
var server = require('http').createServer(); | |
var io = require('socket.io')(server); | |
var Web3 = require('web3'); |
이것은 우리에게 중요한 플랜크 변수 (우리의 물리 엔진), 우리 서버, 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 = {} |
먼저 Ropsten testnet을 사용하여 Web3을 초기화합니다. 이것으로 실제로 Ethereum을 쓰지 않고 테스트 할 수 있습니다.
그런 다음 서버에 고정 된 시간 단계를 설정하고 물리 세계를 초기화합니다. 중력은 10으로 설정되지만 Planck의 기본값은 -9입니다. 우리는 페이저와 잘 어울리는 것을 긍정적으로 표현해야합니다.
우리는 플레이어가 서서 클라이언트 정보를 보유 할 수있는 객체를 만들 수있는 간단한 근거를 마련합니다. 이것은 연결 ID (socket.io에 의해 우리에게 주어진)를 키로, 클라이언트 객체를 값으로 사용하여 맵으로 작동합니다. 따라서 특정 클라이언트와 상호 작용하는 것이 훨씬 쉬워집니다.
이제 게임에서 몇몇 플레이어를 얻고 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 | |
}); |
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 | |
}) |
일단 우리가 가입하면 우리는 위에서 설정 한 기본값으로 통계를 설정하고 통계를 아직 지키기위한 현명한 계약을 설정하지 않았기 때문에 새로운 피직스 바디를 제공합니다.
// 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 | |
}) |
고객은 Ethereum 계정 주소를 보내서 속성으로 추가합니다.
우리의 Web3 기능성을 테스트하기 위해 균형을 잡아서 속성으로 추가합니다. 나중에 우리는 이것을 클라이언트에게 보내서 그들이 그것을 보여줄 수있게 할 것입니다.
// add client to clients object | |
clients[client.id] = client | |
// broadcast connection to players | |
client.broadcast.emit('playerConnected', {id: client.id}); |
이 클라이언트를 클라이언트 객체에 추가 한 다음이 플레이어가 성공적으로 참여한 다른 모든 클라이언트에 브로드 캐스팅합니다. 다른 클라이언트에게이 플레이어의 렌더링을 시작하게합니다.
client.broadcast.emit를 호출하면 socket.io는 현재 클라이언트를 제외하고 연결된 모든 클라이언트에이 메시지를 보냅니다.
다음 단계
다음 단계는 TODO 2가있는 곳으로갑니다.입력
// add listener to input | |
client.on('input', function(data){ | |
clients[client.id].input = data | |
}); |
이렇게 하면 입력 패킷에 대한 수신기가 추가됩니다. 이것은 각 중요한 키 (WASD)에 대한 부울이있는 단순한 개체입니다. 클라이언트는 입력이 변경되었을 때만이 패킷을 보냅니다. 이를 통해 우리는 매 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 패킷을 보냅니다.
이렇게하면 해당 플레이어의 상태가 클라이언트에 전송되어 최신 상태가 될 수 있습니다.
플레이어가 연결 끊김
// 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!") | |
}); |
플레이어가 연결을 끊으면 몇 가지 작업을 수행해야합니다. 먼저, 다른 모든 플레이어에게 연결이 끊어 졌음을 알릴 필요가 있습니다. 이것은 클라이언트에 의해 일종의 타임 아웃을 추가하여 추측 할 수 있습니다 (플레이어가 잠시 동안 업데이트되지 않았을 수도 있음). 과거에는 그런 식으로하는 것이 정말 짜증났습니다.
우리가 잠시 동안 메시지를받지 못하면 단순히 플레이어를 제거하면 각 클라이언트에서 시간 초과를 방지하기 위해 심장 박동을 보내야 함을 의미합니다. 이것은 UDP를 사용할 때 분명히 유용하지만 WebSocket (본질적으로 TCP)에서는이를 원하지 않습니다. 우리가 보내는 모든 메시지는 신뢰할 수 있으므로 모든 플레이어를 신뢰할 수있게 만듭니다!
게임 루프
이 코드는 이제 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); | |
} |
이것이 우리 서버의 강점입니다. 1/20 초마다 실행됩니다 (FIXED_TIME_STEP). 꽤 똑바로되어 있으므로 간단히 살펴 보겠습니다.
4-5 행 모든 클라이언트를 반복하고 싶기 때문에 클라이언트 객체의 모든 키를 얻고 루프를 반복합니다.
Lines 7-9 클라이언트가이 시간 내에 잠재적으로 제거되었을 수 있으므로이 클라이언트가 여전히 존재하는지 확인합니다.
Lines 14-27 플레이어의 현재 입력 상태에 따라 물리 구조 바디를 조정합니다.
Lines 30-36 모든 플레이어가 플래시가 아닌지 확인하기 위해 속도를 제한합니다.
39 행 모든 고객에게 새 상태를 내 보냅니다. 여기에는 우리가 방금 썼던 고객이 포함됩니다. 우리는 클라이언트의 속도를 높여야하기 때문입니다!
Line 42 물리학을 정시에 전진 시키십시오. 나는 물리학의 속도를 반으로 줄이며 게임을 조금 느리게했다. 이 부분은 단지 환경 설정입니다.
Final Touches
setInterval(gameLoop, FIXED_TIME_STEP); | |
server.listen(2217); |
우리가 여기서하는 모든 작업은 매 1/20 초마다 gameLoop 함수를 호출 할 간격을 만든 다음 서버가 청취를 시작하도록합니다!
결론
이제 우리는 Web3를 사용하여 Ethereum에 연결된 게임 서버를 갖게되었습니다!Part 3에서는 클라이언트를 설정하고 게임을 업로드하여 실제로이 사람을 사용하기 시작할 수있는 방법을 보여줄 것입니다.
댓글 없음:
댓글 쓰기