The final game design takes a drastic departure from the original gameplay design document and envisioned product. The gameplay design doucment had intended for the creation of a 2D side-scrolling game. Due to poor response during the Prototype, Syzware decided to completely change the perspective and interaction of the game, and created a 3D game environment. This final game design doucment reflect those changes.
More animations were desired, including animations of the victim fish dying, getting hurt, and spitting at the player. Better animations of Bubbles getting hurt and dying were also intended. Unfortunately, time and lack of experience with 3D modeling were the main reasons why this was not achieved.
sound effects for other aspects of the game were desired. Currently, only sound exists for the player, Bubbles, when Bubbles reacts to the environment. Sound effects for aspects of the non-player characters was desired. Again, time was the main constraint.
Other types of fish and fish tank life were originally envisioned. These includedd snails, frogs, and star fish. Once, again time was a major constraint.
Ohter types of static fish tank models were originally envisioned, such as underwater divers and treasure chests, but again time prevented this from occuring.
Another of the elements that was originally envisioned for the game was the inclusion of a cat's paw and little boy's hand that the player would have to dodge. This was left as a low priority and was never achieved for the same reasons as the others above.
Our primary goal in the design phase can be outlined into three interdependent structures: the I/O structure, the game structure, and the program structure. The I/O structure is the system that communicates information between the computer and the player. The game structure is the internal architecture to build the game around some key elements identified from the topic environment. In other words, it is how the AI is presented in the game. The program structure is the organization of mainline code, subroutines, state machine, and data structure that make up the entire program.
In the following sections, we will explain more detail of the design of Homicidal Goldfish on each structure mentioned above.
I/O is composed of input and output. The computer has two means of output to the player: graphics and sound. These two means sometimes also refer as Model. As we know game design is a combination of artistic and technical process. The model displays an artistic look to the player. In this game, we present a cartoonish theme throughout all our models (both graphics and sound) to give players a relaxing, humorous and entertaining feeling during their play of the game. For instance, when Bubbles got hit by the enemy fish, we will play the animation of Bubbles’ eye popping out with an “ouch” sound to show its angry; we use the 3D “water and air” surrounding sound with all other water plants give a great aquarium effect; and we play “woohoo” sound to depict the Bubbles’ happiness when he catches the life saving “Heart” or speed accelerating “Pill”. More graphics can be seen in the “Screenshots of the actual game” section. As for the input, we use a combination of keyboard and mouse in a very intuitive way – using mouse for selecting the menu; spacebar for shooting bullets; and arrow keys in conjunction with the shift key to give a player full control of Bubbles orientation.
Here we need to identify some key elements from the topic environment and build the game around those key elements through the internal architecture. The key element represents a symbolic issue addressed in the game. In our game, our main issue is to focus on “Bubbles’ revenge” or “Homicidal”. Keeping these in mind, we design our game AI (i.e. combat algorithms) in the advantage of Bubbles to show its vengeance and homicidal rage. For instance, Bubbles can accelerate speed after it catches a “Pill”, or save a life after it catches a “Heart”; Enemy fishes have little AI fighting with Bubbles. For more detail about our game AI, please refer to the “How the AI was implemented” section.
This structure is the vehicle that translates the I/O structure and Game Structure into a real playable game. In the following we will explore our program structure in terms of data structures, simulation constancy, game loop and state machines.
We use a C/C++ like struct type of data structure to define each main object in the aquarium. By using the struct type, not only does it help to make the code more readable, but also easier to modify or extend, because each object is self-contained and cohesive by itself but loosely coupled with each other. Changing on one object give no or little effect on the other. Besides, by using a struct type, we can have each object containing its own states. This can help us easily keep track of their states during developing the state machine. The state machine development in our game will be covered more in the subsequent section. The main object and its data type is shown as follows:
Struct type for Bubbles fish:
type BubblesType objNum state$ bx# by# bz# angleX# angleY# angleZ# speed hit_wall hit health energyTime# points count endtype global Bubbles as BubblesType
Struct type for Enemy fishes:
type enemyFishType objNum state$ up down x# y# z# hit dead count endtype dim enemyfishArr(numEnemyfish) as enemyFishType
Struct for Drop object:
type dropObjectType objNum state$ dropTime# endtype global dropObject as dropObjectType global healthObject as dropObjectType
Struct for Level Boss fish:
type bossType objNum state$ showTime# x# y# z# show up down count hit dead health endtype global levelBoss as bossType
Another data structure used in our game is Array. Array helps store multiple instances of the same object type. In our game, we have two arrays of Enemy fishes - one array of non-defensive enemy fishes (enemyfishArr1(numEnemyfish1)) and one array of defensive enemy fish (enemyfishArr2(numEnemyfish2)). Another array is used for storing defensive enemy fish’s bullets (ene2_bubblesArr(numEnemyfish2*5)). As you can see we use a variable (numEnemyfish1, numEnemyfish2) to define array size. By this approach, we can load as many as enemy fishes (without considering performance) in the aquarium by just adjusting these two variables without modifying any other codes since all calculations in enemy fish subroutines are based on these variables. Below is the graphic view of Enemy fish2 and its associated bullets (when numEnemyfish2 = 6):
Position data structure is used to position the initial location of some objects such as enemy fishes and aquarium plants. By doing this, we can avoid the unnecessary collisions caused by the randomly generated positions in the aquarium if we choose do so. A sample of position data structure for enemyfish1 is shown as follows:
enemyfish1PosData: data -300,250,200 data 420,350,500 data -100,400,-600 data 50,230,200 data -200,150,-300 data -350,180,-100
In order to make sure our objects in the aquarium moves through the scene at the same speed no matter how fast the CPU is, we compute all our object speed based on a cycle time and a moving average of cycle time is implemented in our game to avoid the jitter due to the sudden change of cycle time.
Since this is a game with a moderate computation, we decide not to implement it with multiple threads. Instead, we just have one loop in one thread. Since we have only one thread (or process), there is no need to worry about any mutex or deadlock. Everything runs sequentially which reduce the unnecessary complexity of the game.
State machine is used on almost every object. Based on the object current state that is stored in its own data structure, and the current event acted on the object, the object will response with some activities and progress to the next state. As a matter of fact, our game itself is in a state machine. Below are the state diagrams that depict how the state machine works in our game.
The machine for the game itself:
The corresponding code:
do
select game_state$
case "GameState.GAME_INIT"
Bubbles.state$="BubblesState.BUBBLES_INIT"
…
game_state$="GameState.GAME_INPROGRESS"
endcase
case "GameState.GAME_INPROGRESS"
cycle_time#=ComputeCycleTime(0)
gosub _control_Bubbles
…
endcase
case "GameState.GAME_OVER"
counter=counter+1
if counter=50
game_state$="GameState.GAME_INIT"
counter=0
…
endcase
endselect
Rem Update screen
sync
loop
end
The machine for Bubbles:
The machine for Bubbles' bubble:
The machine for the Green Victim Goldfish:
The machine for the Purple Victim Goldfish:
The machine for the Victim Goldfish's bubble:
The machine for the Level Boss:
The machine for the Level Boss' bubble:
The machine for health power-up:
The machine for speed power-up:
The initial position of the green victim goldfish is predetermined. The movement of the green victim goldfish in the xz-plane is determined as follows: The fish moves forward until it hits an obstacle. This is determined by DarkBASIC's collision detection. An obstacle is considered to be the walls of the tank, another victim goldfish, the Level Boss, or Bubbles. When this occurs it rotates along its y-axis by a random amount, and continues moving forward until it collides with something else. Movement in the xy-plane is determined as follows: The fish moves down slowly until it reaches the bottom of the tank. It then moves up until it reaches the top of the tank. The process repeats. Green victim goldfish spit up to five bubbles at a time when they are moving forward.
The initial position of the purple victim goldfish is predetermined. The movement of the purple victim goldfish in the xz-plane is determined as follows: The fish moves forward until it hits an obstacle. This is determined by DarkBASIC's collision detection. An obstacle is considered to be the walls of the tank, another victim goldfish, the Level Boss, or Bubbles. When this occurs it rotates along its y-axis by a random amount, and continues moving forward until it collides with something else. Movement in the xy-plane is determined as follows: The fish moves down slowly until it reaches the bottom of the tank. It then moves up until it reaches the top of the tank. The process repeats. Purple victim goldfish do not spit bubbles.
The initial position of the level boss is predetermined. The movement of the level boss in the xz-plane is determined as follows: The boss moves forward until it hits an obstacle. This is determined by DarkBASIC's collision detection. An obstacle is considered to be the walls of the tank, another victim goldfish, or Bubbles. When this occurs it rotates along its y-axis by a random amount, and continues moving forward until it collides with something else. Movement in the xy-plane is determined as follows: The boss moves down slowly until it reaches the bottom of the tank. It then moves up until it reaches the top of the tank. The process repeats. The level boss spits up to five bubbles at a time when it is moving forward. It spits them more often than do the green victim goldfish.
The fish tank is made from six box objects for the walls, ceiling, and floor. The walls and ceiling are textured with a water-like texture, and the floor is textured with a gravel texture.
The tank also has invisible walls that are used for collision detection. Five such walls exists inside of the four side walls just below the ceiling. The distance from the invisible walls to the visible ones is the distance between the camera and Bubbles. This is implemented in order to solve an issue related to a flaw in DarkBASIC's collision detection. When collison detection is set on for the camera, the camera may sometimes get stuck in corners and not follow the player. If collison detection on the camera is turned off, the camera may go outside of the tank if the player gets too close to the walls and turns around. So, as a solution, auto collision detection is turned off on the cameras, but left on the player. This way, Bubbles can't go through the invisible walls but the camera can so it doesn't go outside the tank and doesn't get stuck.
The 'Chicken Dance' is played as background music throughout the game.
The appearance of being underwater is achieved by adding blue fog. This is done by using the following code.
fog on fog color rgb(0,0,255)
The two air stones (aka bubblers) at opposite side of the tank are created with two particle systems. The particles are assigned a texture. The following code shows the commands and settings used.
make particles 1, 1100, 10, 100 position particles 1, -260, 60, -750 set particle emissions 1, 1 set particle speed 1, 0.005 set particle gravity 1, -2 set particle velocity 1, 2 set particle chaos 1, 10
Both air stones have a 3D position sound associated with them. This adds depth and a sense of position to the game. The sound was setup code similar to the following.
load 3dsound "media\sounds\air.wav", sound_air1 position sound sound_air1, -260, 60, -750 loop sound sound_air1
In order for the sound to be in the proper place relative to the player's position the position of the listener must be updated. It is done with the following code called once per every iteration of the game loop.
position listener object position x(Bubbles.objNum),
object position y(Bubbles.objNum),
object position z(Bubbles.objNum)
All rocks are 3D models with textures. All plants are untextured 3D models.
Bubbles is modeled as a series of 3D .X objects. One is for Bubbles swimming unhurt. In this case Bubble's tail fin moves from side-to-side. When Bubbles gets hurt, Bubble's eyes momentarily come out of its head and pop back in. This is all accomplished in the .X file.
Three separate sounds are associated with four events that happen to Bubbles. The first is when Bubbles spits a bubble. This triggers and 'plop' sound. Second is when Bubbles gets hurt. This triggers an 'ouch' sound. The third is when Bubbles gets either of the power-ups for health or speed. These trigger the same 'woohoo' sound.
The two types of victim goldfish are modeled as 3D .X objects. Neither use textures for appearance. The model for the fish consists of a swimming animation with the tail fin moving side-to-side. When victim goldfish die they float to the top of the tank. This is achieved by changing the position of the model.
No audio effects are associated with the victim goldfish.
The level boss is modeled as a 3D .X object with separate textures for its body and each type of fin. The model for the boss consists of a swimming animation with the tail fin moving side-to-side. When the boss dies it floats to the bottom of the tank. This is achieved by changing the position of the model.
No audio effects are associated with the level boss.
The health indicators at the top left and right of the screen is implemented as sprites that are placed and removed from the screen.
The bubbles that the fish spit as weapons are achieved by creating sphere objects and moving their position.
Both the health (heart shaped) and speed (pill shaped) power-ups are implemented in the same way. They are 3D models that are dropped from the top and translated to the bottom where they sit for a little while before disappearing.