Survival shooter in Unity

We want to create an isometric shooter game called “Nightmares”. The player is a kid in his dream that have to defend with his infinite shooting rifle from zombie puppets.

Index

  • Download the package
  • Design of the game
    • Rough architecture
    • Agent abstraction
  • Tutorial2: Survival Shooter
    • Environment Setup
    • Player Character
    • Camera Setup
    • Creating Enemy #1
    • Health HUD
    • Player Health
    • Harming Enemies
    • Scoring Points
    • Spawning Enemies
    • Game Over
  • Extensions
    • Behaviour State Automata
    • Steering Behaviours
      • Leader Following
      • Following
      • Separation
      • Avoidance
    • Avoiding Player Shooting
    • Stochastic Behaviour
      • Searching for the leader

Download

This tutorial follows what has been done during Unite training day 20014.
For starter download the assets at the following link:
https://www.assetstore.unity3d.com/en/#!/content/21028

Also, you can follow the Unite2014 at the webpage:

http://unity3d.com/learn/tutorials/projects/survival-shooter

  • Unzip ‘All Unite Training Day Projects’
  • Open Unity, File > Open Project
  • Open ‘Unite 2014 Training Day’

Design of the game

Rough Architecture

concept schema 1

concept schema 2

Agent Abstraction

Who are the main agents and items of our game and how are we gonna define their behaviour?

Player

The player needs to move into the scene and fights enemy. It has his own health and dies when it reaches zero. We can distinguish the modules for the player behaviour:

  • Movement: The player needs to perceive the input from the keyboard and mouse to move and turn around into the scene.
  • Health: The player needs to know his health and die when it reaches zero.
  • Shooting: The player shoots with his rifle in the direction he’s facing, when he perceives the user input.

Camera

The camera follows the player around into the scene.

  • It perceives the player position and moves smoothly to follow him.

Enemy

The enemy needs to seek the player into the scene and attack him at short distance. The enemy will be fully autonomous and can potentially show a complex behaviour. His basic functionality are:

  • Perceiving the player into the scene and attacking him.
  • Moving into the scene.
  • Decreasing his health when it’s hit by the player bullets and die when it reaches zero.

Score Counter

It’s a pure passive component that has to count the user score to display on screen.

Tutorial2: Survival Shooter (Implementation)

Environment Setup

  1. Setup Editor Layout 2 by 3
  2. Drag Project tab below the Hierarchy
  3. Set view slider to minimum on Project panel
  4. File > New Scene
  5. File > Save Scene As, name it Level 01 in Scenes folder

Now we need to add prefabs:

  1. Locate Environment prefab in the Project panel Prefabs folder
  2. Drag into Scene or Hierarchy
  3. Ensure it is at Position (0, 0, 0) in Transform
  4. Repeat 1-3 for the Lights prefab
  5. Save your Scene (CMD-S / CTRL-S)

Right on! Now let's continue

  1. GameObject menu > 3D Object > Quad
  2. Rename to Floor (Return / F2)
  3. Ensure it is at Position (0, 0, 0) in Transform
  4. Set Rotation (90, 0, 0) in Transform
  5. Set Scale to (100, 100, 1) in Transform

Let's now make the floor invisible.

  1. Remove Mesh Renderer Component from the Floor game object
    * This will leave only the collider
  2. Set the Floor game object to use the Floor layer at the top of the Inspector panel
    * this will later be used to identify the floor in our script
  3. Save your Scene (CMD-S / CTRL/S)

>Optional for bg music

  1. GameObject > Create Empty
  2. Rename GameObject to BackgroundMusic
  3. Add Component > Audio > Audio Source
  4. Audio Clip > Circle Select > Background Music
  5. Check Loop and set Volume to 0.1
  6. Save your Scene (CMD-S / CTRL/S)

Player Character

  1. Locate the Player model in Models > Characters folder of the Project panel
  2. Drag it into the Scene or Hierarchy panels
  3. Set the Position to (0, 0, 0) in Transform
  4. Set the Tag to Player in the drop-down in Inspector
Animation (FSM)
  1. Select the Animation folder and click Create on the Project panel, choose Animator Controller
  2. Name this new asset PlayerAC
  3. Drag and drop it onto the Player in the Hierarchy
  4. Double-click PlayerAC asset in Project > Animation
  5. Dock the Animator window by the Scene view

Now let's play with the animation fsm view.

  1. Expand the Player model in Models > Characters
  2. Drag the Idle, Move and Death animations to empty space in the Animator window to create states
  3. Right-click Idle state and choose Set as Default
  4. Create a bool parameter named IsWalking
  5. Create a Trigger parameter named Die

Now let's create a transition.

  1. Right-click Idle and Make Transition to Move
  2. Select the Transition arrow you made
  3. Set the Condition for this to IsWalking = true
  4. Right-click Move and Make Transition to Idle
  5. Set the Condition for this to IsWalking = false
  6. Right-click ‘Any State’ and Make Transition to Death
  7. Set the Condition for this to Die (trigger)

fsm animation view

And finally we update the player.

  1. Select Player game object, Add Component > Physics > Rigidbody
  2. Set Drag & Angular Drag to Infinity
    • cheap trick to stop some not needed forces generated by the physic engine
  3. Expand the Constraints, Freeze the Y Position, and Freeze the X and Z Rotations

Fix the player position.

  1. Select Player game object, Add Component > Physics > Capsule Collider
  2. Set Center to (0.2, 0.6, 0)
  3. Set Height to 1.2

Add the audio and update the script...

  1. Add Component > Audio > Audio Source
  2. Audio Clip > Circle Select Player Hurt
  3. Uncheck Play On Awake
  4. Locate PlayerMovement script in Scripts > Player
  5. Drag & Drop this to Player game object in Hierarchy
  6. Save your Scene (CMD-S / CTRL/S)
  7. Open the PlayerMovement script

Add needed variables to the script, public variables are visible in the inspector.

Initialize the variable within Monobehaviour.Awake() function.

First thing we add to our FixedUpdate() function is the perception of the input.
Then we proceed to write the Move() function that exploits the player rigidbody to make it move into the scene. It’s important to fixate the movement in time, using Time.deltatime.

Since the sum of the two movement vector can be greater than 1 and we don’t want our player to move faster diagonally we have to normalize it afterward.

We’ll now focus on the turning function that rotates the player to face the point the mouse is currently hold on screen.

We can perceive the point where our mouse pointer is currently on into the scene by drawing a ray that from the camera is directed to the mouse position, then using a raycast we can find the point from the intersection of the ray and the Floor layer.

Let’s write the animation function.

It's time to test your game !

Camera Setup

  1. Select the Main Camera in Hierarchy
  2. Set the Transform Position to (1, 15, -22)
  3. Set the Transform Rotation to (30, 0, 0)
  4. In the Camera component set the Projection to Orthographic mode
  5. Set the Size value to 4.5

  6. Set Background Color to Black

  7. Save your Scene (CMD-S / CTRL/S)
  8. Select the Camera folder in the Scripts folder of the Project panel
  9. Click Create > C# Script on Project panel
  10. Name the script CameraFollow

  11. Drag and drop the CameraFollow script onto the Main Camera in Hierarchy

  12. Save your scene now!
  13. Click Open at the top of the Inspector or double-click the script’s icon to open it for editing

Let’s start by defining our variables. We want our camera to smoothly follow the player by having a distance given by an offset vector. We get the distance from the difference that holds between the target and the camera at design time.

We use Vector3.Lerp function to smoothly move the camera.

  1. Save your Script, and return to Unity
  2. Select the MainCamera and assign Player from the Hierarchy to the Target variable on CameraFollow
  3. Save your Player as a Prefab by dragging the Player game object from Hierarchy to the Prefabs folder in the Project. Save, and press Play to test!

Creating Enemy #1

  1. Locate the Zombunny model in the Models > Characters folder in the Project
  2. Drag and Drop the model into the Scene
  3. Find HitParticles in the Prefabs folder, drag and drop this onto the Zombunny in the Hierarchy
  4. Choose the Shootable layer for this game object
Ridigbody
  1. Add Component > Physics > Rigidbody
  2. Set Drag & Angular Drag to Infinity
  3. In Constraints Freeze Position Y and Freeze Rotation X and Z
  4. Add Component > Physics > Capsule Collider
  5. Set Center Y to 0.8 and Height to 1.5
Collider
  1. Add Component > Physics > Sphere Collider
  2. Check the Is Trigger box
  3. Set Center Y and Radius both to 0.8
  4. Add Component > Audio > Audio Source
  5. Circle select the Zombunny Hurt audio clip
  6. Uncheck the Play On Awake box
  1. Add Component > Navigation > Nav Mesh Agent
  2. Set Radius to 0.3
  3. Set Speed to 3
  4. Set Stopping Distance to 1.3
  5. Set Height to 1.1

  6. Go to Window > Navigation and dock it

  7. Choose the Bake tab at the top
  8. Set Radius to 0.75
  9. Set Height to 1.2 and Step Height to 0.1
  10. In Advanced area, set Width Inaccuracy % to 1
  11. Click Bake at the bottom to bake the Nav Mesh
Enemy Animation
  1. Select the Animation folder in Project panel
  2. Right-click it and Create > Animator Controller
  3. Name the asset EnemyAC (for Animator Controller)
  4. Drag and Drop this asset onto the Zombunny parent object in the Hierarchy
  5. Double-click EnemyAC to open in Animator window

  6. Locate and expand the Zombunny model in the Models > Characters folder in Project panel

  7. There are 3 animations - Idle, Move and Death set up
  8. Drag each clip to Animator, starting with Move
  9. Position Idle and Move states near one another, and place Death near to the Any State

  10. Ensure that Move state is default (orange highlight)

  11. If not, right-click and choose Set as Default
  12. In the Animator window’s Parameters, click + and make a Trigger parameter named PlayerDead
  13. Make another Trigger parameter named Dead

  14. Right-click the Move state and create a transition to the Idle state

  15. Right click the Any State and create a transition to the Death state
  16. Set the Condition for Move -> Idle to PlayerDead
  17. Set the Condition for Any State -> Death to Dead
Enemy behaviour
  1. In Scripts > Enemy folder in the Project, locate EnemyMovement script, drag-drop onto Zombunny
  2. Save your Scene
  3. Double-click the script icon to open for editing
  4. Press Play to test the game

Take some time to inspect the script. How the enemy moves?

Health HUD

  1. Click the 2D mode button on the Scene view
  2. Choose GameObject > UI > Canvas from menu
  3. Rename your Canvas to HUDCanvas
  4. Add Component > Miscellaneous > Canvas Group
  5. Un-check Interactable and Blocks Raycasts checkboxes

  6. Right-click HUDCanvas > Create Empty to add child

  7. Rename GameObject to HealthUI
  8. In the Rect Transform, click the Anchor Presets button, and set HealthUI’s Anchor, Position and Pivot to bottom left using Alt-Shift-click on anchor preset
  9. In Rect Transform, set Width to 75 and Height to 60

  10. Right-click HealthUI > UI > Image to add child

  11. Rename Image to Heart
  12. In Rect Transform set Position X and Y to 0
  13. Set Width and Height to 30
  14. In the Image component, for Source Image, circle select the Heart sprite from Assets

  15. Right-click HealthUI > UI > Slider

  16. Rename Slider to HealthSlider
  17. In Rect Transform, set Position X to 95, Y to 0
  18. Expand the HealthSlider to show children, select the Handle Slide Area child of the HealthSlider and delete it from the Hierarchy (Command-Backspace, Delete)

  19. In the Slider component of HealthSlider, set the Transition mode to None

  20. Set the Max Value property to 100
  21. Also set the actual Value to 100 for full health

  22. Right-click HUDCanvas and create a UI > Image

  23. Rename to DamageImage and set Rect Transform Anchor preset to Stretch in both dimensions by Alt + clicking the lower right preset
  24. In the Image component, click the Colour block and set the Alpha (A) value to 0

Player Health

  1. In the Scripts > Player folder, locate PlayerHealth
  2. Drag & drop this onto the Player in the Hierarchy
  3. Open the PlayerHealth script to examine it!
  4. Now let’s return to the Unity Editor..

  5. In the PlayerHealth (Script) component, assign HealthSlider from the Hierarchy to the Health Slider public variable slot using drag and drop

  6. On the same component, assign DamageImage from the Hierarchy to the Damage Image public variable slot via drag & drop

  7. On the PlayerHealth (Script) component, assign the Player Death audio clip to the Death Clip using circle select

  8. Locate EnemyAttack in the Scripts > Enemy folder of the Project, and drag & drop this onto the Zombunny in the Hierarchy

  9. Open the EnemyAttack script for editing by double-clicking the script icon in the Project

 We use the OnTriggerEnter / Exit to know when the player is in range.

     void OnTriggerEnter (Collider other)
    {
        if(other.gameObject == player)
        {
            playerInRange = true;
        }
    }

     void OnTriggerExit (Collider other)
    {
        if(other.gameObject == player)
        {
            playerInRange = false;
        }
    }

We then use a timer in the Update() function to attack the player with a timed basis.

     void Update ()
    {
        timer += Time.deltaTime;

         if(timer >= timeBetweenAttacks && playerInRange)
        {
            Attack ();
   timer = 0f;
        }

         if(playerHealth.currentHealth <= 0)
        {
            anim.SetTrigger ("PlayerDead");
        }
    }

     void Attack ()
    {
        if(playerHealth.currentHealth > 0)
        {
            playerHealth.TakeDamage (attackDamage);
        }
    }
  1. When done, return to the Unity Editor
  2. Save your scene
Harming Enemies
  1. In the Scripts > Enemy folder, locate EnemyHealth
  2. Drag & drop this onto the Zombunny in the Hierarchy
  3. In the Enemy Health (Script) component, assign Zombunny death clip to the Death Clip variable
  4. Open the EnemyHealth script for viewing
  5. Save your script and return to the Unity Editor

  6. Re-open the EnemyAttack script by double-clicking the icon of the script component in the Inspector

  7. Un-comment lines 13 and 22 by removing the preceding // symbols in front of each line
  8. Un-comment part of line 49 that is also commented out, Save your script & return

  9. In the Project > Prefabs folder, select GunParticles

  10. Click the Cog icon to the right of Particle System and choose Copy Component from the context-menu
  11. Expand the Player game object in the Hierarchy and select the child object GunBarrelEnd
  12. Click any Cog and choose Paste Component as New

  13. Collapse the new Particle System component

  14. With GunBarrelEnd still selected, Add Component > Effects > Line Renderer
  15. Expand Materials area and use circle select to pick the element, choose LineRenderMaterial

  16. Expand Parameters section of Line Renderer, set the Line Renderer’s Start Width and End Width to 0.05

  17. Disable Line Renderer component via the checkbox

  18. Add Component > Rendering > Light

  19. Choose a Yellow colour from the Color block / Picker
  20. Disable the Light component using the checkbox
  21. Add Component > Audio > Audio Source
  22. Set the Audio clip to Player Gunshot via circle select
  23. Uncheck Play On Awake for this audio source

  24. In Project > Scripts > Player folder, assign PlayerShooting to GunBarrelEnd in Hierarchy

  25. Open the PlayerShooting script for viewing
  26. Close the script and return to the Unity Editor
  27. Select Player in the Hierarchy and click Apply at the top of the Inspector to update our Prefab

  28. Save your Scene

  29. Press Play to test your scene
  30. Uh-oh! An Error! In the Scripts > Enemy folder of the Project, double-click EnemyMovement to open it
  31. Remove all // symbols to un-comment the inactive lines of code in the script, Save your script!

  32. In the Scripts > Player folder of the Project, doubleclick PlayerHealth to open it

  33. Remove all // symbols to un-comment the inactive lines of code in the script, and Save the script
  34. Return to the Unity editor
  35. Save your Scene, and press Play to test
    Scoring Points
  36. Select the HUDCanvas in the Hierarchy and right-click to create UI > Text as a child game object
  37. Rename this Text game object ScoreText
  38. Set the Anchor position in the Rect Transform to the Top Center preset
  39. Set Position X to 0 and Position Y to -55

  40. Change Width to 300 and Height to 50

  41. In the Text component, set the Text to “Score: 0”
  42. For the Font, circle-select the Luckiest Guy typeface
  43. Set the Font size to 50
  44. Set Alignments to Center and Middle
  45. Set the font Color to White by clicking the color block

  46. Add Component > type ‘Shadow’ to add the Shadow component, set the Effect Distance values to (2, -2)

  47. In the Scripts > Managers folder, locate the ScoreManager script, drag and drop this onto the ScoreText game object
  48. Open the script for review, then return to Unity

  49. Select the Zombunny in the Hierarchy and locate the EnemyHealth (script) component, double-click it’s icon to open for editing

  50. Remove the // symbols to un-comment line 77 in the StartSinking() function
  51. Save the script and return to the Unity editor

  52. Press Play to test your Scene

  53. Drag the Zombunny game object to the Prefabs folder in the Project panel to save it as a prefab
  54. Remove the Zombunny game object from the Hierarchy using Delete (PC) or Cmd-Backspace (Mac)
  55. Save your Scene

Spawning Enemies

  1. In the Prefabs folder of the Project, select the Zombear - he’s just like our Zombunny
  2. Expand Zombear’s Animator component
  3. From the Project, drag and drop EnemyAC from the Animation folder onto the Animator controller property of Zombear’s Animator component

  4. In the Prefabs folder of the Project, select the Hellephant

  5. Select the Animation folder in the Project, and then click Create > Animator Override Controller
  6. Name this asset HellephantAOC
  7. Assign EnemyAC to the Controller property

  8. In the Models > Characters folder of the Project, expand Hellephant model to see animation clips

  9. Drag Idle, Move and Death onto the corresponding slots in the HellephantAOC Override table
  10. Select Hellephant in the Prefabs folder and assign HellephantAOC to it’s Animator Controller

  11. Go to GameObject > Create Empty, rename this from GameObject to EnemyManager

  12. In the Scripts > Managers folder of the Project, locate the EnemyManager script, and drag it onto the EnemyManager game object
  13. Open the EnemyManager script & switch back after

  14. Go to GameObject > Create Empty, rename this from GameObject to ZombunnySpawnPoint

  15. At the top of the Inspector, set the Gizmo for the ZombunnySpawnPoint object to the colour blue
  16. Set Transform > Position to (-20.5, 0, 12.5)
  17. Set Transform > Rotation to (0, 130, 0)

  18. Go to GameObject > Create Empty, rename this from GameObject to ZombearSpawnPoint

  19. At the top of the Inspector, set the Gizmo for the ZombearSpawnPoint object to the colour pink
  20. Set Transform > Position to (22.5, 0, 15)
  21. Set Transform > Rotation to (0, 240, 0)

  22. Go to GameObject > Create Empty, rename this from GameObject to HellephantSpawnPoint

  23. At the top of the Inspector, set the Gizmo for the HellephantSpawnPoint object to the colour yellow
  24. Set Transform > Position to (0, 0, 32)
  25. Set Transform > Rotation to (0, 230, 0)

  26. Select EnemyManager in the Hierarchy, in the EnemyManager component, assign the Player game object to the PlayerHealth variable

  27. From the Prefabs folder, drag Zombunny onto the Enemy property as the game object to spawn
  28. Ensure that Spawn Time is set to 3 seconds

  29. Drag the ZombunnySpawnPoint from the Hierarchy onto the title of the SpawnPoints array variable

  30. Save your scene
  31. Press Play to test the game

  32. In the Scripts > Managers folder of the Project, locate the EnemyManager script, and drag it onto the EnemyManager game object 2 more times

  33. Ensure there are now 3 EnemyManager spawner components on the EnemyManager game object

  34. Assign the Player game object to the PlayerHealth variable on both new EnemyManager components

  35. From the Prefabs folder, drag Zombear onto the Enemy property of the second EnemyManager
  36. From the Prefabs folder, drag Hellephant onto the Enemy property of the third EnemyManager

  37. Drag the ZombearSpawnPoint from the Hierarchy onto the title of the SpawnPoints array variable in the second EnemyManager

  38. Drag the HellephantSpawnPoint from the Hierarchy onto the title of the SpawnPoints array variable in the third EnemyManager

  39. In the third EnemyManager for the Hellephant, set the Spawn Time to 10

  40. Save your scene
  41. Press Play to test your scene

We now want to limit the number of enemies on screen, we’ll use a constant and have track of the current number of enemies into the scene.

Change the EnemyManager script as follow:

static int spawnedEnemies = 0;  
static int killedEnemies = 0;

     void Start ()
    {
  parent = GameObject.Find ("Enemies").transform;
        InvokeRepeating ("Spawn", spawnTime, spawnTime);
    }

     void Spawn ()
    {
        if(playerHealth.currentHealth <= 0f)
        {
            return;
        }
  //We'll limit the max number of entity on screen
  if (GetEnemyOnScene() < MAX_ENEMIES_INSTANCE) {
   int spawnPointIndex = Random.Range (0, spawnPoints.Length);
   GameObject instantiated = (GameObject) Instantiate (enemy, spawnPoints [spawnPointIndex].position, spawnPoints [spawnPointIndex].rotation);
   instantiated.transform.parent = parent; //we move the go in a parent, it's less of a mess
   spawnedEnemies++;
  }
    }

  public static void IncKilledEnemies(){
  killedEnemies++;
 } 

  public static int GetEnemyOnScene(){
  return spawnedEnemies - killedEnemies;
 }
  1. Update the Death() function of EnemyHealth to increase the killedEnemies counter.
  2. Test again.

Game Over

  1. Right-click HUDCanvas and create a UI > Image
  2. Rename this game object ScreenFader
  3. In the Rect Transform component, click the Anchor Presets button and Alt-Click the Stretch both option
  4. In the Image component, click the Color block and choose a shade of light blue

  5. Right-click HUDCanvas and create a UI > Text

  6. Rename this game object GameOverText
  7. In the Rect Transform component, click the Anchor Presets button and Alt-Click the Middle center option
  8. Set the Width to 300 and Height to 50

  9. In the Text component, set the Text property to read ‘Game Over!’

  10. Using circle select, set the Font to Luckiest Guy
  11. Set Font Size to 50, Alignment to Middle and Center
  12. Set the Color to white using the Color block picker
  13. Add Component > type in Shadow and confirm

  14. Re-order the children of HUDCanvas using drag and drop in the Hierarchy, ensure the order is:

    • HealthUI
    • DamageImage
    • ScreenFader
    • GameOverText
    • ScoreText

  1. Select ScreenFader in the Hierarchy
  2. Set the Color’s alpha property in the Image component to 0
  3. Select GameOverText in the Hierarchy
  4. Set the Color’s alpha property in the Text component to 0

  5. Reselect HUDCanvas in the Hierarchy

  6. Go to Window > Animation and dock the panel
  7. Click the Add Curve button
  8. In the Create Animation dialog, choose the Animation folder as destination and name it GameOverClip (note that Unity creates an Animator Controller too)

  9. Add Curve for GameOverText > Text > Color

  10. Add Curve for GameOverText > RectTransform > Scale
  11. Add Curve for ScreenFader > Image > Color
  12. Add Curve for ScoreText > RectTransform > Scale
  13. Select and move all end keyframes to 0:30

  14. Move the playhead in the timeline to 0:20, select the GameOverText > RectTransform > Scale curve & press K to add a keyframe / click the Add Keyframe button

  15. Move to frame 0, select GameOverText > RectTransform > Scale, set values to 0 in Inspector

  16. Select all Keyframes, move them so that they begin at 1:30 (frame 90) in the timeline

  17. Disable Record mode

  18. In the Project panel Animation folder, select GameOverClip, in the Inspector, uncheck Loop time

  19. In the Project panel Animation folder, select the HUDCanvas animator controller that was created
  20. Double-click this asset to load it into the Animator window

  21. In the Animator window, right-click empty space and choose Create State > Empty

  22. Rename the state Empty at the top of the Inspector
  23. Right-click the Empty state and Create Transition to the GameOverClip state by selecting it
  24. Create a new Animator Trigger parameter GameOver

  25. Right-click Empty state, Set As Default

  26. Select the transition from Empty to GameOverClip
  27. In the Inspector, set the Condition to GameOver
  28. Select the HUDCanvas in Hierarchy, and in the Scripts > Managers folder of the Project, drag and drop GameOverManager to assign it to HUDCanvas

  29. In the Scripts > Managers folder of the Project, open GameOverManager to view it

  30. Close the script and return to the Unity editor
  31. Drag the Player from the Hierarchy to the Player Health variable of the Game Over Manager (Script) component

  32. File > Save Scene, File > Save Project

  33. Press Play to test your game
  34. Shoot some Zombie Toys!

References

Nightmares: Unity Survival Shooter tutorial

comments powered by Disqus