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  andlast_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