Concept to Release
In 2013 I was involved in a conversation about updating an academic website called Ukraine Alive; a website that was designed to teach people about the Ukraine. The professor in charge of the project learned that the study of the Ukraine, along with other countries(China, India, Tunisia, and Peru) was part of the Alberta Social Studies elementary school curriculum. They wanted to modify their site to make it more approachable by young students.
It was mentioned that games could really help with this. The usual educational style of games were proposed: Coloring pictures, puzzles, and ordering cards; and eventually implemented. These we’re all great ideas, but I was hoping to do a lot more.
This was enough to impress those involved in the project, and planning started right away for a fully functional game.
Prototype Demo: http://markmckellar.com/HTML5/Ukrainian/index.html
The original plan was to have a game to work similar to a Massive Multiplayer Online(MMO) Role Playing Game(RPG) where students of a specific classroom would have access to their own village. In this village, each student would have access to their own house. They could furnish their house by completing tasks around the village that related to Ukrainian culture. On top of this, each house would have its own Domovoy; a house spirit in Ukrainian folklore that will cause mischief around the house if it was not kept clean or newly furnished.
From here we produced a demo that could allow multiple users to connect to a village using an access code; walk around the village; see other students; and paint a Ukrainian Easter Egg.
In 2014, four student students from a MLCS(Modern Languages and Cultural Studies) course were asked to join the project where they would create the game stories, provide artwork, and design game components. Through the involvement of such an enthusiastic team, the games really began to take shape. They helped to steer the project away from a MMO style game to a more streamlined story driven adventure. The story would focus around the respective countries folklore.
The Ukrainian game would focus on the story of Baba Yaga, a type of witch in slavic cultures who would implement a curse children in order to teach them a lesson. This story would involve a child who was cursed by being turned into some sort of animal(most likely a pig), and had to help people around the village in order to reverse the curse.
Another story was created around Chinese culture. This story would follow a child who was blessed with a magical paintbrush, which would make anything they painted a reality. They then had to use this to help others.
At the end of school year, we were able to present the results of their hard work at Edmonton’s GDK 2015 event.
Over the next four years, eight students were involved in the project; each providing unique content and additions to the game engine. The Ukrainian game was temporarily dropped in order to focus purely on the Chinese culture game.
The engine was completely rebuilt in order to facilitate student involvement where they were not only designing assets for the game, but implementing them as well.
We decided to use Tiled(https://www.mapeditor.org/) as our map editor. It was flexible enough to handle our are, and allowed us to write our scripts directly onto components within the game.
Painting tasks were created to revolve that focused one items important to historical china. A stealth component was also included to help break up the activities within the game and provide a unique challenge.
Finally, a minimap was also requested by the students. Maps became larger and more complex, and having this helped to provide direction.
This was the first time I led a team of constantly changing team members. It required managing individuals with completely different skill sets in order to produce a single result. I learned a lot about time management, curation of ideas, and when and where to maintain expectations.
Last year the implementation for the China Alive game was released a tested among students. Anonymous information was obtained using Google Analytics which described when users performed tasks, which characters they would talk to, and how long it took them to complete the game.
Since then, bug fixes and minor enhancements have been the focus.
Current Implementation: http://chinaalive.ualberta.ca/wp-content/iframes/VillageGameLocal/China/
Source code can be found here: https://github.com/arcualberta/VillageGameLocal
Adapters are used to provide this functional bases for the game engine. Modules used for this game are audio, display, control, physics, map input/output and the dialog system. The modules can be swapped out based on the capabilities of the browser and device being used. For example, the display modules first checks if the browser supports WebGL 2; if it does not, then it will fail over to a WebGL based module; and finally, a regular 2d canvas based adapter if needed.
Event Objects are objects that can have events attached to them. The events could be frame ticks, drawing, click actions, or if a character has interacted with them. Everything within the game including maps, sprites, menus, minigames, and triggers are event objects. The drawing is an event, not all event objects need to react to this event; therefore, not all event objects must be drawn. Event objects can also contain child Event objects; this allows for events to be recursively triggered on the child objects.
The Game Loop
Just like most games, the majority of the work happens between each frame and is used to calculate the next frame and then draws it. This is done over and over again, until the users has left the game. Here is the general loop for this game:
- Read all of the controller events.
- Perform any click events that were recorded.
- If any menus are displayed perform the “tick” event on them.
- If the game is not in the “Paused” state:
- For all objects currently alive in the scene, perform the “tick” event.
- If any objects have collided with other objects, perform the “interacted” event.
- One event that is a subcategory of the “interacted” event is the “talk” event. This happens when the player initiates a conversation with the object.
- Perform the “tick” event on the Heads Up Display (HUD)
- Calculate the drawable portion of the map.
- Calculate any objects that are currently visible.
- Draw all visible tiles below the object layer.
- Draw all visible objects.
- Draw all visible tiles above the object layer.
- Draw the the HUD.
Drawing and the Trouble With Arrays
Within this application, drawing each frame is the most computationally expensive aspect. We must calculate what is visible to the player, and only draw what is necessary.
Calculating what is visible to the player begins when we first load the scene. Each map has multiple layers which can contain either tiles or objects. Each of these contain information about their location on the map and their size. Once loaded, we store all of the information for a layer using a Quad tree data structure. Each node on the quad-tree recursively contains exactly four mutually exclusive area nodes contained with this parent. This allows us to create an easily searchable tree to store these drawable items based on their containing area within the map.
Each frame we search this quad-tree for all elements within the visible area of the camera. For every drawable element found, we add this to an array that will eventually be sent to the Display Adapter for drawing.
When initially drawing the maps, depending on how many layers and objects were used, the frames per second(FPS) could slow to even the single digits. To help solve this, in both the drawing functions and anywhere else in the code that this may pop up, the following rules were used:
- Whenever possible reuse arrays.
- If it’s needed to clear the array, do the following “array.length = 0”
- Keep all the memory allocated for the array, making it quicker to add new items.
- One thing to note, in most browsers this means that all objects in the array are still being referenced until they are overwritten, so garbage collection will not free this memory until then.
- Whenever possible use “Typed” arrays.
- This are arrays that only accept specific primitive types(such as floats or uints).
- Usually browsers contain low level support for these arrays making them much faster.
By implementing these tweeks, it was possible to have the game run at 60FPS on most desktop systems and 30FPS on most mobile. Because of this, the game was set to run at 30FPS.
It turned out that this method was very expensive, and dropped the frame rate dramatically; even on powerful desktop machines. It was essentially doing the most computationally expensive aspect of the draw call, but with a larger area.
One aspect that was requested late in the project’s life cycle was to include a map in top right corner of the screen. This map would show a slightly larger area than what’s visible to the player, and would update dynamically. To do this, may of the same aspects for the drawing loop were used; such as finding what tiles were visible within this area. When it came to drawing, each tile was assigned a specific colour and drawn to a texture as a single pixel. This was then drawn to the screen.
To solve this, I implemented a method that would update only a quarter of the map with each frame. The first frame would update the upper left corner of the map; the next frame the upper right; then the bottom right; then finally the bottom left.
This solution proved to work quite well and was difficult to notice; unless you were looking for it.
Threading Using Web Workers
The initial system calculated the visible area for the map using these web workers. They would contain all of the information of the maps and provide the main thread with all of the visible elements.
This ended up being highly inefficient. The worker was called at the beginning of the frame by passing in the current map state. It would then provide the needed tiles by the end of the frame in order to draw them. This thread had to return these elements due to the fact that these workers were restricted from drawing anything to the screen. Because of this, the process of sending the data back and forth between main thread and the worker cost more than just doing these calculations on the main thread. Due to this, web workers were abandoned.
What am I proud of?
This project has been a passion project of mine for almost six years. In that time I found a lot to be proud of. I was able to produce a concept, see it evolve with input and hard work from others, and eventually become a fully functional game. I learned how to manage teams of ever changing individuals and tasks. Most importantly I was able to meet and work with some highly talented individuals, who still surprise me with the quality of work they produce.