Simplified tutorial for creating multiplayer games using lua/love. This is my answer to the tutorial desired by the love community: http://love2d.org
Just give me a simple framework that demonstrates how to do simple sending and receiving.
Multiplayer tutorial | Simplified tutorial for creating multiplayer games using lua/love. |
Assumptions | What is assumed about the programmer (you) writing a game. |
Game description | What we will do. |
Game architecture | How we manage things. |
Game implementation | What we use and how. |
Game implementation - stage 1 | Create simple oneplayer game. |
Game implementation - stage 2 | Change it to the multiplayer game. |
What is assumed about the programmer (you) writing a game.
So this is not for newbies, but if you are able to create a simple game for love, then you should be able to make it multiplayer (I hope so). There is no code snippets in this tutorial, instead you should loog at the game source while reading this text.
What we will do.
We will create our game in 2 stages. First it will be simple one-player game. Then in stage 2 we will make it multiplayer.
So for the simple game in stage 1 we need
For stage 2
How we manage things.
The game is quite simple. To make things work better in the future, we will set a global table called GameState. This table will keep all data required to get the current game state. For our game it will contain
Each player will be drawn as a circle, so we don’t need to keep a shape of it. But we need its position (X, Y) and color. For the player we will make a class <Player>. Each wall will be a randomly generated rectangle, so we need its position and size. There will be a list of messages - who said what. We will keep only the last 5 messages.
I will split the game code between main.lua (which will be mostly GUI- and mainloop related) and GameLogic.lua (which will keep all the game logic).
Create simple oneplayer game. In this stage we will be looking at stage1 directory.
Siple class for each player. First I will create the player class (look at Player.lua). Player has its position and color (given or randomly generated).
Then I create the GameLogic class (look at GameLogic.lua). Important notes for GameLogic
Creating the GUI/main loop.
This is quite standard. Some remarks
To test this stage
$ cd stage1 $ love .
Move your player with cursor keys, press F2/F6 to add/remove “auto” player, keep pressing F3 for new random players.
Change it to the multiplayer game.
At this stage we will look at the stage2 directory.
I need some files, which I need to copy: UDPIPE.lua, UDPROXY.lua (the framework), dkjson.lua (json encoding), and optionally UDPIPEVERBOSE.lua (for debugging).
Now the game server will be another process, possibly run on different server. So I create gameserver.lua file, which is an entry point (main loop) of my server. It can be run in plain lua (with luasocket) or with love.
The server uses the same GameLogic.lua from stage1, overriding only its updateState method, so that the new information is sent out to all connected clients.
It uses also UDPIPE as a UDP-server, overriding it onNewMessage method such that it unserializes the JSON message, and if the method of the GameLogic exists (with a name of the command from the message), it calls it with provided arguments. If the function returns a result, it is serialized and sent out to the sender (client).
I can not use GameLogic directly, because it is on the server now. So I create ClientGameLogic.lua, which returns an instance of UDPROXY, and I modify it by overriding some methods (those not overrided will be sent to the server). Because this is a proxy for the original GameLogic, I do not need to modify it too much.
I will use local copy of GameState, because my GUI code need a fast access to it (it draws players/walls every frame). This local copy is initialized by the client with some dummy values, and then updated with values from the server. I have modified the getGameState() method by adding a force argument - if there is no argument, then local copy is returned to GUI. If there is a force argument, then the request is sent to the server.
Here is the diff to the previous main.lua file:
@@ -1,5 +1,5 @@ local lg=love.graphics -local GameLogic=require 'GameLogic' +local Game=require 'ClientGameLogic' username=os.getenv('USER') or 'guest' password='secret' @@ -10,9 +10,7 @@ function love.load() lg.setMode(MAXW, MAXH) - Game=GameLogic() - Game:login(username, password) - GameState=Game:getGameState(true) + Game:setUser(username, password) FB=lg.newFramebuffer() end @@ -55,6 +53,7 @@ -- Draw player name lg.print(username, MAXW-120, 10) + lg.print(string.format('Pkts: %d/%d', Game:getSentPackets(), Game:getReceivedPackets()), MAXW-320, 60) end local wascached=CACHED @@ -82,7 +81,7 @@ -- F1 - reinitialize the game if k=='f1' then Game:initialize() - GameState=Game:getGameState() + GameState=Game:getGameState(true) CACHED=nil return end @@ -162,8 +161,19 @@ dy=d end if dx~=0 or dy~=0 then - local nx,ny=p.x+dx, p.y+dy - Game:movePlayerTo(username, nx, ny) - CACHED=nil + if LASTMOVE then + LASTMOVE=LASTMOVE+dt + if LASTMOVE>0.05 then + LASTMOVE=nil -- reset last move timer. See also: ClientGameLogic:onPlayerUpdated + end + end + if not LASTMOVE then + local nx,ny=p.x+dx, p.y+dy + Game:movePlayerTo(username, nx, ny) + CACHED=nil + LASTMOVE=0 -- start last move timer + else + -- skip moving - don't do it too often + end end end
The list of changes goes like this
To test this stage
$ # terminal 1 $ lua gameserver.lua $ # terminal 2 $ cd stage1 $ love . $ # terminal 3 $ cd stage1 $ USER="player2" love .
Move your player with cursor keys, press F2/F6 to add/remove “auto” player, keep pressing F3 for new random players.