Last active
May 8, 2021 15:10
-
-
Save wolf81/1d8bd82acf088a8820751d0c2dc4cdb9 to your computer and use it in GitHub Desktop.
fixed timestep, state interpolation, client-side prediction, apply player input without lag
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| local game = Class {} | |
| -- the index of the current player, which is always 1, regardless of how many clients are in array | |
| local CURRENT_CLIENT_INDEX = 1 | |
| local TICK_RATE = 1/60 | |
| local function exitTo(game, nextState) --[[ exit to other scenes ]] end | |
| function game:init() | |
| Scene.init(self, nil, textures['background-game']) | |
| self.hud = Hud(0, HUD_Y) | |
| self.time = 0 | |
| end | |
| --[[ | |
| The following cases are possible when entering this state: | |
| - human vs cpu => 1 server; 2 clients; index 1 is current player; index 2 is CPU player | |
| - human (server) vs human (client) => 1 server; 1 client; index 1 is current player | |
| - human (client) vs human (server) => no server; 1 client; index 1 is current player | |
| ]] | |
| function game:enter(previous, server, clients) | |
| self.server = server | |
| self.clients = clients | |
| self.state = self.clients[CURRENT_CLIENT_INDEX].state | |
| self.clients[CURRENT_CLIENT_INDEX].disconnected = function() | |
| if self.state.sc == nil then exitTo(self, Disconnect {}) end | |
| end | |
| end | |
| function game:update(dt) | |
| Scene.update(self, dt) | |
| -- FIX TIMESTEP TO 60 FPS | |
| self.time = self.time + dt | |
| while self.time >= TICK_RATE do | |
| self.time = self.time - TICK_RATE | |
| -- GET STATE FROM CLIENT | |
| local state = self.clients[CURRENT_CLIENT_INDEX].state | |
| -- APPLY PLAYER INPUT IMMEDIATELY TO STATE, DON'T WAIT FOR SERVER TO PROCESS | |
| local paddleId = self.clients[CURRENT_CLIENT_INDEX].paddleId | |
| if paddleId ~= nil then | |
| local paddle = state.paddles[paddleId] | |
| local direction = self.clients[CURRENT_CLIENT_INDEX].control.direction | |
| state:paddleMove(paddleId, direction) | |
| end | |
| -- UPDATE STATE (CLIENT-SIDE PREDICTION) | |
| state:update(dt) | |
| --[[ | |
| STATE INTERPOLATION (disabled) | |
| -- if the time difference between current and next state is small, | |
| interpolate position of entities | |
| local stateDt = math.abs(self.state.time - state.time) | |
| if stateDt < (TICK_RATE * 2) then | |
| for id, lastEntity in pairs(self.state.entities) do | |
| local entity = state.entities[id] | |
| if entity ~= nil and entity:getType() ~= ENTITY_TYPE_BLOCK then | |
| entity.position.x = lume.lerp(lastEntity.position.x, entity.position.x, stateDt / TICK_RATE) | |
| entity.position.y = lume.lerp(lastEntity.position.y, entity.position.y, stateDt / TICK_RATE) | |
| end | |
| end | |
| end | |
| ]] | |
| if self.server ~= nil then self.server:update(TICK_RATE) end | |
| for _, client in ipairs(self.clients) do client:update(TICK_RATE) end | |
| self.state = state | |
| end | |
| end | |
| function game:draw() | |
| Scene.draw(self) | |
| -- render code here | |
| end | |
| function game:keypressed(key, code) | |
| self.clients[CURRENT_CLIENT_INDEX]:keypressed(key, code) | |
| end | |
| function game:keyreleased(key, code) | |
| self.clients[CURRENT_CLIENT_INDEX]:keyreleased(key, code) | |
| if key == 'escape' then exitTo(self, Menu {}) end | |
| end | |
| function game:quit() | |
| if self.server ~= nil then self.server:stop() end | |
| self.clients[CURRENT_CLIENT_INDEX]:disconnect() | |
| end | |
| return game | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment