This started out from the prior mmo game blog post and spiraled out of control somewhat. I will probably break it into parts as I expand on sections that are not clear, but for now it is in a single post. www.cmdship.net currently has a copy of the app running on it . My current idea is to develop a multiplayer RPG sim on that domain which will be based on the idea in this blog post - the details of which I will put into a post once the idea is fully formed.
Update 2020 - The code on the repo is currently unavailable, so all the code links on this page will not work. This will be restored shortly. Cmdship.net will also be down for the time being while it is being upgraded to a newer version
So I am going to build a basic browser based multiplayer space game/sim using NodeJS/React/MongoDB and HTML5 canvas. As I build the part I will bung in the code or alterations, attempt to explain what it does/should do - which if you manage to decipher and understand should end up looking something like the following image. The latest source will be at: https://github.com/dbjsdev/cmdship. At the end of each part you will find a link to the source code for that part.
Warning: This isn’t intended to be a guide on what is the best way to do things - I am making this up as I go, and then later coming back to fix it up - the further it goes the more mad it may get and performance will probably be terrible. It will routinely include code that probably violates all sorts of rules and some of it may not even make any sense due to the nature of how the early parts were written. I do however plan to keep returning and updating the prior sections as I work and improve the code. Some of the comments were written as I wrote the post, some I have added in later for clarity and will attempt to do this further as I get time. At times it may just seem like endless walls of code pasted in - as it progresses further this will hopefully reduce. At the end of each part I will put in a link to the source up until that point. If you spot any issues, or have ideas for how parts can be improved/done better, feel free to leave a comment at the bottom of the post or contact me and I will try implement it.
The basic idea is a browser based sim, with simple polygons and shapes only - no flash graphics, html5 canvas only,whereby a player registers/connects and is then loaded into a 2d game world in space, controlling a spaceship. The user can eject from the spaceship, and fly about as a PTU(a small pod thing kind of like in eve), and board other empty spacecraft.
The game world will be broken into Zones or areas that represent star systems. Users can then move between star systems using a series of stargates.
The player ship will have a hyperdrive which lets them move between locations inside a star system, and also move the ship using thrust and direction keys and also an autopilot. The ship will have modules which can fitted such as a cannon, mining laser, shield etc. The game interface will have an item system which allows cargo to moved between locations such as a storage object and the ships cargo hold. The player can also dock with space stations and store vehicles and cargo there. I am also going to integrate a cryptocurrency into the app for the in-game currency.
This app is going to consist of two parts. A frontend client which renders the game world to a canvas with React/Redux handling parts of the UI and interface. On the server side there will be a NodeJS server which handles the login/registration and runs the game server with data being persisted to a MongoDB database. The communication between client/server will be done with Express and Socket.io.
My initial task is going to be to build the basic framework for the client and server communicate, login in the user. To start this I will first get Express setup, delivering the client React app built using webpack.
I am going to whizz through and only briefly cover this intial express/react setup. You can find more details on what exactly is going on with some of this in my other posts, or by looking around on google which can explain webpack in more detail. I’ve started by creating a new repo and clone it locally and setup the basic folder structure we’ll use. (Incase it isn’t clear, when in the code snippets I use ‘…’, it is it to make it clear ive ommited sections of the file that I havn’t changed)
I then ran “npm init”, setting my main entry to ‘src/server/main.js’
I am creating this off using a template I already had so instead of installing what I need via a terminal I copied in the following into my package.json. (Some of these versions you’ll see in this tutorial are what they are due to compatability issues encountered later. It’s possible the issues are fixed by the time you are reading this)
Update your dependencies in package.json to the following and also be sure to add the scripts section.
I then installed the above with
I created a .gitignore file on the root containing the /node_modules folder.
I created webpack.config.js on the root.
Next I created an index.html in my root folder
I then created app.js in src/client/ containing the following basic react code which will output Hello in an H1.
Next in order to run our express server I created the main.js within /src/server/ which we specified in our package.json.
This code just sets up our express server and serves up our index.html on port 3000 to incoming requests.
Finally in order to run this to make sure it works:
This screenshot of the folder structure in sublime and also the commands run at the terminal should give you an idea of what you should end up with.
Part 2 - Authentication
The next step in our framework is going to be authentication. When a user connects to our game we want them to be able to first login and register.
We are going to use express to setup an API at /user with endpoint for register, login and logout.
These will make calls to a UserManager which uses Mongoose/MongoDB to authenticate, or register the user.
On our client React app we will have an interface with forms for register and login which will make API calls to the server. When the user logs in or registers successfully, on the server we’ll create them a JWT token, which is sent back to the client and stored. This will be used in subsequent communications to authenticate them.
So first, lets setup the server side API. We’ll start by creating some new folders within /src/server
Before going further I am going to create a logger as we are going to want to see logs of events that
happen on the server. We are going to use winston to do this.
Note: Through the subsequent chapers you will notice at times the logging appears incomplete or just wierd - this is due to being added in later on but then moved up here in the walkthrough.
So we’ll create /src/server/logger.js with the following code:
This will create a log file error.log which will contain purely errors in order for us to see them more easily should they occur, and another log file combined.log which contains everything. You’ll see this being used further down.
We also want to create a .env file here also… As you’ll see in our initial dependencies list we installed dotenv. This lets us set environment vars in a .env file on the root, which are then read in to be used by referencing process.env.variableName. We’ll use this for storing a few things such as our mongodb address, port and things like our JWT secret which we’ll come to in a bit. Below are the vars you need, with some example values which will need to adjust accordingly depending on your db.
(This needs to be in the root, the same folder as package.json and README.md)
Next we can create our User model, we need mongoose, and you’ll need mongodb installed. I used a local install of Mongo DB version 4.0.3. We also install bcrypt for our password hashing.
For our User model we will start with the following in /src/server/model/user.js
We’ll add more to this User model later but for now this code gives us a user model with our username, password, status and datecreated with a hook on the save method which will has the password before saving. We then have a compare method on the schema for checking a login attempt against the hashed password.
We’ll now create our UserManager.
This is where we will do all our user related activity, in both our api and also later on in our game server. This will use the UserModel we just created. We’ll create this file in /src/server/managers/userManager.js
You’ll see we have three methods loginUser, logoutUser and registerNewUser all of which return a promise back to wherever called it. This ensures nothing is hanging around waiting whilst our queries occur. We also import our logger, and have some temp error messages in there. Our logging occurs simply through importing the logger and then calling logger.error or logger.info, with our message being written to the appropriate log file. The logoutUser doesn’t really do anything yet but will later on.
The method verifyActiveToken is not implemented yet but will be where our code can add the JWT to a list so that it can be blacklisted after the user logs out.
You will also notice some odd very error handling/throwing going on in the above and the code that follows. This is temporary and is going to be reworked later on.
Next we’ll create our controller for our API which will make the calls to our UserManager. This is going to make use of JWT tokens (you can read about these in my prior React/NodeJS tutorial or google so I’ll skip the explaination here) so I’ll install that first.
Then within /src/server/controllers , create the file authController.js with the following code.
So in there you’ll see the controller had 3 functions, login, logout and register.
The login function takes a username and password as arguments, and a callback and then makes a call to our UserManager’s login function, passing in those args. You’ll see we then do “.then(“, which means the returned Promise that our UserManager login method returns, has completed successfully. It then calls the callback passing in an object that contains a list of variables such as userid and username, and critically the JWT tokenID, which is signed using the userid, username and our JWT secret which we set in our .env environments file. If the UserManager login method fails to login the user in it will return an error which is caught by our “.catch” (I am going to alter this at somepoint to an alternate solution), and the callback is fired with “success: false”.
Logout does not really do anything beyond call the logout method in userManager (which does not do much yet beyond return true) and register is similar to login except it is calling the userManager.registernewuser method, creating a new user and then just like with login we create the JWT tokenID and return it in our callback.
Continuing on we now need our routes file which calls the above controller. Within /src/server/routes , create the file user.js with the following
In the above file we have 3 posts: ‘/logout’, ‘/login’ and ‘/’ (our register). These make calls to the methods in the authController we just made. Upon a successful login or register callback we return a status 200 back to the client and pass back a structure containing the JWT tokenID, username, userid and a status.
One note about the /logout route. You might notice it uses “req.userData.userid” and “req.userData.username”. This does not exist yet. This is a structure which our authentication middleware will pass in which will contain the userid based on their JWT.
In order for this to happen we will next create this middleware. In /src/server/middleware/ create the file authCheck.js with the following code:
This middleware code takes the JWT, decodes it, and then extracts the users userid and username and places it into the request data for our further routes to use. This helps to ensure the user is who they say they are.
You’ll note the use of UserManager.verifyActiveToken - that would be where our yet to be implemented JWT blacklist was checked against. This would help stop anyone reusing someone elses JWT after they log out if they were in some way able to get hold of it.
The final step is now to update our /src/server/main.js file to take into account these changes.
You’ll see we setup our routes on /user and prepare our mongodb connection ready for our game server to be intiialised later on.
We now have a functioning API for registering and logging a user in. The calls can come into our express server, route into our router user.js, which then calls our authController, this in turn calls userManager which checks against/registers to our mongodb. authController takes the result, generates our JWT if needs be, returns the result in an object to the router which then sends the result back to the client.
We now need to build our client which will make the calls to our API.
The next few steps will add quite a few things in. We are going to integrate in react redux. Our store, reducer and actions. Some of the styling here for the UI stuff here is a bit hacked together.
First for our redux structure we’ll create 4 folders, /src/client/actions, /src/client/constants/, /src/client/reducers, /src/client/stores.
We need to install a few things here for react redux
I then installed reactstrap. As a lot of the UI in what follows is a bit of a bodge and if you don’t use the same version numbers I used it is possible you may get issues.
To install what I did was:
( There are some instructions here that might help if you have problems: https://www.npmjs.com/package/reactstrap )
Beginning with our redux setup, within /src/client/constants/ create the file actionTypes.js with the following.
Next within /src/client/reducers/ create the file worldReducer.js. In this app we are going to only have a single reducer for everything.
The above implements our three actions, storing the username, userid, loggedInStatus.
You’ll also notice we default our initial state to include items in local storage. This is where we are going to store our JWT returned from the server and our returned userid. This will assist us if the user refreshes the browser by retaining the token so that it immediatly loads them back into the game without needing to log in again.
Next within /src/client/actions/ create the file actions.js as follows:
The key things in there are our loginUser, logoutUser and registerNewUser.
Each one dispatches “updateAuthenticationResponseMessage”, passing in a message in order to update the UI on an authentication box which we will build shortly.
They then do a fetch request against the API calling the relevant resource, gets the response, updates properties in local storage (or in the case of logout, deletes them), then dispatches a login/logout/register action and finally invokes “updateAuthenticationResponseMessage” to inform the UI with what has happened.
We now need to make our store. In /src/client/stores/ create store.js with the following:
With that in place we can now build the foundations of our UI which will dispatch the actions.
We will alter our ./src/client/app.js to be the following:
In there we have added in redux and our store and also you will notice we have imported the bootstrap css for our UI.
Next up we will create that new cmdshipClient.js in /src/client
For now don’t worry why that is called WindowSystem (It is going to form the basis of some mental draggable window UI later on).
We’ll make a UI folder within /src/client and then create windowSystem.js within /src/client/UI
This for now will just show our Authentication box.. which we will build in /src/client/UI/authentication.js with the following code:
(Warning: The HTML and CSS in here (and onwards) is a bit naff (as is usual with all html/css/front end I do) - In a later section it will get tidied up slightly )
You will see that the Authentication panel displays the authentication state message that we have in our model. You may notice the socket props - for now don’t worry about that.
At this stage launch and disconnect do nothing, and we will add those in next but for now we can test that our authentication panel works against the API
Incase anything about the above structure wasn’t clear, this should now be your folder structure:
If we now build and run this…
We should get the following:
and you should be able to register, logout and login
When logged in our client now has a JWT with which it can authenticate itself. We are now going to add socket.io to our server and also implement authentication on that socket connection using the JWT already created by our API.
This is so that when our players are sending messages to the game server we know who they are.
To begin this we will build the foundations of our game server by creating a game server class that will be loaded up when our server starts. When users connect they will be registered on the server and on disconnect they will be removed.
When a user connects they will be loaded into a Zone. A Zone is a world in the game. We are going to have a Zone for each star system, with the user moving between zones as they travel between star systems.
To start we are going to create a file in /src/common/ called constants.js with the following:
These constants are going to be messages which are common between both our client and server.
We will now make a user class which will represent the live user instance on the server. In /src/server/ we’ll create the file user.js with the following code:
The user object keeps a reference to the users socket and some other data such as their userid, the ID of the zone on the server they are in.
Then we will create our Zone in /src/server/zone.js as follows:
Now we’ll create this zoneManager… in /src/server/managers/zoneManager.js
It’s quite straightforward, just a collection of zones and a means of looking up a zone, creating a new zone etc
As you can see each zone has an id and a name. Each zone will have objects and users within it.
The zone has a reference to the global zoneManager and userManager (which we will setup shortly) of the server. When a user joins the zone they are added to the ‘registeredUsers’ array, and upon leaving they are removed.
An example of how this will work is that, given each Zone is a Star system, as a user travels between zones using ‘stargates’ (which we will build later), the user is registered and removed from zones, but all throughout this time the single global userManager will keep their data.
So the userManager keeps track of all users on the server and the zone keeps track of only the users within that particular zone.
Now we will make some changes to the start of /src/server/managers/userManager.js - all the way up to the loginUser method.
You will see we have added a user collection, and also that the init method takes a reference to the zoneManager.
loadUser looks up the users details in the db and if found creates a user object (which we just previously wrote)
and returns it ready for registerUser to be called which adds it to the collection of ‘online users’.
unloadUser does the opposite, this would be called on an event such as a user disconnecting. Given a userid it looks up the user, finds what zone they are in, removes them from it, and then finally removes them from the global user collection.
With that all done we can integrate these zones and users into our server. But before going further we need to install socketio-jwt first. It seems this is a bit out of date but it does the job we want.
Now we will finally create our game server. In /src/server/ create cmdShipServer.js with the following:
You’ll see the constructor takes an argument io, this is our socket server. main.js is going to create our gameserver and pass in this arg. In the constructor you will see we first create an object containing some systemStats, this is going to be information on the state of the server such as whether it is online/offline/initialising. We are going to have it so that our clients can request this information via the API so that it is displayed on the authentication screen.
We then create server wide instances of userManager and zoneManager, and then initialise them, passing each a reference to the other (there are probably far better ways of doing this obviously).
Following that is the socket code..socketioJwt is used to check that the client socket is connecting with a valid JWT (which the client should have recieved when they logged in via the API).
I’ll break down what is happening there:
So on a connection which has been authenticated.. first check if the server is online (using this.systemStats.status).
If it isn’t.. we send back a message to the user (via their socket using .emit) - telling them it is either offline or starting up, and then we disconnect the socket.
if it was online.. it proceeds, logs out some debug info, and then sends a CONNECT_READY message to the connecting client
When our client receives this CONNECT_READY - it is going to send to the server a ‘INIT_HANDSHAKE’ message (We will code this shortly)
The above code is the handler for the server recieving that INIT_HANDSHAKE from the client. It returns back the current time on the server (Our client needs to know the time on the server for keeping things in sync and determining lag).
When the client receives this response (and timestamp).. it will finally send in a LAUNCH_REQUEST message.
The above is the handler for that LAUNCH_REQUEST message from the client. This is the stage which loads the user into the game.
Using the JWT it calls our loadUser method in userManager (which if you remember pulls out the users data), it then registers the user with the userManager before finally calling userLoad.
userLoad gets the users zone (for now there is only 1), and then passes the user to the zone telling it to load the user into it (For now zone.userLoad does nothing other than register the user as being in the zone)
Finally we have our handler for a user disconnecting. It looks up the user using the JWT, then invokes our previously written userManager.unloadUser method which looks up what zone they are in, removes them from it, and then unloads them from the global userManager.
Also within the cmdShipServer you will see the init method. This currently only sets up 1 zone and initialises it. Later on this will contain multiple zones. As loading a zone will involve loading up persistant objects into it such as space stations, it may take some time. For this reason we will place the promises from the init methods into a collection, and then using Promises.all we wait until they are all ready.
Currently the start method does nothing, but later on this will be where our gameLoop runs.
Finally we need to alter main.js to launch and setup this game server - In /src/server/main.js we modify as follows:
So… main.js has changed. I’ll step through these changes - first, instead of just being an express server delivering an API, we now created a cmdShipServer passing in socketIO.
Beneath this you can see our promise chain whereby after our mongooseDB has successfully connected, we run an init method on this cmdShipServer (which we just covered - the zone init methods), which will setup up our game server, when this promise returns successfully we then run start on the cmdShipServer.
You might also spot this:
This now lets clients call /status on the server in order to pull down the online/offline state of the gameServer
Ok that is the server parts updated… Next the client needs updating so that it makes a socket connection to the server and sends the required messages, we’ll also have our client make use of the new getSystemStatus abilities of the server so that when our user is logging in, they know whether the server is operational or not.
We’ll begin by altering /src/client/constants/actionTypes.js to have our new actions, as follows:
Now in /src/client/actions/actions.js we are going to add some methods:
(remember, … means I have excluded the existing unchanged code)
Now again in /src/client/actions/actions.js we will makes changes to loginUser and registerNewUser
These changes above are that we imported our common constants, and then added some new actions.
You will see many of these are for the messages we created handlers for on our server such as INIT_HANDSHAKE and LAUNCH_REQUEST.
We also added the getSystemStatus API call so our client can get the state of the gameServer. commandLogReceived was also added - this is going to be part of a client side log of messages.
You will also notice we made a slight change to loginUser and registerNewUser, it is the same except that on a successful login/register we also invoke ‘dispatch(getSystemStatus());’. This gets the latest game server status once they are logged in/registered.
After that we need to make some modifications to the reducer in /src/client/reducers/worldReducer.js - which can be changed to the following:
All of that is quite simple. We added some elements to our state, and handled their updating, such as our commandLog text string, the systemStatus, the timestamps from the INIT_HANDSHAKE and also the setting of connectionStatus.
Next we will install the socket.io-client which will allow us to open a socket connection to the server.
The next part is going to be one of the key parts of the client. The client side game world UI. For now this is going to contain just some basic code for handling the connection but will be expanded on later.
In /src/client/UI/ create the file gameWorld.js with the following
The initSocket method you can see above is the client side version of the socket connection method we earlier went through on cmdShipServer.
It emits an AUTHENTICATE message to the server, passing in the JWT. It then handles the CONNECT_READY message that comes in from the server, which fires an initHandshake action.
Finally the LAUNCH_REQUEST will be fired via the handshake response handler in actions.js.
To begin to integrate all this together we will make some alterations to /src/client/UI/windowSystem.js which can be changed to the following:
So this creates our socket, and then passes it into our components as a prop. In our render method we have some code which will either show the Authentication component, OR the gameWorld component we just created.
Which is shown is based on whether the user is logged in, and also the connectionStatus. We are now going to alter authentication.js so that when the user clicks launch, it fires an action which alters connectionStatus to ‘connecting’, and thus the screen should change to our gameWorld - which will fire off the socket connection code in componentDidMounth within gameWorld that we just wrote. (Note that the socket i create in WindowSystem has autoConnect set to false - this is because we want the connection to open when screen switches to the gameWorld component)
We’ll now make alterations to /src/client/UI/authentication.js as follows:
You’ll see we have updated the handlers for launch and disconnect so that they now call the new actions.
We also added in the system status information, with the component invoking the API call via getSystemStatus() in componentDidMount.
If we now build and run all this…
If we now build and run this…
You’ll see the server is starting up, with main.js successfully connecting to mongoDB and then initialising our gameServer and one zone.
If you then log in..
You’ll see our launchpad now has the gameServer status and time.
If we then hit launch…
You can see from the logging that our client successfully connected and authenticated using the JWT, did the handshake process, had it’s user record loaded in from the db using userManager and then added to the zone.
If we now hit refresh on our browser, we can now see what happens on disconnection…
As you can see the client loaded back in at the launchpad, the socket handled the disconnect with the userManager then unloading the user from the Zone and also from its collection of global users.
To recap so far…
We now have a gameServer which is started up in main.js, it has a UserManager which manages and holds a collection of all users on the server. The game server also has a zoneManager which holds a collection of Zones (each representing an area in the game universe - currently we have just 1).
A user logs in via REST API - recieves a JWT. They then connect to our game server using that JWT and when a user successfully connects they are added to the UserManagers collection of online users and also placed into a zone.
We are now going to add in some stuff…
First we need to send some game config to the client after it has connected. In /src/client/actions/action.js add the following two functions:
This is the method which will call our API to get the config data, and also the action for receiving it.
We’ll now update /src/client/reducers/worldReducer.js so that it stores this config data:
This adds configData to our initial state, and also updates it on recieving the action.
Next we need to add that action to the list in /src/client/constants/actionTypes.js
Now we need to integrate this into the client application. Ideally this should take place before the user can click launch so that we have the game data ready.
What we will do is at it in three places. The first when our Authentication box loads incase the user is already logged in, and then again when the login action returns successfully, or register is successful.
The line added is ‘dispatch(getSystemConfig());’, at the end of the promise response once the login/register fetch has returned.
THe next change is in /src/client/UI/authentication.js - alterting componentDidMount() to be the following:
The change here is that we added a dispatch for getSystemConfig at the end of componentDidMount - this will fail if they are not logged in and should only dispatch if they are logged in but for now it will do.
That is the client side of this done, we now need to implement the server side.
We’ll begin by creating our config file in /src/common/game.json, with just some basic things.
Next in /src/server/controllers/ create gameController.js
You will see it imports in the gameConfig, and then returns back the two parts of it in the function. The reason for this is that this gameConfig will later contain other things that we may not want the client receiving.
Next we’ll make a new route. In /src/server/routes/ create game.js with the following:
This one is another straightforward one, it calls the gameController, gets the config in and returns it back. You will see we used Buffer, you’ll need to run the following code to install that
To make it work fully we then need to alter /src/server/main.js to import the gameRoutes and then use them as follows:
We are now going to create a config manager which will access and use that data on the server.
In /src/server/managers/ create configurationManager.js as follows:
All this does is return elements in the game config although currently we don’t have much data in it so don’t worry too much about that for now.
Next we will initialise this with our game config in our game server. In /src/server/cmdShipServer.js make the following changes:
In the above there are three changes. We imported the ConfigurationManager and the gameConfig json file, and then within the constructor i ran init on the configurationManager passing in the json game data.
We now have a lot of the server framework in place. main.js is finished.
We are going to next expand our clientside gameWorld. In /src/client/UI/gameWorld.js
The changes above are that we added a “cam” structure. This is going to store where we are within the current game world, acting like a camera. We created some variables in the constructor that we’ll use later and in componentDidMount we get the context of the canvas we have added into the render return method and also we created some event listeners for our clients update (though currently the handlers are empty). We also added our client side game loop to the start method using “window.requestAnimationFrame”. This loops calling update which for now just clears the canvas and runs draw (which currently draws nothing).
Later on this draw method is going to draw all the objects the client currently knows about and all the effects going on.
We will come back to this in the next part which is the construction of our game world world.
It should all build and run at this point but for now you won’t see anything on the gameWorld
Create the Schema which represents our world objects so that they persist in the DB
Create the classes which define our objects when they in memory and hold their state
Create an Object Manager which runs in a Zone - this holds the objects in memory for that zone
There will be 3 versions of an object. The persisted copy in the db, the object in memory on the server when it is within the game world, and also the representation of that object on the client.
We will start by creating the db model schema of our world objects.
In /src/server/model/ create the folder /objects and then within that create the file worldObject.js with the following:
For now don’t worry about what everything in there is for. This is the objects persistant state.
Info such as it’s last X,Y position, it’s angle, the zone it was in, it’s type, whether it is docked in a base or not etc
The baseOptions structure that contains the discriminatorKey ‘itemType’ allows us to create ‘sub schemas’ from this.
In /src/server/model/objects/ we will next add our first object by creating ship.js:
Here we have created a Ship schema, which is based on our WorldObject schema, but with one extra property dockedUserID which will contain the userID of the player currently occupying that Spaceship.
With the DB aspect of a world object in place, now we will add in some code which loads the object from the db, into an object within the game server. An example of this happening is when a user connects.
In this game the user will always be in a spaceship of some kind. The idea is that the player is piloting a small capsule (similar to pods in Eve), these can then dock/board with spacecraft and the user assumes control of that spacecraft.
The capsule we will call a PTU (Personal Transport Unit). This will be the smallest type of vehicle (Although it will be of type Ship, when speaking of Spaceships we mean all things that the PTU can dock with). When the user boards or docks with a spacecraft, the PTU will load into the spacecraft. From a code perspective, what will happen is that upon the user chosing to ‘dock/board’ a vehicle (If they have permission or capable of doing so), the PTU Ship object they currently control will be unloaded from the Zone, its location changed to be inside the new vehicle and persisted to the DB, the user will then have their ‘current vehicle id’ changed to that of the new vehicle and from the perspective of the player it will look like their PTU docked with the new vehicle.
For now don’t worry about all this too much because we are going to have only a single type of ‘Ship’ to begin with - that being the PTU, the small capsule the players control.
We’ll now do the sections of code which define the player objects and then load the players spacecraft from the DB into game world as those objects.
In /src/server/ create a new folder ‘gameObjects’ , and within that create the file spaceObject.js
Again, some of this isn’t created yet such as the zone oms (Object Management System) so don’t worry about what it is. The key thing to know is that the above is the root class from which all objects in our game world extend from.
The important attributes within an object are:
objectID: the unique id of the object - which is their generated id in the db
typeID: an integer which is the type of object
These properties will go in a core structure along with other things such as the position and angle.
Now we will create a intermediate class BoardableEntity which will extend from this SpaceObject class.
In /src/server/gameObjects create boardableEntity.js as follows:
This class is going to be between spaceObject and ship and extends spaceObject and some methods which allow a user to ‘dock/board’ a vehicle and control it.
Now we will create a ship which extends from this boardableEntity class. In /src/server/gameObjects create ship.js
For now our ship does not have much in it, just the basic core values with some keyinput data and movement data (both of which we will use later)
We will now add the PTU to our game config file with basic properties such as its title and type. We’ll also add some item categories:
When a user connects, their spacecraft is loaded from the persisted copy in the db (/model/objects/ship.js). An object is then created on the server which represents this within the game world (/gameObjects/ship.js). There will also be other world objects such as Asteroids and spacestations which are loaded up when the Zone is loaded. All of these objects will be stored in the zone in a collection.
We are now going to create a system so that a Zone can manage these objects in memory.
In /src/server/managers/ create objectManager.js
This will be expanded on later but for now it is mostly about methods for managing the worldObjects array which is a list of the space objects in the Zone. Methods for getting a SpaceObject in the collection, adding and removing.
There are also 3 other new methods in there that we will use shortly:
createShip - this is for creating a new ship object in the DB
loadShipFromID - given the _id of a ship in the db it will load and return the record if it exists
instantiateShipFromRecord - given the db record of a ship it will create and return a new ship object based on it
We will now have /src/server/zone.js instantiate a new ObjectManager in its constructor
The two changes were, we imported the objectManager, and then in the constructor instantiated it as the instance variable oms, and passed in the zone.
We need to take a detour here in order to give each user an initial spaceship. First we will extend the user schema to include id’s for their current spaceship and also their ‘PTU’
The changes above are that we added three new properties to the user model. dockedVehicle - which is the id of a spaceObject, and ptuVehicleID - which is the users PTU ‘pod’ id. When the user is in not in a vehicle and is flying around just as their PTU, the dockedVehicle attribute will be the same as their PTU id, and also zoneid - which we will use later on.
When a user joins the server and a user object is created in UserManager, we need to populate their user object with this data from the db record. In /src/server/user.js add the following changes to the constructor:
With the above in place, the ptuVehicleID, vehicleID, zoneid variables will be set from the userDocument object when the user object is created.
Next, When a user registers we will create a new spaceship in the DB for them. Change /src/server/managers/userManager.js as follows:
The two changes in the above are we added a new method ‘newUserSetup’, which creates a new spaceship for the user.
We then made a change to the end of the method registerNewUser, removing the existing resolve() and instead calling this new method to setup the users spaceship.
Next back within /src/server/zone.js we need to update the loadUserIntoZone and the unloadUserFromZone method:
The loadUserIntoZone method will add the user to the collection of users registered in the zone, pull the users spaceship record from the db, instantiate a Ship game object, set the user as being in it (docked with) and then it adds it to the OMS.
unloadUserFromZone will do the reverse and remove the object from the OMS.
To test this, I will register a new account…
I will then connect and see that my ship is loaded into the zone OMS
And upon disconnect the reverse happens
We now need to make a change in order that both our user and and spaceobject has the ability to persist its attributes.
Our object persistance is going to work by using a boolean flag on spaceObject. When we want the object to persist its data we set this flag to true. In our zone’s game loop at the end of each loop we will then get all the objects which have been flagged as needing updating, and then we will call a persist method which the sub classes will overload.
We will start with altering /src/server/gameObjects/spaceObject.js
The changes here are that we have added the ‘markedForPersistance’ property to the constuctor, and then added two methods doPersistanceUpdateCheck (which will be called by the zone) and ‘persistObjectData’ which will be overridden by the sub classes to include their specific properties.
We will now implement ‘persistObjectData’ in /src/server/gameObjects/ship.js
We will expand which properties are persisted later but for now it will just be basic position info, the zone and which user is docked in the vehicle, and the two attributes which determined if it stored somewhere.
We need to do another critical detour to make this work. This is going to be the update method of our space objects. When the game loop in each zone runs, it will iterate over all space objects and fire an update method on them.
So we now have our game server run method, which invokes the ‘run’ game loop on each zone.
We will now add in this “this.setPersistanceMark(true)” on the space object whenever we want it to persist. For now we will do this in two places,
when setting an objects zone and also setting its occupant.
On zone change or occupant change, our vehicle will now persist that info to the DB.
Now we will implement user persistance:
We added new collection ‘usersAwaitingPersistance’, this will store the users that need to be persisted across the server.
We’ll then alter our main server run loop in /src/server/cmdShipServer.js to make use of these methods:
Our server loop now gets the users on the server awaiting update and persists them. Whenever we want to update our user, such as when they change zone, get in a new vehicle, dock in a structure - we will fire this method on the user ‘addUserToPersistanceQueue’, currently we won’t yet use it.
Now that we have user and object persistance, we will go back to the management of our objects in the Zone.
These zones are going to be big with it taking a long time to travel across. We don’t want users to aware of all objects in the Zone and only want them aware of things in a local area.. because of this we are going to break down a Zone into a grid. When an object moves across the Zone it will be registered to a grid and as it moves into the next grid box it will then change its registration. A benefit of this is that it will reduce overhead when the server wants to do things such as check collision detection, with it being able to not need to check against objects we are not nearby.
We’ll begin by adding some config for our grid sizes to /src/common/game.json
We’ll then add a new property to our spaceObject which keeps track of what grid the object is currently in:
In the above we added the single line, ‘this.registeredGrid = null;’ to the constructor
We’ll now create a new manager in /src/server/managers/ called gridManager.js
This gridManager for now only tracks objects but later will include those observing the world. Each cell in the grid has a width and height (this.width, this.height).
Each element in the grid is stored using a ‘grid key’ - this is the coordinates of that cell within the grid. So an object at x:100, y:100, within the grid (based on our current settings our grid cell dimensions are 1500 by 750) would at grid with key “0_0”. An object at x: 1501, y: 100 would be in cell “1_0”.
We then store objects within our grid collection using this key in a collection ‘objects’ - this.grids[gKey].objects’. Most of the methods are for managing objects adding and removing from this grid collection.
Some other important methods are:
calculateGridSurrounds - Someone observing the world will be aware of their current grid at their x,y location, and also all adjoining grids. Given an X and Y, this method returns the list of all 9 of these grids.
addObjectToZone - adds an object into the grid
objectPositionChange - this method will be called whenever an object moves. It’s purpose is to check to see if an object has changed grid through its movement. Currently it won’t be used as nothing in our world yet moves.
Next we’ll integrate this into our OMS at /src/server/managers/objectManager.js
The five changes above are that we imported the new gridManager, then in the constructor we instantiated the gridManager, and then we integrated the addObjectToZone, removeObjectFromZone methods into the add/remove methods of the OMS.
With this in place our object will now be added to a grid when it is added to the oms. We also have a method for calling our objectPositionChange within the gridManager.
We will make another change here in /src/server/gameObjects/spaceObject.js, placing in our OMS updateObjectPosition method.
We will continue on now onto how players observe the world.
With the objects in the Zone, assigned to a grid, we need some way of the player seeing into the Zone at a particular position (like a camera point) and being updated of the state of objects around that point.
We will do this by extending our grid system so that aswell as objects registering themselves with a grid, a ‘watcher’ will also register with a grid. A watcher will be a kind of listener or camera, which is notified of objects add/removing from their current grid and the adjoining grids.
When a player joins a watcher will be created and assigned to them, and the registered to the current position of the spaceship they control. When the spaceship moves the occupants watcher will also be moved. Then as the users spaceship moves across grids, the watcher will also move and the user will recieve the appropriate add/remove messages.
To begin we need a means by which to message users. We will start this by adding some methods to /src/server/zone.js
For now the above methods are quite straight forward, sending a ‘cmu’ command to the user. cmu commands will for game events. In the message we also include the time, which will be critical later on for making things appear to occur smoothly.
We now need to return to the client.. and build in a means to recieve and process our game zone messages.
So you will see we have added an inbound and an outbound message queue which is processed on each run of the client side game loop. You will see it also checks the time of the inbound message before processing, using the passed in event time. This is because we are going to have it so that the clients are always slightly behind the server to ensure things run smoothly. In some games this would be impossible such as FPS type games, but due to the nature of the game mechanics in this game, we can do it.
As it processes the inbound message queue they are sent to ‘inboundMessageParser’ which will contain case statements for our different CMU events such as add/remove objects - which we will add in shortly.
Next back on the server we’ll create a new file in /src/server/ called watcher.js
For now don’t worry too much about what some of this will be for. Later on this watcher will be potentially for NPC’s also and not just users.
The important things are that it records the registeredGrid (as with spaceObject), the owner (the user) , and the zone it is in. There is then a method which will broadcast to the owner a given message.
When our clients recieve messages from the server such as for adding objects, they need to know what they are composed of - their specification:
To do this will have some ‘getComposition’ methods - for now we will just have one which returns some basic details on the spaceObject such as it’s id and type, we’ll then add one to ship.js which returns the same but also the docked user id.
This allows our server to call .getComposition() on any spaceObject/ship and recieves its makeup - which can then be sent out the clients.
Moving on to our client messages - we need to add them to our constants:
ADD_OBJECT - this will be called when a client needs to be told that a spaceship has come within range (entered the current grid or adjoining grids) and the objects composition sent with the message.
REMOVE OBJECT - this will be a remove for a single object , send with the id of that object to be removed on the client. This would be sent when an object is no longer visible to a client.
REMOVE_ALL_OBJECTS - wipes the clients current list of visible objects
GRID_CHANGE - this is for when the client moves into a new ground, a list of ADDS and REMOVES is sent for updated client visibility.
Next is quite a big change to /src/server/managers/gridManager.js - if it is confusing it might be worth comparing the differences between old and new gridManager to understand what is going on. I will attempt to explain it after the code:
Quite a lot changes in there compared to the previous version.
The first thing was that wheras before at each grid key there was just the “.objects” collection. We added in “.watchers” - which stores which watchers are located in that grid.
objectPositionChange - When an object moves and in the process moves to another grid. Anyone who needs notifying of a removal is sent one, and those needing to be notified of an add are sent one of those.
addObjectToZone/removeObjectFromZone - This adds/removes objects and notifies watchers of those events
updateVisibleObjectsForWatcher - This is used to generate the gridchange message when a watcher changes zone. The adds and removes being worked out and sent to the watcher.
We now need to add in some methods to /src/server/managers/objectManager.js for a user registering themselves as a watcher in the OMS, and for leaving, making use of our new gridManager code.
Now we will modify /src/server/zone.js to use these OMS methods
The two lines added were one each in unload and load, this.oms.userWatcherJoin(user, shipObject.core.x, shipObject.core.y); going between the ‘setDockedUser’ and ‘addWorldObject’ methods, and this.oms.userWatcherLeave(user); going after ‘removeUserRegistration’ in the user unload.
Now we need to switch back to our client and add in the handling for our new server messages.
Alter the inboundMessageParser method in /src/client/UI/gameWorld.js as follows:
If we compile and run this on the server and the have our client connect, in the client console window we should see the following:
So to summarize this section, we created the ‘Watcher’, we had it so a watcher can be created for a user when they join. Watchers can be assigned to grids just as objects are. When a user joins they are effectively subscribed to add/removes that happen on the grids surrounding them.
Now that we have messages being recieved on the client, we need to create a visual representation of those objects on the screen but to begin that we need to build the foundations of our game world engine.
We are now going to start on our game world engine which runs within each zone on the server, and also on the client.
First we will make some objects on the client.
Create the folder ‘entities’ in /src/client/ and then within /src/client/entities create the folder ‘objects’. Finally within /src/client/entities/objects create the file spaceObject.js with the following:
This is the client side equivilent of the spaceObject class on the server. Next we’ll do the ship. In /src/client/entities/objects/ create ship.js
The game world engine on the client is going to share code with the game world on the server. This is so that the events happening on the client are the same as that on the server (albeit slightly delayed). The server is going to send messages to the clients keeping them updated on the change of state of objects, and as the ‘world code’ both the server and client run is the same it should in theory mean they are doing the same thing. I’ll explain this more clearly as it comes together..
For now I will create a skeletal version of this engine.
In /src/common/ create gameWorldEngine.js
The above common gameWorldEngine will be from what our server and client gameEngines extend from.
Next in /src/client/ create clientWorldEngine.js with the following:
This client side world engine for now is just holding our game objects that the client is aware of. If the object added is our players ship then we set a reference to it.
Next in /src/client/UI/gameWorld.js we need to integrate in our new client engine.
In the above we created our client game engine when the component mounts. We added a doRun flag to the constructor for pausing the game loop if needs be. We then altered the inboundMessageParser to make calls into the game engine for each of our recieved object messages.
Next we altered update to run our game engine. We then altered the draw method to run on each object within the game engine.
Now we will create the server side game world engine in /src/server/serverWorldEngine.js
As you can see this extends our common engine, similar to the client. Now we need to setup the engine in our zone.
The change there is that We setup the world engine in the constructor, and for now directly pass in our OMS object collection.
In the run method we added a call to the run method of the world engine inserted before setting the lastTimeStamp.
We can now do a multiplayer test of this to see that the ship add/remove messages are coming in.
Next open up two different browsers, run localhost:3000 in each. Create a new game account on each. Then launch just one of them:
You can see the single ship, and the console debug output on the client. Now we will click launch on the 2nd client..
The second ship now appears on the 1st client (both are not visible on the 2nd client due to it being off screen). Now we refresh the 2nd client to make it disconnect and take it back to launchpad…
And there are the messages on the 1st client removing the 2nd ship due to the 2nd player disconnecting.
The server should have the output along the lines of the following:
Next.. we are going to now add some movement using our new world engine.
Part 7 - Game Engine Movement and collision detection
You may have seen the following variables on ship.js within the ‘core’ data structure: keyH, keyV, keyS.
Using these is how our game movement will occur. They each represent a pair of keys onkeyboard and have 3 potential values.
keyH - rotating left or right, or neither (A and D)
keyV - accelerating forward, reversing, or neither (W and S)
keyS - L and K (these are not for movement but for modules such as moving a turret and will be used later)
Each ship/spaceObject has a “step” method. This is run on each run of the game engine, we our is to make the actions taken on the step on the server spaceobject’s, be the same as the actions taken by each clients spaceObjects.
This step method is given a time in ms, the object will then this amount of time worth’s of actions and movement. This will become clearer later.
When a player presses down a key on their client, the key press will be sent to the game server, added to an input queue on that players ship with a timestamp. The keypress will also at this point be dispatched to all nearby players. As it arrives on those clients it will also be added to a clientside input queue for the ship.
If all works correctly both the server game engine, and all the client game engines that are aware of the ship (and the keypress) will then process this keypress in the same way - translating the keypress into a change of state of one those 3 variables above (accelerating/reversing/turning left/turning right etc), the clients being slightly behind the server due to our designed in lag.
We’ll add some more constants in /src/common/constants.js
In /src/client/UI/gameWorld.js we need to start sending input
In the above We changed the two key press handlers and added a new method sendInput. You will see we capture the key presses for A,S,W,D etc, then call sendInput which adds the event to the outbound message queue - ready to be sent to the server.
We are going to soon be ‘broadcasting’ to clients in a particular area, to do this we need to add some more methods in our zone OMS.
This broadcast makes use of ‘getWatchersAroundPoint’ within the GridManager, finds everyone watching that area and broadcasts whatever message to them.
We’ll now alter the common gameserver slightly so that it can handle input:
Next we need to handle these messages as they arrive on the server.
In the above we added a socket.on for MOVECMD to the end of the “io.sockets.on(‘connection’” handler. This routes the input into the users zone.
Now in /src/server/zone.js we’ll add this ‘onReceivedMoveInput’ method:
In the above we added some queue variables to the constructor, added some new methods for handling it and receiving the input. You will also see we added the processing of the input queue to our zone run method.
At this point our messages are coming in from the clients and being pushed into the serverGameEngine on the zone which invokes the run method in the common game engine.
We also need to create the method ‘addToInputQueue’ on /src/server/gameObjects/ship.js
In /src/common/constants.js we’ll add that shipKey constant
So with that working, when our a ships key presses come into the server they will added to the queue for that ship, and within the above method ‘addToInput’ queue, the key change will be broadcast to those nearby also using ‘broadcastInRangeOfObject’.
Now back to the client and add the handler for that shipkey message, so in /src/client/UI/gameWorld.js add the following to the inbound message parser:
So just as our server added the ships keypress to its input queue (and then dispatched to all clients), the client now also recieves it (if it is in range) and adds it to its world engine queue.
Next we need to begin processing these input queues:
Both our server game engine and client game engine extend from the same common game engine in /src/common/clientWorldEngine.js, it is in here we will now build the input processor:
If you remember the important aspect of our movement is the ‘step’ on each object (We have not yet written out these step methods). Our processor works out how long has elapsed since the last time it ran, up until the first input item in the queue and then ‘steps’ every object this amount of time. We then iterate over all inputs and apply them one by one moving forward through the input changes and then ‘stepping’ forward to the next input.
You will see ‘applyKeyChange’. Our inputs are applied to the ships using this method (it isnt created yet but we’ll make it next). It will take the input key press for that ship, and then alters the previously mentioned key settings (keyH, keyV etc) to alter its rotationSpeed, acceleration and so on.
So to summarise the processor, we go over each inputkey press, apply it. Then apply movement on everything up until the next keypress is due. Apply it, and then apply movement again and so on, up until the present.
Now we will add the applyKeyChange to our ship objects on both the client and server (as both will be called due to it being the shared processor code)
First in /src/server/gameObjects/ship.js add the following method:
Then on the client at /src/client/entities/object/ship.js add this method:
You will notice both are almost identical. Ideally further on we can refactor the server side and client side objects to have a base class so that we can share a lot of the code between the two but for now they will remain seperate.
Ok, so we have our input processor running, applying key presses to the objects. Next the important method - the actual step method themselves that do the movement.
As with applyKeyChange we need to do this on both client and server ships.
The code in the above two methods again is very similar and is some basic physics. If the move keys are down it accelerates forward/backwards by the step time and similarly with rotation.
You will notice some odd code on the server method on how it sets the position at the end.. this will be clearer why later but for now can be ignored.
Also you see we are using hardcoded values for speed/maxSpeed etc - this will be changed to none harded later once we add in ship modules and begin using the ship spec.
And if you now build and test that.. you should be able to use the W and S keys to move forward and the A and D keys to rotate. If you connect two clients to your server and move on them you should see them move accordingly on the other client.
At this stage we finally have some kind of multiplayer thing going on. Most of the key components of the server and client are now in place in some capacity.
We will now add in some collision detection, to do our collision detection we will use the following sat package: https://www.npmjs.com/package/sat
We are going to use this to test polygon against polygon. To do this we need to give our objects a shape polygon.
So you will see we have defined 3 verticies for our ship polygon (which as is shown on the client is a small triangle shape)
First in /src/server/gameObjects/spaceObject.js
In the above we are imported some shape stuff from sat. We then have three methods. The first is ‘getCurrentCollisionBounds’, this gets our ships current polygon based on its position and rotation.
‘getCollisionBoundsAtPositionRotation’ returns a structure containing a polygon proposed at a proposed x,y,angle. Notice it also returns a type as hull, this is because later we are going to have shields on our ship which it may instead collide against.
‘getObjectPolygonAtPositionRotation’ creates the polygon, using the shapes in our specification (game.json itemtype entry for the ship), and then rotates it given our ships present angle.
Now in /src/server/managers/objectManager.js:
In the above we imported sat, in our overlap check we then got all nearby objects, looped over them and tested against every other object whether the polygons collided.
We now need to integrate in our overlapcheck, it will be added in a few places:
In gridManager we need to update the objectPositionChange method so that when a ship moves it checks to see if it can do the movement:
The changes in the above are that we altered the method header so that it also takes a proposedPoly argument. This is the new shape of object (as it may wish to rotate and potentially be in collision by doing so).
We then added the overlap check method call in two places, the first in the section with the gridChange, and again at the bottom of the method whereby no gridChange happens.
We’ll now alter the ‘updateObjectPosition’ in /src/server/managers/objectManager.js
And then alter the ‘setPosition’ in /src/server/gameObjects/spaceObject.js
Next we need to alter our ship step method to make use of this altered setPosition:
In /src/server/gameObjects/ship.js alter the end of the step method to the following:
This is all slightly mental but should work (setPosition probably shouldnt be the name). On a movement or rotation we generated the new polygon, call setPosition which tests the new polygon, potentially returns true/false.
You might notice that in this the x,y will be set twice, once in gridManager and once when it comes back here. That will be sorted later on.
You could test this now.. but you won’t see much going on, because your client won’t know of the collision.
Before adding client side collision detection we are going to use this as an excuse to now add an important feature into our server-client communications.
We will make it so that every x seconds the server sends out an update to the clients on the state of the world around it in order to keep things in sync.
First in /src/client/clientWorldEngine.js add the following new method:
This will take a block of ‘core’ data’s from many objects, and then update them all, which will keep their positions and rotations in sync.
Then in /src/common/constants.js add the FULLUPDATE constant
Now in /src/client/UI/gameWorld.js find the ‘inboundMessageParser’ method and add the following case for our fullupdate method:
Now on the server in /src/server/zone.js
We added a new method which checks to see if 10 seconds has elapsed, if so it gets all watchers on the zone, gets all the objects around that watcher, packs up their “core’s” and then sends them to the watcher.
We also altered the run method so that it calls this fullupdatecheck method at the end.
With this in place we can test our server side collision detection is working by getting two clients loaded. Flying one ship into the other.. on the server they will collide and stop. On the client however it will continue on moving until… the fullupdate comes in, snapping the object back to the collision point.
In the following 3 images you can see the before, 1. lining up to ram the other ship, 2. it appearing to have passed through on the client, 3. the fullupdate coming in snapping it back to a position of collision next to it
With that working we will now implement collision detection on the client
Some of these methods will look similar to those on the server:
Next in /src/client/clientWorldEngine.js
This gives us our collision detection check method similar to on the server (for now only testing hulls).
We altered the doStep so that it is given a reference to the engine, and then before setting the position and rotation tests using that engine. This arg will be used for more things later on.
With this change we need to make a couple of alterations so that the engine passes itself as an arg to step
Athough it isnt used yet, in these three places /src/server/gameObjects/ship.js and spaceObject.js, and /src/client/entities/objects/spaceObject.js alter the method header for doStep accordingly to take the engine arg also.
If you now test the ship ramming scenario again, you will now see that our client no longer passes through the ship but collides keeping sync’d with the server.
There’s a bit more collision detection we’ll do later but for now that is enough.
Currently we can send move and rotation messages to our ship but next we are going to add in the ability to give our ship and surrounding objects commands.
This is going to be a kind of command line interface between the player and the world.
We’ll start by create a sendCMD method on our client in /src/client/actions/actions.js
In /src/common/constants.js add the new constant
We’ll now add some basic handling of this on the server, In /src/server/cmdShipServer.js
As before with move, we added the handler for USERCMD message to the end the socket message handlers.
We’ll now implement the new ‘onReceivedInput’ command in /src/server/zone.js:
The above mechanism will form the basis for a lot of our players interaction with the game world.
Now we will go back to the client (Warning: Things may get even more mental in the following section)
Here we are going to build a draggable window system, one of which will be our command line interface.
To do this we are going to use a package called React-dnd - http://react-dnd.github.io/react-dnd/
How I have done this may not be the best way of doing it with react-dnd, but it works.
You may remember we made /src/client/UI/windowSystem.js - this will be where our windows will be contained.
We’ll have our store keep track of what windows we have, and have a means by which our WindowSystem can ‘register’ (create) a new window.
Our windows will have an id, a type, a title and positioning done with top and left.
In /src/client/actions/actions.js add the following action:
Next add the actiontype for that to /src/client/constants/actionTypes.js
Next in the reducer we’ll handle that action:
We did two things there, we added windows to initialState, we then added the handling of the REGISTER_WINDOW action which just puts the window into our collection.
We are going to need something here called flow for bunging our components together for export
With that done we can go back to /src/client/UI/windowSystem.js and change it to the following:
If you want to understand entirely whats going on there with the drag n drop code it is probably best to go through the react-dnd docs.
You will see how in the constructor we registered a new window, this will go into our store, and then get handled by the render method, creating a new window of type interface (which for now just has placeholder text but will shortly be our command line interface component).
You will see flow in action at the end of the file at the export.
An ‘interface window’ is going to be a window which is used by the app for the user to do interfacey stuff, as opposed to a window such as seeing what cargo is in your ship (which will later be an itemWindow).
In /src/client/UI/ create a new file interfaceWindow.js with the following:
Most of that is straight forward, it gives some basic window functionality such as a move drag button and minimise and maximise, and sticks a title on it.
You’ll notice we used some icons in there for our window such as minimize/maximize, we need to now add these in:
To use these in /src/client/app.js we need to add the following:
If you build and run the above you should have the following:
The first image shows our new interface window, the second after the minimise has been clicked, and third it being dragged.
Lets now build a command line interface to go into this:
In /src/client/UI/ create the new file commandLineInterface.js
This is just a textarea which displays our earlier created commandLog, an input, the content of which is sent to the server via our sendcmd action we made.
Back in /src/client/UI/windowSystem.js we can now add this in:
You will see we imported the commandline component and then switched our placeholder text for the component, passing in the socket.
Building and running that, gives us our commandlog and and sending the test message will show up recieved on the server.
The next change is going to be pinning the camera onto the users vehicle.
In /src/client/UI/gameWorld.js make the following changes:
If you now run this the player will be at the centre of the screen, but you will notice an issue, with no other objects around the ship it becomes impossible to know if it is moving. To resolve this issue we will create a starfield in the background:
In /src/client/UI/gameWorld.js make the following changes:
So in the constructor we generated a 2000x2000 ‘starfield’, randomly positioned dots of varying magnitude.
In our update method we call drawStarfield which wraps this starfield around the screen as the user moves.
Running that you should see something like the following:
At this stage we need to now expand the types of objects in our game world.
The first one is going to be a space ship, instead of just the basic PTU. We’ll then do board/unboarding of vehicles.
In /src/common/game.json add the following:
We will add some temporary code on the client for handling this new ship so we can see the difference:
First add a new constant in /src/common/constants.js
Now in /src/client/entities/objects/ship.js alter the end of the draw method (after the beginPath call) to the following:
Next in /src/client/clientWorldEngine.js find the addObject case statement for type 6 and change to the following:
To test this I will now create a temporary debug method on the server which we can call via our commandline interface to spawn world objects:
in /src/server/managers/objectManager.js we need some new methods - add the following two:
The first method uses loadShipFromID and instantiates a new ship object. The second does similar except also sets the objects position and adds it into the world.
You’ll notice the second calls methods that don’t yet exist ‘initShipBasedOnState’ and ‘getSafePositionForObject’.
We are going to take a bit of a detour here to add these in. The first is simple and for now will be empty:
For now that empty but we will expand it shortly, its purpose is to setup the ship depending on its last ‘position state’ as it may have been doing something when it removed from the world.
This ship state is stored in a property that you may have already seen ‘positionState’ which we added to the schema at he start. Position state may be the wrong word to have called it, it really is just ‘the state of the object’.
Before going further we’ll add a constant for one type of positionState:
Standard space means its just in a normal state.
The second method we’ll add is more mental and involves another detour:
There’s two added methods. The purpose of the ‘getSafePositionForObject’ is that given a point and an object, and it attempts to find the nearest empty space that fits this object (As obviously we don’t want things spawning ontop of other things). There are probably far better ways of doing that than the way i did it using my rogue maths.
The second method is a method for testing the sphere shaped area for emptiness.
Here there’s going to be another detour.. we want to implement this in other places - anywhere we are loading an object into the world such as when our ship loads and we’ll also add in the new initState method:
In /src/server/zone.js we will edit the loadUserIntoZone method to use our new methods:
So now when the users ship attempts to load at the logged off position, it will check its safe and if not find a nearby place that is.
With that done, again in /src/server/zone.js change ‘onReceivedInput’ to the following:
Building and running that, and then entering ‘spawn’ in our command line should give us this:
A new ship appearing alongside our PTU.
Shortly we will work out how we can get into that ship.. but we have some things to sort out first:
We are going to expand the state system on the ship, using this we are going to add an important feature into our game.
When someone logs off, we don’t want their ship disappearing immediatly. If it does, somebody could escape from being killed when surrounded by enemies, just be closing the browser. Instead we want a ‘logoff timer’, whereby when someone disconnects, the ship stays in space that amount of time before disappearing.
If someone logs back on before the timer expires it cancels the timer, else the ship is removed.
Aswell as this we will have a ‘logon timer’, when a person logs on a timer begins during which time they are in a state other than STANDARD. This will allow us to perhaps make them invunerable/unable to move when they first logon.
To begin implementing this we’ll add some settings in our /src/common/game.json
Next in /src/common/constants.js add some new states into the object_position_states constant:
Next in /src/server/gameObjects/ship.js we will change our state init method, and add some new methods:
Don’t worry yet what ‘queuedLogoffStateChange’ does. This is needed shortly for times when we cannot begin the logoffTimer, such as if the vehicle is moving between zones. Instead it will start once it lands at its destination and that code (yet to be written) will make use of this variable.
Now.. we need to alter our zone code so that it uses this new mechanism:
Instead of removing the object from the zone, it now invokes beginLogoff.
There is one other thing we need to do here, if the user logs back on before their ship unload, we need to cancel the unload and put them back in the ship. In the loadUserIntoZone alter it to the following:
So you will see that now the method looks to see if the users vehicle is present in the world, if it isn’t it runs the same code as before. However if the ship is still in the world, we don’t make a new one and instead dock the user with the existing.
If we build and start this. Connect a client to the server and wait 10 seconds you should see the following:
Initially the state was moved from OFFLINE to LOADING. Then once the timer expired the state was changed to STANDARD_SPACE.
I then disconnected the client.. waited until the logoff timer expired and got as follows:
Right.. next part given that we can spawn ships that are empty. We need these to stay in the world and persist. The only ships that should logoff/logon are those that have players in them. Empty ships will always be present if the zone is up.
To do this, when a zone loads we want to load in all ships that do not have a player in them.
We’ll alter our init method on zone to fire a loadObjects method, which must complete before the init promise resolves.
The loadObjects method will shortly load other things such as structures but for now it will just do empty ships.
So in the above I created a new method for querying all world objects ‘queryWorldObjects’.
loadEmptyShips then uses this (You may see that this.constructor.queryWorldObjects - that it seems is the best way of calling a static method from within an instance).
It queries all ships that have empty ‘dockedUserID’ (no docked user), and empty ‘dockedBaseID’ (not stored within a structure),
it then created an empty array which will hold promises, then it iterates over the ships, pushing into the promises array the returned promise from the ship init.
Once complete we then do Promises.all, which lets us not resolve until all ship init promises in the array have done their thing.
If you build and run this, if you messed around with the spawn a lot prior to this you will get something like the following when starting up the server:
If you then connect a client, you should see your ships all loaded in from earlier.
We are getting closer to being able to write the code for board/unboard vehicle, but we still have more things we need.
Next up is the ship composition/specification data that is sent out.
You will have see we currently send out the ‘public composition’, this is meant for everyone nearby regardless of whos ship/object it is. It contains core information that a client needs to render the vehicle.
We are going to add a new type of ‘composition data’ called ‘private composition’. This is going to be data that only some people know, such as the person flying the ship.
This might contain information that other players don’t know such as how much fuel your ship has, how badly it is damaged internally etc etc, it is private info for only those who should know it.
Aswell as adding private composition , we are going to add in a ‘state change’ messages for both public or private composition. Using these if the state (public or private) alters, those who should know can be informed.
An example of this is our ‘positionState’ variable on whether the user is logging off or loading, we want to inform those around when this happens (fullUpdate does send some but only core - which is for positional/movement data).
First we will add a private composition method to /src/server/gameObjects/ship.js, and also alter the getComposition():
You will see we modified the public composition so that positionState is now sent in public, and we have a private composition method (which for now has nothing special).
The above get sent when a client is notified of an object, but there may be updates, we will add the constants for both these ‘composition update’ messages - /src/common/constants.js, and also the initial private composition
SHIP_DETAILS is the private composition message which is sent to the occupant.
OBJECT_PRIVATE_SPEC_UPDATE is when there is a change in some kind of private ship data
OBJECT_SPEC_UPDATE is for public changes to a ship that the client is aware of
On the client we will now handle these by adding the following cases to ‘inboundMessageParser’ in /src/client/UI/gameWorld.js:
In /src/client/clientWorldEngine.js we’ll add these new methods:
For now only the ‘objectSpecUpdate’ method has anything of significance in it. We handle three potential messages, the status change such as ‘LOADING/LOGGING_OFF/STANDARD_SPACE’
and ‘uc’ - which is what we will send to clients when the occupant of a ship changes.
Then there is “props”, this is a means by which we can updated a list of object properties such as “ship.thrusters.status” to a value.
That method ‘setDeepVal’ is something I digged out of stackoverflow once which will update an object given the dot notation for it.
We don’t yet however have a means for sending out ‘props’ messages on the server, but that will come later.
Now we will switch back to the server, and put in the code for sending out the status change in /src/server/gameObjects/spaceObject.js
When this is built and run you should see the client logging status changes on vehicles to the console.
Now in /src/server/gameObjects/boardableEntity.js we are going to alter ‘setOccupancyData’ and ‘setDockedUser’, and add a new method for setting a vehicle to vacant (which we will use when unboarding)
You will see the method header on setDockedUser has now changed to include the ‘broadcast’ arg, we are going to use this because sometime we want to disable the message going out.
In /src/server/zone.js we will alter the setDockedUser call to handle this broadcast arg.
Next we will have our SHIP_DETAILS message go out when we join and are loaded in our ship:
In /src/server/zone.js - add the following broadcastTouser to the end of ‘loadUserIntoZone’ to send the message.
You will see the broadcastUsers were added on both potential routes of the method, sending out the SHIP_DETAILS.
Now we can go back to the client and we will use this ‘docked’ data, to display who is in the vehicle.
To do this we are going to add some interface functionality, selecting an object using the mouse pointer.
Our store keeps hold of our UI information, so we’ll create a new actionType in /src/client/constants/actionTypes.js
Now in /src/client/actions/actions.js
Finally we will add the handling of the action to our reducer at /src/client/reducers/worldReducer.js, and put it into the initial state.
Now we need to fire this selectedObject action upon the mouse click in /src/client/UI/gameWorld.js
In the above we added an id attribute to the canvas, and to test on click and then our click handler asks the gameWorldEngine for what object is at the position and if found dispatches our selectedObject action.
If there is nothing under the click we dispatch selectedObject with null to clear the selection.
Now in /src/client/clientWorldEngine.js we’ll add that ‘getObjectUnderPoint’ method
Now we can use this selectedObject ID
Before this we’ll add a getLabel method to our client side space objects.
Now back to /src/client/UI/gameWorld.js
In the above we created a new method called ‘drawSelectedUI’ which draws our selected object label, we then added a drawSelectedUI call to the end of the update method.
Building and running the above we get this when we click the ships:
Now we have enough in place to do board/unboard:
To begin with we will have it so that when the user sends a ‘board [objectid]’ command, the server attempts to put them in that empty ship (if they are close enough).
Later we will add some kind of ‘object actions’ so that you click select a ship and somewhere on the interface will be a button ‘Board’.
To unboard we will simply send an ‘unboard’ command, which will spawn the user back in their PTU near the vehicle, and set the previously occupied vehicle to empty.
We’ll start by adding some command handlers to the ‘onReceivedInput’ method in zone.js to handle our two commands:
Next we will create the two methods referenced above - They will work as follows:
‘board’ will take an id of a target vehicle.
It will check that the vehicle exists. After this we will do a check to see if our current ship is a PTU (which it must be).
We’ll then check that the target vehicle is boardable (which for now will just be whether it is empty).
If all that is okay, we will then remove the users PTU from the world and set it to be located at the target vehicle (as if inside it).
We’ll then set the user to be docked with the targetVehicle, then broadcast a new SHIP_DETAILS message so that the user has the private ship data for the new vehicle they are piloting.
‘unboard’ will kind of do the reverse. We’ll check that we are not a PTU (a user can’t unboard their PTU). We’ll then instantiate the PTU next to the users current vehicle (in a safe non colliding position), set the previous vehicle as empty, add the PTU to the world and then set the user docked with the PTU and as before send the SHIP_DETAILS to the user.
The code for this is below:
You will see we do a check on the target vehicle to see if it is boardable by us, we’ll add a basic version of that to boardableEntity which for now just checks to see if the vehicle is empty.
Earlier we used console.log to output an object’s ID when it is selected, this will make it easier to test this:
Building and running this.. we should get something like the following:
You will see in the first image we are in our PTU, and select the empty Ship, in the second image we have boarded the vessel and our PTU loaded into the ship, in the third I have flown around a bit, and then unboarded which ejected my PTU from the vehicle leaving the Ship empty.
Part 11 - Ship composition, Hyperdrive and Shields
Now that we can get in the new vehicle we will extend what a ship can do, for this we will add the idea of ship modules.
We will start this by adding more state to a ship. This may end being a bad idea but I will store a ships state in a json object in the DB.
This is ‘objectState’ that you may have seen when we initially created the worldObject schema.
Our world objects are going to be composed of 4 elements of data:
persistantStateData (objectState) - This is all the stuff we want persisted such as damage, the condition of parts of the ship and fuel that may be loaded.
compositeStateData - this is going to be stuff that doesnt need persisting such as if the object is moving when it unloads
core - this is the data we have been using already which contains the core info such as the type, x, y and angle of the ship, (it could be in the previous one but isnt)
itemSpec(specification) - this is the data that comes in from the game.json spec data for the item type
We’ll then have modules which can be fitted to the ship, which aswell as adding capabilities to the ship, may alter some of the properties above when active. Such as some kind of special reinforced shield module / reactor or thruster speed improvements. The above data is going to consist of some elements which are public (known by all that the vehicle is visible too) and private (known only to those such as the occupant). We won’t build this all in one go but gradually in parts so don’t worry too much about what all that means. To begin to implement this we will add some basic ‘engineering’ (kind of like in star trek) to the ship.
In this part we will not yet add in modules/fittings. Before we add fittings we need to implement the ingame item system. We can however implement some functionality that all ships have the ability to do in some capacity, regardless of fittings. To start we will have a shield, some hull and armour, a hyperdrive and also build the autopilot system.
(This may all seem a bit mental - a symptom of making all this up as I go along without a plan)
We’ll begin with just hull and armour so that it is clearer what is going on with the composition of a ship. The following will add code which sets u