Celestial Ninja
My 2D game, which can be found here, features a celestial ninja with the power to bend the elements of fire, earth, water, sun, and air. The ninja must defend against shadow warriors who are growing stronger and trying to attack the Sun itself. To fight these shadow warriors, the player can shoot sun orbs or slash a sword that damages enemies with the left mouse button and move around using the WASD keys. However, the player must be careful not to collide with the shadow warriors or get hit by their shadow balls, as this will cause damage. As the player defeats more enemies, they'll collect experience points that can be used to level up. After every few levels, the player can choose to upgrade their bending abilities or their health, speed, or defense, which will help them defeat even more enemies and ultimately save the Sun from danger.
Introduction
Learning
P5play
Before doing any coding, I wanted to make sure I had a general idea of how the p5play library worked. So, I spent time looking through the examples the creator(s) have made. This helped me get familiar with how I wanted to structure my code and what was possible. From digging around in the beginning in the examples, I had already found a few examples that applied to my code such as overlapping dots that could be collected.
P5play documentation
Goals
Then, I got started on a to-do list of the things I wanted to create. I had to order and prioritize items based on what was most critical for the game to work, and then I would focus on add-ons such as visuals. The main components of the game are enemies that follow the player and grow stronger over time, a way to damage the enemies, a way to power up from killing enemies, and a moving character. After setting these foundations, I began to explore what I thought would add more value to the game such as upgrades, visuals, sounds, a tutorial, game over screen, etc. Moving forward with the project as I completed these tasks, I made sure the enemy would scale over time exponentially such as their health and speed, whereas the player and upgrades would increase linearly.
My to-do list I would refer back to
Adding Sprites
The first thing I needed to do was create a player, which was easy, as I had seen code in the example about the camera moving with the player. Next, was adding experience, which was also already an example from p5. The real challenge of this step was creating enemies that had substance, meaning they had health, experience, and would follow the character. I decided that these enemies would also look like the player and have the same default physics and visuals early on in the development to make it more simple. Creating spawning enemies was my first challenge because there were no examples of it. How could I make enemies that would spawn on the edge of the screen randomly? In addition, how would they follow the player? Spawning the enemies on the edge of the screen required me to randomly generate values along all axes, which was a bit difficult to conceptually wrap my head around, but I got working.
Spawning of other sprites
Chasing Enemies
The most difficult part: chasing the player, required me to read the documentation. Originally, I had the enemies have a force vector that would move toward the player every time the draw function was called. However, this force vector would keep increasing, so the enemies over time would just magnetize to the player instantly. So, I had to keep reading more documentation for possible solutions and finally, I found one that worked. This solution follows the player at a constant velocity that is set.
Sprites following the main player
Damage to Enemies
Now, these enemies needed a way to take damage. The most important thing I needed to do was add an orb in the game that the player could shoot with the mouse button. I decided to opt for the mousepressed() function created by p5 to recognize this. There was a lot of struggle trying to find a way to calculate where the bullet would go since it needed to move relative to the player's location and the window. This is extremely difficult with a moving player in p5play, since the player's x and y move without respect to the window size, but the originally set canvas size. To solve this, I first tried calculating a unit vector from where the mouse was pressed on the screen, but this didn't work out. So, I tried doing some more research to see if other people had a solution and it turns out that in the p5play documentation, they had a hidden example of how to accomplish this. Another thing I had to account for was that when you create a group() in p5 AKA a class, you can not make up your own fields, so to represent enemy health, I chose to use their .life field. Although it makes semantic sense, the intention of that field is to represent the amount of time the sprite object has been alive.
Sprites being removed after being hit by mouse clicked objects
Adding Powerups
Because the player also needs to scale in power outside of shooting bullets, I decided to add power up. I came up a list of power ups and what I wanted each one to do and implemented them. The spinning stones was the most difficult because it required me to go back and remember how the unit circle worked to calculate x and y positions. The next most difficult was the bouncing airball due to the nature of calculating it's position and changing its velocity when hitting the window edges.
Power up selectors
I decided to add randomly generated cards that would appear every few levels and pause the game for the user to select what powerup options of the three offered they wanted.
Power up selector cards
Adding Sound and Visuals
Adding sound was quite easy since there are plenty of examples of how to do it online. I spent a lot of time trying to find sounds and music based on what I thought sounded the best. Updating the graphics was an easy, but very long process. I originally thought of drawing my own art, which I did but it looked terrible. So, I instead opted for looking for pixel art of the things I wanted, compressed them to the appropriate size, and then attached them to the asset. It was a lot of work, especially for animations that moved such as the main player ninja and rotating water.
Added graphics
Adding a Tutorial and Game Over
To top the game off before my initial project deadline, I added a tutorial and game over screen to make it feel more complete. A sandwich is only good if it has bread.
Issue Deep-Dive
Lots of issues popped up during this initial stage of the project. Especially since I only had 1.5 weeks to work on it, it was a new library to me, and it is 800+ lines of code. A few issues I mentioned were: the enemies following too quickly and gathering speed over time, calculating orb direction when clicked, and determining how to change the velocity of the bouncing airball. Though, a few other issues popped up. One was not being able to draw a rectangle or text when calling noloop(). To circumvent this, whenever the user reaches a milestone level and selects upgrades, they choose from native javascript buttons. Even the backgrounds of the cards is a button, since I couldn't put p5 rectangles behind them. In addition, when reaching the end of development and adding visuals, I had the issue of my water upgrade animation appearing on top of the player. This was extremely frustrating because there were no sources on why this occurred, but after a lot of experimentation, I learned that animation layers appear based on the order of when the sprite is created. Because the player is created in the beginning, to have anything under them they must have a sprite created before. So, to hack the upgrade, I made it invisible and off the screen until the player got the upgrade, finally making it visible again and following them. Lastly, during deployment, I was having difficulty trying to get my game working. Turns out I wasn't configuring my rollup.config correctly to upload my music and image assets.
Poorly chasing enemies
Design
Prototyping
Now that I had learned how to use the library and had a framework for the game, I began to imagine what other features I wanted to include for a final prototype. I began planning out what UI elements I wanted and how I could modify the game to be more refined.
Paper plans for the UI and new features
Final Version
I expected gameplay to change with new enemies because it would be very difficult for players to stand still and boss levels would change how players use their resources. The most difficult aspect I anticipated was incorporating a database. I was unfamiliar with the syntax and documentation of Firebase. Although it was not many lines, I wanted to have a complete understanding of the code before I inject it into my game.
Development Plan
Thus, for my planning, I began creating a timeline for when I wanted certain features completed so that I could have the project to show to my class HCDE 438. I needed to consider a few things such as learning Firebase to implement a backend database to store high scores and if the page has been visited, and reducing p5 and p5play performance issues. As stretch goals, I thought of possibly adding coins and loot that can be carried over into future games, a menu page, and a multiplayer co-op and/or versus.
-
Prototype Version (3/7)
-
Select between sun sword or sun orb for LMB (3/2)
-
Pause button (3/3)
-
Play again button (3/3)
-
Balance game (3/6)
-
-
Final Version (3/17)
-
Different enemies (3/9)
-
Boss levels (3/11)
-
High score and tutorial tracker (3/15)
-
New visuals (3/16)
-
Development
Adding a Sword Option
The sword was a great addition to the game. I had to spend a long time thinking about how I would implement it since I had a specific idea of how I wanted it to work. I ended up needing to read more documentation about creating sprites, and fortunately, p5play came out with a new way to initialize their sprite class. I used a line sprite that would rotate around the player and this created the effect I wanted. Then, I slapped on an animation and I was satisfied with the result.
A working sword
Adding a Play Again and Pause Button
Adding these buttons was fairly easy. For playing again, since p5 already requires code for initialization, I could run it again when the button is pressed. Luckily, I factored my code in a way that made this a few lines of code with minor tweaks. As for the pause button, it was really easy to implement since I already have situations in the game that require it to pause. Though, it's a bit buggy since the game pauses regularly without a pause button. This makes the function of the pause button weird when the game is already paused and its text content incorrect. Initially, the pause button was in the draw function, but after trying to optimize my code, I realized this could be done in the setup, using fewer resources.
Play again screen
Balancing the Game
Balancing the game only required a few minor tweaks. I reduced the amount of damage certain skills did and mainly reduced the defense upgrade. Now, the defense upgrade reduces a percentage of itself, instead of a full percentage. Also, I added darkness to the game. As you can see below, over time, enemies become more difficult to see (due to low contrast) as time increases. I want the game to be difficult and not really last any longer than 10 minutes, because even with the library's performance updates, it is still slow. In the future, if I have the time, I want to split my code into separate files and use JSON to separate types of variables.
Darkness added to make finding enemies difficult
Cleaning Code and Removing Performance Inefficiencies
There were a lot of times when I named variables inconsistently and had bad code quality. I camel-cased my functions and uppercased my global variables. This was thanks to the CMD+Shift+H Find and replace all functionality in VSCode. Thanks to Quinton, the creator of p5play I was able to optimize the code. I mentioned to him that the program begins to run slowly when more enemies spawn. He ended up changing the library to use an array instead of a map to check for collisions, which significantly reduced resources. I also removed a few things in my code that were unnecessary, such as specifying the diameter of center sprites when their animation already has one and using fewer flag variables. Now, the performance issues were more of a limitation due to planck (the physics engine) and p5.
Conversing with the creator of the library
Adding Different Enemies
Adding different enemies was a simple task, fortunately, since I already had an enemies group/class. For my new enemies, I wanted ones that were better at close range, and ones better at far range, so I started prototyping a few ideas. I decided to add red enemies that run quickly and purple ones that can shoot shadow orbs at the player's location. The coding of the fast melee enemies was easy to do since it's almost a carbon copy of the existing enemies but with more speed and different animations. The coding of the slow-shooting enemies was also easy to do since my sun orbs when clicking the left mouse button have the same interactions and physics.
New purple and red enemies with varying attributes
Adding a Boss
The boss was a really cool addition. My initial idea was to have a large enemy that spawned every five minutes that would charge at the player in intervals. This ended up being difficult to implement since the logic of the charging is difficult to achieve when the location of the player updates frequently. Nevertheless, I got it completed with some fine tuning with the timing and speed.
Fighting the five minute interval boss
Leaderboard/Score Tracker
To track scores, I had to learn Firebase and with some help from a peer, I was able to get a working backend quickly. The main problem was that submitting scores and displaying scores occurred outside the canvas, so it kind of ruined the flow of the game and appearance. Though the backend worked, I wanted players to be able to see live updates of the database, so I added the snapshot function so that users could see their or others' scores populate the scoreboard if they made the top 5 in real-time. So, I needed to move everything to p5, which ended up taking a lot of time.
Early iteration of the live leaderboard
I had to read the documentation about how to create, alter, append, and style HTML elements on the canvas. By playing around with the various functions and shower thinking about getting the scoreboard to look how I wanted, I was able to get it working.
Incorporated a live leaderboard on the canvas
Visual Update
I was nearing the end of the timeline and project, so I made a few last-minute visual changes. One was an upgrade tracker. This was fairly easy to do since there is a function in p5 to import images and place them on the canvas. I also decided to change the background images since I realized the images were eating up a lot of resources and didn't fit the theme as well as I envisioned.
Cleaner UX and UI with upgrade tracker on bottom right
Better UX
The last few changes I made were from interviewing friends to test and what they would want to improve about the gameplay. A few mentioned that they would want to be able to hold down the mouse instead of having to click all the time, so that was easy to add with the mouseIsPressed built-in p5 function. They also mentioned that the timer would run when they clicked off the page, so I added a document event listener to pause the game automatically when the page isn't being viewed.
Conversing with the creator of the library
Evaluation
Issue Deep-Dive Part 2
Adding a sword to my game was a challenging task. I wanted the sword to move based on the mouse position, but the p5play library doesn't work well with moving canvases when calculating mouse position. I tried adapting my existing bullet code to work for the sword, but it wasn't instant, which meant the sword needed time to rotate and stick to the player. To create a hitbox similar to a sword, I used a new sprite option called the line/chain, but it was very buggy and didn't support many of the fields that the library sprites do. I eventually discovered that I needed to create a group for the sword and access the first available element to stick it to the moving player. However, the line hitbox wasn't perfect and the line location was displaced depending on which quadrant the mouse was in. I decided to keep this displacement as fine-tuning the sword to work exactly where the mouse is would be extremely difficult. I also faced issues with the sword animation as it would be displaced incorrectly and rotate along the wrong axis when added to the line. To work around this, I made four different swords that were rotated slightly to match the hitbox of the line sprite I created. Other issues I faced included my timer stacking on itself and updating my code without affecting other parts of the game. To resolve the timer issue, I cleared it whenever playing the game again, and I extensively tested my game whenever I made a change to avoid breaking other things in my code. Although this process was time-consuming, it was worthwhile as it helped me narrow down issues that would have been harder to find later on.
Nonworking sword
As I continued working on the game, I found difficulty in balancing the game. I was constantly changing the values of upgrade damage/values, enemy health, enemy speed, and more to try and prevent overpowered things from happening, yet keeping the game enjoyable. I do expect people to find more issues in the future with certain mechanics, and balancing those will be even more difficult because of how large the code is and not knowing what approach to take to address the issue. I also ran into the issue of my music/sounds starting to become choppy. After some googling, I learned that it was a limitation of the p5 sound library and has been an issue since 2016. Because this issue has existed for so long, I don't expect them to fix it soon, so I removed any sounds that occur often such as pressing the left mouse button or collecting experience.
Errors from coding
Next Steps
As I finished the project, I was very happy with how I left it, but if I were to add a few things it would be loot that transfers over to future games, a menu, multiplayer, visual updates, contrast, a magnet powerup, and improving the sword swinging UX.
What I Learned
I was able to finish everything I wanted to on time despite some challenges. There were moments when I found myself working on tasks out of order because I was getting bored or lost. This approach led to some confusion and added extra time to the development process. In the future, I plan to create a more structured plan and stick to it to avoid any unnecessary complications. Furthermore, I learned a lot from this experience. I now have a better understanding of the p5play library and how to work with moving canvases. I also gained insight into sprite creation and how to use different options to create unique hitboxes. Overall, this project taught me to be patient, persistent, and creative when faced with obstacles in coding.
-
Programming and JavaScript: Throughout this project, I delved deep into JavaScript programming, gaining a solid grasp of its syntax, functions, and data structures. I learned how to manipulate DOM elements, manage event listeners, and create responsive interactions in the game. This hands-on experience boosted my confidence in working with this versatile language.
-
p5play Library: My journey with the p5play library taught me how to create engaging and interactive visual experiences. From understanding sprite initialization methods to handling collisions and animations, I acquired the skills needed to craft a dynamic game environment. Notably, I learned how to adapt and troubleshoot the library to suit my project's unique requirements.
-
Game Development Concepts: I gained insights into various game development concepts, such as hitboxes, sprite animations, game balance, and performance optimization. These concepts empowered me to craft a seamless and enjoyable gaming experience for players, emphasizing the importance of game design principles.
-
Firebase Integration: Learning to integrate Firebase for real-time data management was a significant milestone. I honed my skills in utilizing cloud databases, understanding how to store and retrieve data efficiently. The experience allowed me to create a functional leaderboard system that enhances player engagement and competitiveness.
-
Version Control and Deployment: Navigating GitHub for version control and deploying the game to GitHub Pages was an invaluable learning experience. I now understand how to manage code repositories, collaborate with others, and ensure a smooth deployment process for web-based applications.
-
Technical Problem-Solving: Developing the game presented a myriad of challenges, from accurate sword rotation to optimizing performance. I enhanced my problem-solving skills by researching, experimenting, and adapting solutions to address each issue. This experience taught me how to break down complex problems, identify root causes, and implement effective solutions.
-
Debugging Techniques: As I encountered bugs and inconsistencies, I refined my debugging techniques. Through trial and error, I became adept at identifying faulty code snippets, isolating problematic sections, and employing debugging tools to trace issues back to their source. This skill is essential for maintaining a robust and error-free codebase.
-
Gameplay Mechanics and User Experience: Creating a captivating gameplay experience required me to think creatively and strategically. I learned to design gameplay mechanics that balanced challenge and enjoyment, enabling players to feel a sense of progression and achievement. Incorporating user feedback enriched my understanding of player expectations, ultimately enhancing the overall user experience.