Design Document
Overall Game Design
While developing Splat, our code changed form many, many times. In particular, from the demo to the final presentation our code had to be modified to handle our own collision detection, launching of different projectiles, different types of pickups, different types of weapons for the NPCs, and different NPCs.
We debated for some time whether we wanted to make the game based on a timer, or the number kills the gamer had, or perhaps a combination of both. In the end we decided to use 5 kills as a criterion, and then Nelson (big boss) would be the target. If Nelson was defeated, then the player would win the game. Otherwise, if the player lost all three lives, they would lose.
We wanted Picasso to be able to attack by more than one method (crap), so we decided Picasso needed some way of obtaining weapons. Since Picasso needed some sort of weapons and a way to fill up his stomach, we needed to add and scatter different pickups around the city. This included food (pizza, chili, and cheeseburgers), and projectiles (pebbles, stones, cigarette butts). We also decided to add armor and medicine packs to help the player recover lost health and armor.
The gamer can move Picasso anywhere within the invisible bounding box, which is slightly larger than the size of the city. We did this so the gamer would not try to waste their time to try to find something of interest out in the world, because there isn’t anything of interest beyond the border of the city. It also allowed us to have a fixed sky box that didn’t have to move with Picasso.
As for the game screen, we wanted the user to know the current game statistics, without having to think about it at all. We made all the status bars large, in bold colors, and very easy to read.
The point of our game was to have fun playing, and also creating the game. So we tried to take into consideration whatever creative idea we came across. This resulted in many, many cool ideas for the game. Unfortunately, we neither had the time nor the artistic skills to accomplish everything we wanted. Despite these setbacks, we did manage to accomplish a lot of things. In particular, we wanted a game that allowed unrestricted 3D motion (in the city), as well as attacking anytime or anything.
We realize that accomplishing this sort of feat is near
impossible, and we have earned great respect for video game companies that do
actually do this. We knew in the beginning that trying to do this would be
hard; but we decided to go ahead and try anyway.
Game Flow
Intro
Setup – basic Dark Basic Pro settings
Declare Types – our own declared types
Declare Globals – for
convenience, most of our variables were globally declared
Loading of objects (NPCs,
Pickups, Picasso, City, ground, sky)
Loading of sounds/images
Setup bounding box, sky box
Initialize - the objects, create the necessary arrays,
init. diff. counter vars
//game loop
do
NPC_AI // run through the Array of NPCs and call their corr. AIs
Move_objects // move
the current projectiles/bullets in the world
Check_keys //
player movements
Detect
Collisions // bulk of the game is here
Update Vars // update
certain global vars
Every
300 frames, show the pickups that are hidden
if (Picasso lives = 0) or (nelson killed)
exit
loop
win or lose game screen
outro
Initially, we tried to accomplish too many things given the time we had to complete the game. One of these tasks was trying to develop different camera views. We had 4 different camera setups for the demo, but we decided that the 3rd person camera was the best one to use. We also have a small “Crap Cam” in the upper right corner of the screen to show whether an enemy is directly below Picasso. The crosshair will turn red if the enemy is directly below Picasso. This was intended to be both a way to assist in the game play, and also to make the game fun by showing the NPC being attacked.
We also realized very quickly that collision detection would be a problem between most objects. DarkBASIC’s collision detection with the city, which was exported from Cartography Shop as one big .X file, was worthless. We decided to focus on the collision between Picasso and the city, invisible bounding box, and ground. Since the focus of the player’s attention is what’s on the screen, and the camera follows Picasso, this made the most sense. We also focused on Nelson’s motion and collision detection, which is described in Nelson_AI. As for the other NPCs, we decided not to allow them to walk into the street, and used a bounding circle to restrict their motions.
Data Structures
Dark Basic Pro (DBP) provides many data structures and utilities, but as we (and most of the class) found out, they don’t always work as they claim. In particular, we had problems with the Array/Queue/List structure DBP provides. When we delete an element from the queue, we found that the DBP did not compress the list and actually remove it (so we would get illegal access errors when we weren’t supposed to). To overcome this (and other problems) we had to add our own variables to our own declared types to do our own existence checking of objects.
We generalized the NPCs by providing a type providing all the necessary information needed to handle the NPC AIs. What this generalized NPC type allows us to do is not change the overall structure of the game if we want to add an NPC; rather we just have to add the NPC type and its corresponding AI to the code, and modify the NPC_AI function to add the case for the new NPC type.
Picasso also had his own type, which helped make the code more readable and easy to modify. For instance, when we want to re-spawn Picasso or check his position, we just access his corresponding position members.
We also provided structures to describe the weapons (projectiles). This allowed us to call launch( ) or shoot( ) while passing the weapon type as a variable. This allowed code reuse for all the launching and shooting or projectiles/weapons.
Pickups were also necessary for the game play, and we also created a type for them as well. This was mainly used for placing them and also for respawning them.
Since our game utilized many sprites, we had to declare constants for each of them.
We also declared several arrays to provide important information, such as:
Please see Appendix A for the actual declared
variables (taken directly from the source code).
Algorithms
NPC_AI
Run through the array of NPCs
If the npc is alive (not killed) then call its corresponding AI
Else
Check the fade value for the NPC
Fade the object to let the player know its dead
Nelson_AI
Nelson’s AI was implemented using a small number of If/then statements, and keeping track of his current state in his global NPC type. In our demo, we had Nelson roaming around, and if Picasso happened to be within range of Nelson, then Nelson would hunt Picasso. If Picasso got out of Nelsons range, then he would go back to randomly roaming the landscape.
This obviously would not be sufficient for the new terrain, since we had spent a lot of time to develop a city for the game action to take place in.
So, we decided to implement Nelson as follows:
Initially he would be placed in a corner of the city. When Picasso had 5 kills, Nelson would become active.
State 1:
Check forward, 90 deg left, 90 deg right to see how far the bounding box was.
Of all the viable options, randomly pick one.
Go to state 2.
State 2:
For a certain number of frames, move in that direction.
If colliding with the bounding box, then go to state 1 to pick another direction.
State 4:
Picasso is within range. Shoot a bullet
Stop moving and shoot at Picasso.
Ninja_AI & Kid_AI
We decided to use the same AI for the Ninja and Kid because of the lack of time.
Because we tried to generalize functions, this worked out nicely because all we had to change was to detect what type the NPC was, and launch a ninja star or bee bee gun pellet based on the type.
The main idea of the AI is to roam in a certain circle, centered at the starting position of the NPC. When Picasso became in range, then they would shoot the corresponding weapon at Picasso. If the NPC came to the border of their bounding circle, then they would turn around and then randomly turn slightly to the left or right to make the motion appear not so fixed.
Shoot
This would check the type of the NPC that was passed in as a variable, and then check the last frame it shot. If the corresponding launch_interval passed, and if Picasso is within the line of sight from the NPC, then shoot_bullet would be called. Otherwise the function would do nothing.
Shoot bullet
This will create a new bullet object, and based on the NPC type it would load the appropriate weapon model. It will place it at the NPCs position and then set collision detection on for the object. It will also be pointed toward Picasso.
Then the last_shot field will be updated.
Move objects
Cycle through the Bullets and Projectiles arrays, and if the exist variable is 1, then move the object forward the corresponding number of units.
Launch
This function is called when Picasso wants to either crap or drop whatever he is holding in his right, left claw. The process is very similar to the Shoot_bullet function, except that the object is added to the projectiles array, and is positioned at Picasso’s current position and pointed straight down.
Check_keys
This function checks to see what keys are being pressed by the user
‘s’ -- launch a super crap
‘c’ -- launch a normal crap
‘z’ -- launch whatever is in the left claw
‘x’ -- launch whatever is in the right claw
In order to prevent accidental launches, we had to implement an overall game “frame” counter. So each NPC and Picasso had a member variable called “launch_interval” and “last_launched” which would keep track of how often an action could be taken.
Left shift key -- move up
Left control key -- move down
Up arrow key -- move forward
We decided to prevent the user from moving backwards, since this would be too unrealistic. Originally we had Picasso move up and down using DBPs “pitch object” function, but this created an unpredictable camera, so instead we decided to use “move object up.”
Another note here is that we wanted to have all the keys clustered together so the user did not have to stretch or strain their hands to awkward positions.
‘q’ -- quit
‘p’ -- pause
We decided to disable the ‘esc’ key and use our own quitting method because if the escape key was pressed accidentally, then the entire game would be lost.
We thought the pause we would be nice in case the user had to leave the computer for whatever reason.
Detect collisions
(This does the bulk of the processing)
First we cycle through the bullets in the world. If they collide with Picasso, then we deduct the appropriate health/armor from Picasso and then disable the bullet. Otherwise if it hits the city or bounding box, we simply destroy it.
Then we cycle through the projectiles in the world. We check to see if it hits any NPC, and deduct the points from the NPCs health. If the projectile hits anything else, then we just destroy it.
Now we check Picasso’s collisions.
We built a modified model of Picasso specifically so we could perform collision detection. There are 7 small limbs (spheres) surrounding Picasso. These are hidden during the game, but they are still there nonetheless. The point of the spheres being there is to create rays shooting out of Picasso’s center to each of the spheres. These rays in turn can be used to detect collisions in any of the directions Picasso can move. Doing this allows us to properly detect collisions. The seven limbs appear at distinct locations: one at each wingtip, one at each point between wingtip and beak, one at the tip of the beak, one above, and one below. There are no limbs behind Picasso because he cannot move backwards.
We also check between Picasso and the Pickups. Instead of using line of sight collision detection, we used a simpler distance measure to determine if Picasso is within range of a pickup. When a pickup is picked up, then we hide it and also add the appropriate health, food, armor or add it to the left or right claw as appropriate (this is mainly done in get_pickup).
There are also other helper functions that enable code reuse, which simply are not worth mentioning how they work here because they are trivial.
Appendix A - Selected Portions of subroutine
_declared_types
rem subroutine that declares
our defined types
rem BEGIN _declared_types
_declared_types:
rem
| make an array of strings for the file names
dim filenames$(12)
filenames$(BULLET)
= "models/bullet.x"
filenames$(CRAP) =
"models/crap.x"
filenames$(PEBBLE)
= "models/pebble.x"
filenames$(STONE) =
"models/pebble.x"
filenames$(CIGARETTE)
= "models/cigarette.x"
filenames$(COCONUT)
= "models/coconut.x"
filenames$(SMED) =
"models/health.x"
filenames$(LMED) = "models/health.x"
filenames$(ARMOR) =
"models/armor.x"
filenames$(FOOD1) =
"models/food1.x"
filenames$(FOOD2) =
"models/food2.x"
filenames$(FOOD3) =
"models/food3.x"
rem
| make an array to hold the scaling values for each type of projectile
dim proj_scal(15)
proj_scal(BULLET) = 2000
proj_scal(CRAP) = 2000
proj_scal(PEBBLE) = 600
proj_scal(STONE) = 900
proj_scal(CIGARETTE) = 400
proj_scal(COCONUT) = 1500
proj_scal(SMED) = 2000
proj_scal(LMED) = 3500
proj_scal(ARMOR) = 2500
proj_scal(FOOD1) = 3500
proj_scal(FOOD2) = 3500
proj_scal(FOOD3) = 3500
proj_scal(HUMMER) = 1000
proj_scal(KID) = 500
proj_scal(NINJA) = 500
rem
| make an array to hold the point values for each type of projectile
dim proj_pt(6)
proj_pt(BULLET) = 75
proj_pt(CRAP) = 15
proj_pt(PEBBLE) = 10
proj_pt(STONE) = 15
proj_pt(CIGARETTE) = 20
proj_pt(COCONUT) = 45
rem
| NPC is used to keep track of npcs
type NPC
state as
integer rem current state
in
tx
as float rem
target coords
ty
as float
tz
as float
cx
as float rem
current coords
cy as float
cz
as float
speed as
integer rem
speed of particular npc
roam as integer rem the max
distance npc will roam from curr
pos
fhealth
as integer
health as
integer
shot_at
as integer
radius as float
obj_no
as integer
ntype
as integer ` type of NPC
lshot
as double integer `last frame shot
shoot_int
as double integer `shoot interval
alive as
integer `if picasso
killed him or not
min_shoot_dist
as integer
start as coord
fade as integer
health_bar
as integer
endtype
rem
| used for bullets, projectiles
type proj
tx
as float rem
target coords
ty
as float
tz
as float
cx
as float rem
current coords
cy as float
cz
as float
obj_no
as integer
exist as integer
ptype
as integer ` proj
type
endtype
type pickup
show as
integer `if its visible or
not
lasthide
as double integer `last frame it was
hidden - used for reshowing obj
obj_no
as integer
ptype
as integer `type of pickup
c as coord
`location of pickup
health as
integer
armor as integer
food as integer
endtype
rem
| used to keep track of picasso
type bird
cx
as float rem
| current coords
cy as float
cz
as float
ox as float rem | prev coords
oy
as float
oz as float
tx
as float rem
| current direction
ty
as float
tz
as float
move as
integer rem |
how much to move picasso by
move2 as
integer rem |
how much picasso wants to move up or down
radius as float
angle as float
pitch as float
health as
integer
armor as integer
speed as float
points as
integer
lives as integer
kills as integer
stomach as
integer
l_claw
as integer
r_claw
as integer
launched as
double integer
launch_int
as double integer
endtype
return
rem END _declared_types