DEV Community

Nathan Rymarz
Nathan Rymarz

Posted on

Making A JavaScript Role-Playing Game

In order to cement the concepts of Object Oriented Programming and get more familiar with JavaScript I decided to make my own turn-based role-playing game. This post will describe the steps I took and some of the challenges I faced while creating my game.

To start, I built out a very basic game with the help of tutorials that I watched on YouTube. It comprised a game-loop, an update function and a render function. I also made Player and Level Classes with their own update and draw methods. So far the game only had a moveable player character and a background. I used assets that I found from OpenGameArt.org. The asset that I chose for the my player included sprites for walking in all directions. So the next thing I did was animate my player character. To do this, I made an animate() method in my Player class. When I call player.animate(), the player starts his walk animation. I ran into a problem with requestAnimationFrame() changing the context of animate(). Fortunately, I knew how to fix it using bind. I used an instance variable to count frames and update the spriteIndex every 5 frames. The code for my animate() method is below.

  animate(){
        this.frame++
        if(this.frame % 5 === 0){
            this.spriteIndex[0] +=1
            if(this.spriteIndex[0] === 4)this.spriteIndex[0] = 0
        }
        requestAnimationFrame(this.animate.bind(this))
    }
Enter fullscreen mode Exit fullscreen mode

After that, I added in enemies and a spawning method in my Level Class that makes sure enemies don't spawn on the player or on top of each other. The next thing to do was to get collision working. To check if the player is touching enemies, I made a method isTouchingEnemies() inside my player class. The method takes a list of enemies(the enemies that belong to the current level) and returns falsey if the player is not touching an enemy or it returns an enemy that the player is touching.

 isTouchingEnemies(enemies){
        return enemies.find(enemy =>{
            return(this.x+40>enemy.x && this.x<enemy.x+enemy.width)
            &&(this.y+50>enemy.y && this.y<enemy.y+enemy.height)
        })
    }
Enter fullscreen mode Exit fullscreen mode

I also added an instance variable isTouchingEdge that gets set to true when the player is touching the edge of the screen. Now I could get onto creating battles and spawning new levels.

Inside of the update function for my main game loop, I added checks for player.isTouchingEnemies and player.isTouchingEdge. When the player is touching the edge, create a new level and put the the player on the opposite side that he touched. This way, the player looks like he is traversing across screens.

Subsequently, to handle battles, I created a global scope variable GAMESTATE and added checks to see if GAMESTATE = "MAP" or "BATTLE" inside the game loop. If GAMESTATE was "BATTLE," then render and update an instance of my BattleUI class. The BattleUI class handles drawing the battle UI (obviously) and selecting and using abilities during battle.

One issue that occurred with my BattleUI was super fast scrolling when the user held up or down to select their ability. This happened because for every frame that passed while the key was held down, the selected ability index would increase or decrease. To deal with this, I used a frame counter and an instance variable, abilityIndexLockout. When the user presses up or down, abilityIndexLockout sets to true, preventing them from scrolling until 15 frames have passed and abilityIndexLockout sets back to false. This might not be the most user-friendly way to deal with fast scrolling but it got the job done.

The next big piece of my game was the pause screen, which I had planned to also use as an inventory and player stats screen. Making the Inventory class was very similar to the BattleUI class, the difference was that instead of selecting and using abilites, the player is scrolling through items. I kept it simple by only having 2 items in my game, health and mana potions. For a user to get to their inventory, I added an EventListener that switches the GAMESTATE variable to "INVENTORY" or "MAP", depending on if they are in their inventory already, when the user presses the 'i' key.

Once that was done, the last step to completing my game was adding in new abilities and implementing player levelups. Implementing Level Ups was straightforward. To keep it simple, I used getter methods for player stats like attack, spellpower and hp that scale with the current player level. For example...

get attack(){
        return (this.level + (this.level-1))*2 + 8 
    }
Enter fullscreen mode Exit fullscreen mode

This way, I just have to worry about saving the players current level and XP, instead of all their current stats.

After finishing everything with the game itself, I built a backend Rails app to use for saving and loading game data. I encountered a frustrating and odd bug with VSCode's LiveServer extension. Anytime I used a fetch request, the page would reload. I had to disable automatic refreshing from LiveServer to get my app to work. Other than that, getting the backend to work was simple.

Overall, I'm very pleased with how my game turned out. I have found a new appreciation for indie game developers since I've experienced how much work it takes to add basic features to a game. I enjoyed the time I spent building it and learned a lot about game development and JavaScript along the way.

Top comments (0)