10.) MMO From Scratch - Inventory

Results 1 to 4 of 4
  1. #1
    :-) s-p-n is offline
    Jun 2007 Join Date
    Next DoorLocation

    10.) MMO From Scratch - Inventory

    10.) MMO From Scratch - Inventory
    Previous (Spring Cleaning)
    Table of Contents (Introduction)
    Next (Iron Mines & Ground Items)

    Git the files for this tutorial here

    Adding the inventory was complicated. When we created the Slire Herb, I had full intentions of the player having the ability to pick up the herb, but I didn't plan ahead for that functionality at all. I didn't realize that creating 3 different files on the server for herbs, would force me to use all 3 of those files when communication from the player->server->herb was to take place.

    With that said, I have good news and bad news.

    Good News...
    is that I got the inventory working.

    Bad News...
    is that I need to completely rethink the way I'm going to implement items, inventory, and resources- and how they are going to work together.

    Let me explain the problem in more detail, and perhaps you'll understand.

    The Problem:
    When the client requests to add something to the player's inventory, we need to know about the player, the "something", and the player's inventory. We need access to the player, so we must do this in a part of the server that knows about the session- that would be a channel or something a channel initializes or communicates with. We need to know if the something exists, and then enforce rules that take into account the player's game data and the something in the world. For example, is the player close enough to something to pick it up? Once something is picked up, it must be removed from the game- but only if it is successfully added to a player's inventory. We might consider the player's inventory player data, but one way or another, we need to add something to the player's inventory. I haven't even begun to talk about removing something from the player's inventory and adding it to the map, which is just as complex.

    In order to get inventory working, we must, one way or another, couple all three of these entities. Doing so at such an early stage is extremely risky, because in early stages of development we often make changes to things. Any time one thing is coupled to another, and one of those things change, there's a chance of chaos that sends us straight into the abyss (a place where we don't know where we are in our code, and are trapped there until we solve a chain [of problems] that has an unknown amount of chain-links [sub-problems]). The idea of entering such a place frightens me.

    The other option is neglecting to implement a feature that couples entities on the map with the player and the respective inventory- at least for now. Say we make all of the resources before implementing the inventory. I can't say I know it would turn out badly, but I assume different entities on the map may function differently, and it's reasonable to assume they would be designed differently. It's also reasonable to assume we would still have to couple each item to the player in some way, shape or form. I don't think it's wise to go down this path. I think it's a little more wise to create some kind of interface- a set of rules, if you will- for entities on the map that players may pick up and drop.

    At present, we only have the Slire Herb entity. So this makes the Slire Herb very important to us right now. Before we go forward, I have a confession to make. I have a personal problem with designing systems that influence the design of many other systems that are known to come in the future because I feel doing so hinders innovation. I can get over that obsessive compulsive issue, though, if I try really hard.

    With that said, I did implement inventory- and it does work. Let's explore how it was done.

    The Client
    On the client, I wrote some HTML and CSS to draw the inventory on the right sidebar next to the game canvas. There are a generous 30 slots available. I decided that items would be stack-able up to 64, giving the player a maximum of 30 unique items, for a total of 1,920 items.

    View the /public/index.html on github.
    View the /public/css/main.css file on github.

    So, I created a file, /public/js/inventory.js, that acts as a controller for the player's inventory. The add method takes the item (string: name of item) as an argument, and cycles through all of the inventory slots searching for an item with the same name, or an empty slot. If an item is found with the same name, and the num for that entry is less than 64, then the num is increased, the inventory is rendered with the change, and the add method returns true and is done executing. If an empty slot is found, the id of the first empty slot is saved.

    After the cycle is complete, if there are no empty slots the add method returns false. Otherwise, the item is added to the first available slot and the add method returns true.

    There's a restore method that will take an inventory array as the argument, assign it to the inventory's items array, overwriting it, and then render the inventory.

    There's a getItemImage method that is used to display the image of an item given an item entry (name and num). It also displays the multiplier text, that is 'x' + item.num. (example: x31).

    Finally, there's a render method that cycles through each of the DOM elements '#rightPanel .item', and assigns the appropriate item to it's respective visual slot.

    I haven't yet implemented a way to move items around, but that idea should be compatible with the inventory code on the client so far. In games like this, the ability to sort your items could mean the difference between life and death.

    Further down the code, I added some events for placing items onto the map. I set a gridSize variable to 25. This dictates items are placed nicely onto the game world. I also set a variable, sprite, to null.

    I create a click listener for items, that will assign the sprite variable to the item that was clicked, and associate the inventory_id with that sprite. If a sprite was already active, though, it will drop the sprite back into the inventory, instead. This allows the player to stop placing sprites onto the world.

    I then create a mousemove listener. This will set the x, y coordinates of the sprite to the center of the mouse cursor when the mouse is in motion. However, I use the gridSize variable to only move the item in a 25x25 grid.

    Finally, I create a click listener on '#canvas' that will subtract the item.num by 1, and emit an 'herb-planted' event to the server. If item.num is less than or equal to 0, the sprite will be destroyed and set to null again. Otherwise, the player may keep placing sprites. I don't update the game world here, but instead the server will process this information and broadcast the change to all of the players- including the one who placed the herb.

    Notice that the name of the event, 'herb-planted', is specific to herbs. This isn't right- and we'll be decoupling the inventory to the herb in the next tutorial. At the moment, though, we're just getting an inventory working with herbs.

    I updated the /public/js/objects/herbs.js file and added a createHerb method. Each herb is given the id from the database, and I enabled input on each herb in phaser. When an herb is clicked, we check if the herb is close to the player, and we attempt to add the herb to the player's inventory. If the herb is successfully added to the inventory, we delete the herb and emit an 'herb-picked' event to the server.

    I also added a deleteHerb method (herb.id argument), that will destroy the sprite and remove the herb from the list of herbs on the client.

    I then added communications listener for 'herb-deleted', that expects an herb id. It will simply call the deleteHerb method, passing it the id.

    I then modified the file, /public/js/objects/player.js. The player initialization now includes an inventory restore operation. The inventory is sent from the server in game.inventory, and is 1-1 compatible with the client's inventory structure. I then added a listener for 'inventory-update', that will restore the inventory as well.

    I also added a closeRect (Phaser.Rectangle) that will be an invisible box around the player. It helps to determine if an item on the game world is close enough for the player to interact with. I update the position of this rectangle in the player's update function, so that it moves when the player moves. I added some debug code to the player's render function, that will make this box appear red at 50% alpha so that I could test that it worked. I commented out the render code once I was finished implementing this.

    I had to modify the file, /public/js/main.js and add a utils namespace, that is to contain various functions which will be shared by other plugins. I added one utils method, 'isClose', that checks if the player is close enough to an item to pick it up.

    That about wraps it up for the changes made to the client.

    The Server
    The server changes are more concerning. Some changes will be rethought in the next tutorial.

    I first had to add an inventory to the player's game field in the database. This isn't MySQL, so we don't actually edit the schema or log into a SQL editing client to do this. We just change the application a bit. So I edited /private/channels/register.js and added the inventory there. It's actually not really necessary to do this, because if we do a $push operation to an array that doesn't exist, Mongo will happily create an array containing the item we're attempting to push. Perhaps because of my roots, I was compelled to add the array before I pushed elements onto it.. For me, it just seemed natural to do this. Not many features are too high-level for me to feel comfortable with, but I guess I found one.. Am I getting old? Oh wait, I found a reason. Because I want to initialize the inventory on the client. I don't want to check if it isn't an array. The client or server will have to check if the array does or doesn't exist, is or isn't an array, and create one if needed. I'd rather just pull from DB and push straight to a JSON packet to the client with no processing. I can remain optimistic if I create this array when a user is created.

    I also entered a mongo shell and added an inventory array to each user by running this command:
    db.users.update({$set: {'game.inventory': []}}, {multi: true});
    The inventory on the server is very similar to the inventory on the client. Instead of worrying about visual display, though, the server version has an update method instead of a render method. The update method will emit an 'inventory-update' for the player. It will, of course, run a query on the database to reflect the changes to inventory items as well.

    The add method is very much the same as the client. I created a remove method that will check the item.num, and if it's greater than 0, will subtract num by 1. Otherwise, it will remove that inventory item.

    I put the inventory code in /private/channels/player.js, though I plan on moving it to it's own file in the next tutorial. The reason I put it in the player.js file, is because I wanted to instantiate the inventory on the player's session when the initPlayer function is executed. There are other ways to instantiate the inventory once the player enters state 4, though.

    In /private/channels/herbs.js I added a main event listener for 'herb-deleted'. All it does is forward the id it receives to the client using the same name for the emit.

    I made an intersects helper function for the 'herb-picked' listener. If a client says an herb is picked, we need to check if the player's sightRect intersects with the herb. I just create 2 objects with x,y,w,h params, and use the intersects helper function to see if they intersect. If so, we send an event on the main event system titled 'herb-picked', passing it the data for the herb and this session's user. It should be noted that we don't trust the client to give us the herb's coordinates, we just accept an herb-id from the client, and use that id to get the herb from the server's list of herbs.

    We then listen for the client to send an 'herb-planted' packet, that should send us an herb object with an inventory_id, place, and name. We run an extensive series of checks to assure that the herb sent from the client is compliant with our schema of an herb. If all of that checks out, we remove the item from the user's inventory, and emit an 'herb-planted' event on the main event system. We pass an object containing a name and a place for the herb to be planted.

    After that, I set up two listeners in /private/extensions/objects.js- 'herb-picked' and 'herb-planted'.

    If an herb is picked, we get the herb from the list of herbs based on the id, and attempt to add it to the player's inventory. If that works, then we remove the herb from the game (this includes the DB) using the herbs.remove method. We also delete the herb from our list of herbs, and emit an 'herb-deleted' event on the main event system. This is handled in /private/channels/herbs.js as noted above.

    If an herb is planted, we attempt to add the herb to the game (and db). If that works, we assign it to m.objects.herbs and emit an 'herb-created' event on the main event system. This is also handled in /private/channels/herbs.js.

    In /private/extensions/objects/herbs.js, I made some changes to how the self.db.add method worked, and added the self.db.remove method. I also felt it was necessary to create a self.add, and a self.remove method which would deal with synchronizing the game and the DB with external changes (such as a user planting/picking herbs).

    Those are all of the changes needed to get an inventory working with 1 single game item.. It's rather pathetic, though many of the things added were necessary. For example, the changes to /private/extensions/objects/herbs.js were necessary. Adding an inventory was necessary.

    The things that shouldn't be necessary, are the communications between /private/extensions/objects.js and /private/channels/herbs.js. Furthermore, I propose that the inventory be given it's very own channel- rather than leaching off of the player channel. In the next tutorial we'll work on that, and implement something else that a player can pick up/drop... Something that isn't too difficult.

    Would iron mines be something easy? I think so, but we'll see.

    Previous (Spring Cleaning)
    Table of Contents (Introduction)
    Next (Iron Mines & Ground Items)
    Last edited by s-p-n; 10-02-16 at 09:52 PM.

  2. #2
    Registered Lerradus is offline
    Jan 2016 Join Date

    Re: 10.) MMO From Scratch - Inventory

    Very nice & interesting! I wish you the best of luck!
    Also which languages do you use for this development, if I may ask?

  3. #3
    10.) MMO From Scratch - Inventory Future is offline
    Dec 2011 Join Date

    Re: 10.) MMO From Scratch - Inventory

    Quote Originally Posted by Lerradus View Post
    Very nice & interesting! I wish you the best of luck!
    Also which languages do you use for this development, if I may ask?
    Mostly JavaScript with some CSS from what I can see.

    Quote Originally Posted by A Wise Man
    P-Servers are NOT dead. Bugs need squishing. Quests need fixing. Unfortunately, majority of people don't know the difference between a computer and a toaster so...

  4. #4
    :-) s-p-n is offline
    Jun 2007 Join Date
    Next DoorLocation

    Re: 10.) MMO From Scratch - Inventory

    I use node.js (ES5) on the server, MongoDB for the database, and HTML5 + CSS3 + JS (ES5) on the client.