This post will review how I handled gameplay visualization in a JavaScript SPA browser game I built.
When working through the Software Engineering bootcamp at Flatiron, I built a JavaScript SPA as a portfolio project.
I decided to build a game that was simple enough for my (then) three-year-old to play. (Spoiler alert: he was not impressed. ) The resulting game, Eggasaurus, came from a combination of my love of browser games and my son’s fascination with dinosaurs. Users can create dinosaur pets and interact with them.
Over time, the dinosaurs get hungry, tired and bored. This is shown visually by what I call mood meters. Moods decrease constantly, over time. The player can click the care buttons to "fill" the corresponding mood meter back up.
I accomplished this with a combination of HTML (divs), CSS (background colors, width) and JavaScript (setInterval, event listeners, HTML DOM style property).
Mood Meter HTML & CSS
The mood meters are built with divs. Each mood meter (there are three total: hunger, happiness and tiredness) starts with a div with the class meters
.
This parent div has a border-radius, border and height to form the actual meter. If this was an old-school thermometer, it would be the outer, glass piece of the thermometer.
<div class="meters">
...
</div>
div.meters {
height: 20px;
border: 4px solid gray;
border-radius: 30px ;
margin-bottom:5px !important;
}
Then there is a child div with a class green
and and id according to what mood it represents. It also has a width value that we're retrieving via string interpolation. This div represents the value or level of the mood. Continuing our thermometer example, it is analogous to the mercury level in the thermometer (if that mercury was green).
<div class="meters">
<div id="hunger-meter" class="green" style="width:${this.hungerP}">
...
</div>
</div>
The CSS gives the mood level div the same height as its parent; a left float so it appears to grow and shrink from the left; a background color to visually display the level value; and a border-radius to match our illusion of the meter.
div.meters div.green{
height: 20px;
float: left;
border-radius: 30px;
background: green;
}
Finally, there is a grandchild and a great-grandchid div. These hold the sprite that matched the mood the meter represents. So, chicken leg for hunger, in this case.
<div class="meters">
<div id="hunger-meter" class="green" style="width:${this.hungerP}">
<div class="sprite-holder">
<div class="sprite"></div>
</div>
</div>
</div>
The CSS gives the sprite holder a background color, keeping the mood level from showing through. It also places it perfectly in our top-level mood meter div; and sets the background position depending on the meter, so the correct icon is displayed.
div.sprite{
background: url('images/meter_icons.png');
width: 20px;
height: 20px;
background-size: 58px auto;
}
div.sprite-holder{
width: 25px;
background-color: #fff;
float: left;
position: relative;
left: -5px;
top: -5px;
padding: 5px;
border-radius: 50%;
}
div#hunger-meter div.sprite{
background-position: 0px 0px;
}
To handle the interactions with these mood meters, there are corresponding care buttons. Each button has a background image and id that corresponds with the mood meter it will affect.
<div id="care-btns">
<button id="feed" data-id="${this.id}"></button>
<button id="play" data-id="${this.id}"></button>
<button id="nap" data-id="${this.id}"></button>
</div>
Mood Meter JS
The JavaScript takes care of changing the width of the mood levels with the use of setInterval, the HTML DOM style property and event listeners.
setInterval
First, when the game loads or a dinosaur is selected, moodTimer
is called. This function accepts a Dino object id and uses setInterval to repeatedly call a function decreaseMoods
on the Dino instance with the passed id.
function moodTimer(dinoId){
newMoodAdjust = window.setInterval(() => {Dino.findDino(dinoId).decreaseMoods()}, 1000)
}
The setInterval() method calls a function or evaluates an expression at specified intervals (in milliseconds).
In newMoodAdjust, decreaseMoods will be called on the Dino instance, every 1000 milliseconds.
The setInterval() method will continue calling the function until clearInterval() is called, or the window is closed.
-- W3Schools
If a user logs out or selects a different dinosaur to play with, clearInterval is called.
clearInterval(newMoodAdjust)
If a user just selected a new dino, moodTimer()
is called again, and it is passed the new dino id.
Decreasing Moods (HTML DOM style)
The newMoodAdjust calls the function decreaseMoods every second. This function is a Dino class method. It checks to see if a Dino instance's hunger, happiness and tiredness properties are above 0. If so, it calls the corresponding class methods hungry(); bored(); or tired(). These methods adjust the value of the Dino instance's properties and call another class method.
decreaseMoods(){
if(this.hunger > 0){
this.hungry()
}
if(this.happiness > 0){
this.bored()
}
if(this.tiredness > 0){
this.tired()
}
}
hungry(){
this.hunger -= 0.5
this.adjustHungerMeter()
}
bored() {
this.happiness -= 0.5
this.adjustHappinessMeter()
}
tired() {
this.tiredness -= 0.5
this.adjustTirednessMeter()
}
Remember that we are displaying the Dino's hunger, happiness and tiredness property values to the player visually by adjusting the width of the green div. Zero is our floor. Once those properties and the width of the green div reaches zero, we don't need to make any more adjustments.
Our adjust class methods retrieve the appropriate green div and set its width equal to the Dino's hunger, happiness or tiredness property value, using the HTML DOM style property.
this.hungerP
is a getter that translates the hunger property to a percent string. So if this.hunger // 50
then this.hungerP // "50%"
adjustHungerMeter(){
const hungerMeter = document.getElementById("hunger-meter")
hungerMeter.style.width = this.hungerP
}
adjustHappinessMeter(){
const happinessMeter = document.getElementById("happiness-meter")
happinessMeter.style.width = this.happinessP
}
adjustTirednessMeter(){
const napMeter = document.getElementById("tiredness-meter")
napMeter.style.width = this.tiredP
}
Increasing Moods (Event listeners & style)
When the game is loaded and a dino is selected, the SPA calls moodListeners()
.
This function adds click event listeners to each of our care buttons.
function moodListeners(){
document.getElementById("care-btns").childNodes.forEach(btn => {
btn.addEventListener("click", (e) => {
const thisDino = Dino.findDino(e.target.dataset.id)
if(e.target.id == "play"){
thisDino.play()
} else if(e.target.id == "feed"){
thisDino.feed()
} else if(e.target.id == "nap"){
thisDino.nap()
}
})
})
}
When the buttons are clicked, different Dino class methods are called: play(); feed(); and nap().
Unsurprisingly, these methods increase the Dino instance's mood property values (setting them to 100), and call the adjust class methods discussed above.
feed(){
this.hunger = 100
this.adjustHungerMeter()
}
play(){
this.happiness = 100
this.adjustHappinessMeter()
}
nap(){
this.tiredness = 100
this.adjustTirednessMeter()
}
I decided to increase the moods to their max level, instead of increasing them by increments, because my son was getting frustrated with all the clicking.
Have you built any games just for fun? Are you a professional game developer or engineer? What are your favorite browser games? (Mine are CookieClicker and NGU Idle.)
Top comments (4)
By the way @Liz you can add syntax highlighting to your code for improved readability by adding the name of the language / markup (javascript, html, css) after code begin tag (the three ` ).
THANK YOU!
Is it deployed somewhere so I can check it? 😊
Would like to play, too!