Section 1 - Concept
Section 1.1 - Prompt
For this project, I was using the
Royal Society for Art's Student Design Awards Brief 9
as a starting point to then gamify. The original brief is to create an animation that will accompany
one of two potentional audio files, Change is Necessary by Sarah Ichioka or Interrogate the Truth by Jeffrey Boakye.
My first thought was of kinetic typography, which is an animation technique that mixes motion and text to express the
ideas. The most known example of kinetic typography is the opening crawl in the Star Wars movies, but it has also made
a resurgence in modern day "lyric videos" for songs (Figure 1.1.1). While doing research into listening comprehension and learning styles, I found that listening comprehension relies on both
visual and auditory input, so I came up with the idea to combine visual and auditory input with an interactive element to
promote understanding of the speech provided. The interactive element would incorporate a hands-on learning style, encouraging
players to not only understand what they're hearing but to learn from it as well.
Source 1
Source 2
Source 3
Figure 1.1.1 - From left to right, an example of kinetic typography in general,
the opening crawl from Star Wars, and kinetic typography for a lyric video
Section 1.2 - Game Design
In order to gamify the speech, I looked at platforming and bullet hell games, as I wanted to emulate kinetic typography within the platforming aspect. In addition, bullet hell games gave me a sense of the manageable chaos of dodging multiple projectiles at once, as in both audios, the presentors speak at a relatively quick speed with little pauses. Since I thought both messages were equally important, my deciding factor ended up being to pick the slower of the two,
Interrogate the Truth,
as I personally am very bad at dodging and bullet hell games so I figured the slower speed would allow me to actually play test the game myself. From there, I decided the projectiles and platforms would be the words being spoken, where the sides and bottoms of the words would be "dangerous" to touch and the tops of the words would be the safe platform the player can jump onto. In addition, I made a very quick animation mock-up to better illustrate my idea, as seen in Figure 1.2.1.
Figure 1.2.1 - The original game mock-up
Section 1.3 - Asset Design
Once I had the game idea planned and laid out, I needed to decide how I wanted it to look. Since I already knew I was going to create the game objects as simple rectangles with a flat top to make the platforming easier, I needed the words to have a similar flat top so the character didn't look like they were floating at times. Because of this, I decided the words would be in all caps and chose a serif font to create more of a bridge between letters. However, since serif fonts are not preferable to increase readability, I did research into the most accessible serif fonts and chose one of the top picks: Times New Roman.
For a very long time throughout the project, I was unsure of what to do character design wise. I had been working with a basic rectangle as a placeholder, and thought an intricate or colorful character would take away from the message too much. I also considered sticking with the rectangle, similar to indie puzzle-platformer Thomas was Alone, but thought that would be too confusing for players without any explanation as to why. I ended up looking through character sprites on the Unity Store and discovered a
simple greyscale hero asset
that fit well enough with the other assets in the game, and didn't draw any attention away from the obstacles.
Figure 1.3.1 - Thomas was Alone, an indie puzzle-platformer where the main character, Thomas, is a red rectangle.
Figure 1.3.2 - The word and character assets in game.
Section 2 - Creation
Section 2.1 - Basic Mechanics
When it came to actually creating the game, the first thing I did was whitebox and code the very basic mechanics. Since the game was going to be in 2D, for whiteboxing, all I needed to create was 4 rectangles to act as the floor, walls, and ceiling of the play area so the player can't get lost off screen. For movement, since I had never worked with Unity 2D before, I found tutorials online that coded movement using the AddForce script, so that's what I did for the project to better understand it. In addition, I used a boolean function alongside a trigger collider so the player can only jump once, so it would be an actual jump and not a jetpack-esque mechanic. However, I found that this method caused the character to seem too "floaty" and slide across the ground as if it was ice. While I knew there were other solutions, I found that simply increasing the gravity and Movement Speed/Jump Force created just the right amount of "floaty" and sliding that wouldn't throw the player off but still give more wiggle room to dodge the projectiles.
Section 2.2 - Procedural Generation
After that, my next step was to code the projectile/platforms or "words" as I referred to them. In order to add an element of difficulty, I wanted each word to spawn in a different location every time but I was concerned about the words "crushing" the player, creating a wall of obstacles there was no way out of, so I took to sketching my thoughts.
Figure 2.2.1 - 2 Sketches showcasing "zones" the words could occupy
In order to ensure the words general location and that there was always a gap for the player, I decided there would be 12 possible spawn locations and which one each word would spawn at was randomly generated. In addition, I used an InvokeRepeating function so I could change the rate of the words spawning to match the general pace of the speech. Within the Generate method, I generated a random integer between 1 and 12, and used that number to transform each word to the corresponding location.
void Generate()
{
randomNumber = Random.Range(1, 12);
if(randomNumber == 1)
{
Word.transform.position = RB.position;
WM.MoveLeft();
}
else if(randomNumber == 2)
{
Word.transform.position = RM.position;
WM.MoveLeft();
}
//Etc...
}
In order to find which word to transform, I created another integer, i, which started at 001. From there, I would convert the integer into a string and find the GameObject with the matching name to be the current Word. After each word was transformed, I increased the count of i so the next time the method was called, it would find GameObject 002, etc.
void Generate()
{
if (i <= 199) //To stop trying after its finished transforming all 199 words
{
randomNumber = Random.Range(1, 12);
Word = GameObject.Find(i.ToString());
if(randomNumber == 1)
{
Word.transform.position = RB.position;
WM.MoveLeft();
i++;
}
else if(randomNumber == 2)
{
Word.transform.position = RM.position;
WM.MoveLeft();
i++;
}
//Etc...
}
}
While this worked perfectly for spawning the words in order in random locations, the MoveLeft method wasn't working properly. As I was attempting to use the translate function to move the words, I found that I couldn't use it unless the code was on the word itself, and up until this point, all of the code was on an empty GameObject. When I tried to put the code onto each word, every word would generate its own random number and try to transform the same word to all 12 different locations at the same time, so I thought calling a Method on a different script attached to each word could solve the problem. However, the original generation script wanted to know which iteration of the WordMovement script to call the method in, and I couldn't find a way to tell it within the code. At this point, I had been racking my brain for ways around this, I attempted using the RigidBody's AddForce function but found that to have many similar issues and suggestions given by my instructors didn't lead to a desirable outcome. In an attempt to better understand my code and find a solution, I ended up sketching out flowcharts to better analyse it.
Figure 2.2.2 - A basic and more in-depth flowchart of the Generate and WordMovement scripts.
Eventually I abandoned the two scripts altogether and attempted to re-write the code. In doing so, I realised that there was no reason the number generating needed to be on the same script as the word transformation and I separated them into two. From here, I had a single script generating the number and a second script transforming and translating on each GameObject.
void Move()
{
if (i <= 199)
{
Word = GameObject.Find(i.ToString());
if(NG.randomNumber == 1)
{
Word.transform.position = RB.position;
MoveLeft();
i++;
}
else if(NG.randomNumber == 2)
{
Word.transform.position = RM.position;
MoveLeft();
i++
}
//Etc...
}
}
void MoveLeft()
{
if (check.name == i.ToString()){
InvokeRepeating("Left", 0f, 0.1f);
}
}
void Left()
{
transform.Translate(Vector2.left * wordSpeed);
}
Figure 2.2.3 - The result of the above code
While the words spawn randomly and move on and off the screen as they should, this has done nothing to solve my original problem of "crushing" the player. From here, I created 4 booleans to indicate which "slots" were open for words to spawn into, 2 for left and right, 2 for up and down. As each word transforms to its new location, it would set one of the booleans to true, and the boolean would only become false (and therefore "open" again) when the word left the screen. While I did figure out a very roundabout way of doing this using trigger colliders, after the first four words, all of the other words would get stuck. They would move onto the edge of the screen and then stop moving entirely. That is where the game is at as of now, however I do have ideas on how to get this up and working properly.