Introduction
Today we will go over making a very basic multiplayer online system using websockets. This is usually a topic that many people struggle with, because it usually involves installing many things, dealing with heavily written plugins and libraries, etc and most people that create plugins/demos for doing that in MV, does it in a complex way, making it hard for a beginner to get the basics.
What we will build here is the very basic foundation of a multiplayer online system. Our project will allow up to 3 players at the same time and we will make a kick system as well as a command for closing the server. At the end, there will be ideas and indications for those who want to expand it.
What will we need
For this project, we will use just the RPG Maker MV(I recommend the last version, for bette performance) for the client, and a software of mine for the server, which you can download for free here:
https://jitjs.blogspot.com/p/download.html
(You can also use node.js for the server, but at least I believe my project to be easier to work with. Also, the command for kick and closing the server that we will later do requires it to work).
You also need a text/code editor and some basic knowledge of javascript.
The server
Now let's start building our server. Since what we're doing is something very basic, our server will only register each player under an ID number, receive information about its location and broadcast back.
To begin, we will need to require a library called "nodejs-websocket". If you're using nodejs, open it and do "npm install nodejs-websocket", if you're using JIT.js, its already installed by default. So let's begin:
Code:
var ws = require("nodejs-websocket") // require our websocket server library
var playercounter=0 //Create a variable to count the number of players connected, so each will receive one ID
var server = ws.createServer(function (connection) { //this creates the server
//our code will go here
}).listen(8081) //start running the server, listening to port 8081
Code:
var ws = require("nodejs-websocket")
var playercounter=0
var server = ws.createServer(function (connection) {
connection.playerid = null //For now, let's define it as null
}).listen(8081)
Code:
function broadcast(str) {
server.connections.forEach(function (connection) {
connection.sendText(str)
})
//This function will send the argument "str" received to all connected players
//Place this after the ws.createServer created above
}
Code:
var ws = require("nodejs-websocket")// require our websocket server library
var playercounter=0 //Create a variable to count the number of players connected, so each will receive one ID
var server = ws.createServer(function (connection) {
connection.playerid = null
connection.on("text", function (str) {
if (connection.playerid === null) { /*if connection.playerid===null, it means that its the first time the player is connecting, so this part executes*/
playercounter++ //we add 1 to the player counter
connection.playerid = playercounter //the id of the player will be equal to the current number of playercounter
console.log("New player joined, ID:"+playercounter) /*displays a message showing that a new player connected within JIT.js*/
connection.sendText("id:"+playercounter) //we send to the player his ID
broadcast("eventid:"+playercounter) //We send to all users that there's a new player under that ID
} else //if its not the first connection
broadcast("update:"+connection.playerid+":"+str) //we send to everyone the position of that player
})
}).listen(8081)
Code:
connection.on("error", function(err){
console.warn(err)
})
First, let's begin with creating our askmode "container". After everything(synchronous) outside of it runs, that container takes place and keeps looping itself for each command you send in the terminal. Each time it loops, a variable called "jitask" is given to you, with the last input string given within the terminal. Using it, we will build some basic commands for our server.(For more information about this mode click here).
Code:
askmode{//Enter's askmode
if(jitask==="close"){//if you write "close" within JIT.js, this runs
server.connections.forEach(function (connection) {//runs this function for each active connection
connection.close() //close the current connection
})
console.log("Server closed.")
backtoconsole() //ends the askmode and returns to JIT.js' terminal
}
if(jitask.startsWith("kick")){//If you write something that starts with kick
var kickid=jitask.split(" ")//splits the string received from the terminal
server.connections.forEach(function (connection) {//repeats this function for each connection
if(String(connection.playerid)===String(kickid[1]))/*If the number written after ID is the same as one of the players' ID */
{
connection.close()//close's the connection with that specific player
console.log("Player id "+kickid[1]+"has been kicked.")
}
})
}
}askmode
With the code above, when we run our server, we will lock JIT.js for receiving only 2 commands: "close" and "kick <NUMBER>". If you write the first one within the terminal, all connections are closed, and the askmode ends. If its the second one, and the number given is the same as one of the player's ID, that given player will be disconnected.
If you did everything correctly until here, your server code should look like this:
Code:
var ws = require("nodejs-websocket")// require our websocket server library
var playercounter=0 //Create a variable to count the number of players connected, so each will receive one ID
var server = ws.createServer(function (connection) { //this creates the server
connection.playerid = null
connection.on("text", function (str) {/*if connection.playerid===null, it means that its the first time the player is connecting, so this part executes*/
if (connection.playerid === null) {
playercounter++//we add 1 to the player counter
connection.playerid = playercounter//the id of the player will be equal to the current number of playercounter
console.log("New player joined, ID:"+playercounter)/*displays a message showing that a new player connected within JIT.js*/
connection.sendText("id:"+playercounter)//we send to the player his ID
broadcast("eventid:"+playercounter)//We send to all users that there's a new player under that ID
} else
broadcast("update:"+connection.playerid+":"+str)/*We send to all players the position information received from one of them. */
})
connection.on("error", function(err){
console.warn(err)
})
}).listen(8081) //start running the server, listening to port 8081
function broadcast(str) {
server.connections.forEach(function (connection) {
connection.sendText(str)
})
}
console.log("Server running!")
console.log('Type "kick <id>" to kick a player from the server, or "close" to close the server.')
///////// From now on, it won't work in node.js
askmode{//Enter's askmode
if(jitask==="close"){//if you write "close" within JIT.js, this runs
server.connections.forEach(function (connection) {//runs this function for each active connection
connection.close()//close the current connection
})
console.log("Server closed.")
backtoconsole()//ends the askmode and returns to JIT.js' terminal
}
if(jitask.startsWith("kick")){//If you write something that starts with kick
var kickid=jitask.split(" ")//splits the string received from the terminal
server.connections.forEach(function (connection) {//repeats this function for each connection
if(String(connection.playerid)===String(kickid[1]))/*If the number written after ID is the same as one of the players' ID */
{
connection.close()//close's the connection with that specific player
console.log("Player id "+kickid[1]+"has been kicked.")
}
})
}
}askmode
Let's now head to our Client part.
The client
Our client won't require a lot of things. At least not external(like libraries and softwares). In order to it to work not only on PC(Windows, Linux and Mac) but also browsers, here we will be using only HTML5 features here(which shouldn't be much different from how we coded our server part).
First, make a new project. Create an Event in a map with 3 pages. The first empty, the second checking if the variable with ID 1 is >=1, and the third with the same check + Check if self switch A is on. Also, add a image to that last page(of a character). Make the second page have a conditional branch, checking if the variable ID 1 is different from the variable ID 10, and that all pages have priority as "Bellow Characters". Now copy this event 2 times, changing the checks in the second and third pages(both in the page and in the conditional branch) to ID 2(for the second copy) and 3(for the third copy).
Those objects will represent the other players. The variables 1,2 and 3 will receive 1, 2 and 3 respectively whenever a first, second and third player join. When a client connects, the variable of ID 10 will receive our ID. With that system, no objects will show in the map, until a player joins, and we won't see a copy of ourselves in the map too.
After that, create a common event, change the trigger to "parallel" and leave the switch to "001". This event will catch the player's position every frame. Select the variable ID 4, mark "Game data", click it, in the new window opened mark "Character", select "Player" and then "Map X". Then add another one and do the same, but now for Variable ID 4 and in the end select Map Y.
Ok, finished that, save the event, close the window, and let's go back to the map. Make it set the location of the events we made before(ID 1,2 and 3) to be the same as the place where the player spawns, then make it set the Switch 1 to "ON", call the common event we made in the last part and finally add a Script event within it.
This script will be responsible for connecting us to the server, and create a function that every 16ms(+/- the time that each frame stays in the screen, when running the game at 60FPS) will send our position to the server.
Code:
var update
var connection = new WebSocket("ws://localhost:8081")/*Creates our websocket connection to the server we made. You can change locahost by the IP:Port of a dedicated server if you have one */
connection.onopen = function () {//This function runs when we connect to the server
console.log("Connection made")
setInterval(()=>{//repeats the following code every 16ms
coordinates=$gameVariables.value(4)+":"+$gameVariables.value(5);//makes a variable to hold the player X and Y
connection.send(coordinates);//Sends to the server the variable with player X and Y
},16)
}
connection.onmessage = function (event) {//This function runs when we receive a message from the server
update=event.data //variable "update" receives the information from the server
update=update.toString().split(":")/*since we're sending multiple informations in the same message, we add this ":" to separate them*/
if(update[0].startsWith("update")){//If the first part of the message received begins with "update"
if(Number(update[1])!==Number($gameVariables.value(10))){//and the second part is not the same number as our ID
$gameMap.event(Number(update[1])).setPosition(Number(update[2]), Number(update[3]))/*we update that event ID, X and Y*/
$gameVariables.setValue(Number(update[1]),Number(update[1]))//This is just to make sure that other players that joined before will be visible
}
}
else if(update[0].startsWith("id")){//If the first part starts with ID
$gameVariables.setValue(10,Number(update[1]))/*We set the variable ID 10 to the our ID. This will help us make sure that there's not an event copying our movement*/
}
else if(update[0].startsWith("eventid")){//If the first part starts with eventid
$gameVariables.setValue(Number(update[1]),Number(update[1]))/*We give the variable with that ID a value equal to the ID. This way, we can make sure that the 3 events we made in the map will only be visible and move when there's a player with ID equal to their ID*/
}
}
Now add "erase event" to it, set trigger to autorun, save and close. If you did everything right, the client should be done, and you can deploy it for testing(To run multiple windows of your RPG Maker MV game at once for testing, you will need to change "name" parameter within a file called "package.json" each time you open it).
Final considerations
The project we made in this tutorial is, once again saying, really simple and basic, it basically teleports events, that represents our players, to the same location in a map that our players are. But, using the basic knowledge you got here, and doing some research on how websockets work, you can easily extend it to do your liking.
For example:
- Using the same websocket system above, you can easilly build a in-game chat.
- You can easilly send a message before all else(In the function thar runs right when the client connects) from the client to the server, and make that message contain the nickname of the player, displaying it above the players head using this plugin
- You can make the events move in a less robotic way using this plugin
- You can create a basic database system, requiring a username and password from the client upon connection, and storing it in the server as a .json file(encrypting the password using itself as the encryption key, so each password is encrypted with a different key)
As a final word, I would like to say that, this is my first time making a tutorial. I did made some documentations(for my software, for example), but those were only for informing "this do that". So if you didn't understand something and/or would like me to change/add something, feel free to PM me about it or comment bellow, and I will do whatever I can to help.
If you want, you can download a demo with all the code used:
https://www.dropbox.com/s/35la3zbaqdwm94w/demo project.zip?dl=0
Also check this open-source game of mine made using the principles/suggestions given in this tutorial:
https://forums.rpgmakerweb.com/index.php?threads/rpg-battle-royal-alpha-0-1.106007/
(You may use it as a base for a project of yours)
Last edited:
