Play the game here
Overview
Video games have always been a part of my life. Starting from my early days playing Super Mario on the Gameboy Advance, moving through teenage years immersed in Call of Duty and Battlefield, and now, as an adult, nostalgically exploring the vast world of Hyrule in The Legend of Zelda: Breath of the Wild during my spare time. Unsurprisingly, my Articulate Storyline journey quickly led me to explore ways to replicate the logic, challenge, and sheer delight that I experience in video games.
After gaining a foundational understanding of Javascript and GSAP, I made the decision to embark on a project. My goal was to utilise these tools within Storyline to recreate a cherished arcade game – Snake.
Process
The last time I had played Snake was probably when I was punching the keys of an old Nokia phone. So I played different versions of the game (some that had even been developed in Storyline) to source inspiration. I landed on several rules that my game would adhere to:
- The snake will move within a grid system.
- The snake will move according to a timer.
- The snake will grow when it eats food.
- The food will move at random when certain variables are equal.
- The snake will die when certain variables are equal.
The snake will move within a grid system
Setting up the grid required a little math. I made a square with an aspect ratio of 4:3, ensuring it could move from 0 to 660 pixels horizontally and 0 to 480 pixels vertically. The square’s size was 60 x 60 pixels, allowing it to move 9 squares up and down and 12 squares left and right. I also made sure the food stayed in the grid by copying the square, making it transparent, and grouping it with a circle. This way, the circle stayed in line with the grid.
Afterward, I created variables like the snake’s direction and position, displayed in text boxes for monitoring. I wrote some simple JavaScript to move the snake when arrow keys were pressed:
if (direction == right) {
gsap.to(snake, { x: xposition + 60 })
}Now users could control the snake manually with arrow keys. However, it required key presses. To make it move automatically, I planned to add a timer in the next step.
The snake will move according to a timer
I made a timer for the game. Initially, it ran every second, but I quickly switched it to every 0.2 seconds for faster testing. The timer reset when a key was pressed (though I changed this later when I added a title slide). However, when I switched from manual to automatic snake movement using the timer, unexpected problems arose.
Firstly, the snake could still turn back on itself. For instance, if it was going right and you pressed left, it could instantly turn around. To fix this, I added a check to prevent the direction from changing if the input was opposite. That seemed to work, but then another issue popped up.
The snake could still do a 180-degree turn if you pressed two directional keys quickly. If it was going right, you couldn’t switch it to left. However, if you changed it to up and then left within 0.2 seconds, it would turn around. To solve this and lock down the snake’s movements, I introduced a new variable. I created a Boolean variable aptly named ‘boo’ and set it to allow only one direction input every 0.2 seconds. This prevented the snake from quickly doubling back on itself.
The snake will grow when it eats food
This part was the trickiest while making the game. When the snake ate the food, a new square had to appear behind the snake’s head and follow its every move. This needed to continue as the snake’s body grew. I tried different solutions with states, layers, and GSAP transformations, but I only got half the problem solved. The snake’s body followed the head, but it changed direction at the same time instead of a bit behind.
I finally figured it out when I asked myself, “What if the snake’s body was just another head?” This meant each body square would use the same GSAP code as the head, with a few tweaks. I duplicated the snake head to create seven body squares and placed them on the side of the stage. Each body square needed specific accessibility text to match a variable in my code (snake2, snake3, snake4…). Then, I added extra code in JavaScript for each of the four movements:
gsap.to(snake, { x: xposition + 60, duration: 0 })
if (score > 0) {gsap.set(snake2, { x: xposition, y: yposition })}
if (score > 1) {gsap.set(snake3, { x: xposition2, y: yposition2 })}
if (score > 2) {gsap.set(snake4, { x: xposition3, y: yposition3 })}
if (score > 3) {gsap.set(snake5, { x: xposition4, y: yposition4 })}
The code above runs when the time changes and the snake is moving to the right. The snake head moves 60 pixels to the right. If the score is more than zero, the second body square (‘snake2’) starts moving to where the head was 0.2 seconds ago. This pattern continues down the line, with each body square following where its predecessor was just before.
Once this approach worked well, I had to create and define all these body squares. To let a player ‘beat the game,’ I would have needed 107 body squares, but I stopped at 46. Players can still keep growing their score, but the difficulty doesn’t change after the snake reaches 46 squares in length.
The food will move at random when certain variables are equal
For this part, I needed a random number generator to decide where the snake’s food would appear. It had to pick from 9 x-coordinates and 12 y-coordinates, giving 108 possible spots. After some digging in JavaScript forums, I found a method to set a specific array of numbers for the generator to pick from.
While testing this solution, I noticed the food would sometimes vanish from the screen. This happened when the food landed on a square beneath the snake’s body, only revealing itself when the snake moved away. Other players might like this element of mystery, but I didn’t. I wanted the food always visible to keep the game fast-paced.
I also realized the player couldn’t see their score during the game. I was so focused on getting the movement right that I overlooked a spot on the screen for score display. Changing the project’s aspect ratio would interrupt the snake’s position, so fixing it would mean adjusting a lot of code, including the just-made random number generator. Dealing with this seemed too inconvenient, so I left the absence of a score display as part of the game.
The snake will die when certain variables are equal
Initially, I thought killing the snake would be a piece of cake. Then I discovered that GSAP and the Storyline ‘Object Intersect’ trigger do not get along. I have spent hours scrolling through forums trying to find a way to get these two to play nicely with each other but alas, I still don’t have the answer. What this meant for my Snake game was that I couldn’t simply kill the snake when it intersected with its body or the perimeter. Instead, I would need to use variables.
Firstly, I created a boolean variable named ‘stop’ and set its default value to ‘False’. When it changes to ‘True’ it triggers the snake to stop moving. Next, I needed to code the conditions in which the stop variable would change. This would be whenever the snake head’s coordinates are equal to the coordinates of any of the body squares (excluding the first three body squares as it is impossible for the snake to collide with these). The stop variable would also change to ‘True’ if the snake’s head was to go beyond certain coordinates (the perimeter).
After successfully testing my code with the first few body squares I moved on to copy the code for all of the body squares. It was then when I noticed another minor difference in my Snake game compared to the ones that I played. Because I had designed the game to move the snake according to the timer then check if the coordinates were equal; what this meant was that if the coordinates were equal and the snake had died, the head of the snake would be hidden under the body. The player did not have the opportunity to see exactly where they were when they made the wrong move and I would prefer it if the snake had just stopped moving. In an attempt to remedy this, I coded a rebound where the snake head would quickly return to its previous position upon dying. There was a slight delay in this though and it made the dying mechanic look worse. Having the head disappear was the lesser of the two evils in this case.
Final touches
After getting the snake moving and munching on circles, I added some final touches to wrap up the project. I made a simple title screen inspired by 80s arcade games, with one key to start the game. I chose a green background and used a complementary color for the snake. I liked the checkered design from the Snake game on Google, so I added that and made the snake head a darker shade of purple to help players spot it.
I tried different colours for the food and border, but I settled on black and purple. For some audio flair, I found and tested various royalty-free sound effects until I found two that fit the game. I thought about adding a chiptune-style soundtrack matching the snake’s rhythm but decided against it, realizing it would mean adding an option to adjust sound. I experimented with the stock audio settings button in the Storyline Classic Player, but I didn’t like how it looked.
Evaluation
Overall, I am proud of how this small passion project turned out. I enhanced my knowledge of Javascript and GSAP language and refined my understanding of how these tools can be fused with Articulate Storyline. This project revealed to me some gaps in my approach and has left me with some questions such as “how can I get GSAP and Object Intersect to talk to one another?”. Furthermore, I gained experience in exporting my project as a html file and then hosting it on a webpage via Github. This was due to the fact that I could not share it via Review 360 as changing the size of the web browser would influence the positioning in the game (yet another problem that I will keep in mind for my next game!). Upon revision I would like to add a score tracker, an option to adjust difficulty (this would simply change the timer duration), and I would like to maintain visibility of the snake head when the player loses.