Couch Wars: Local Multiplayer Basics

Checked with version: 4.6

-

Difficulty: Beginner

In this live training session we will look at adding local two player coop play to the existing 2D Platformer project which is available on the asset store. We will also add support for controller input, in this case setting up XBox 360 controllers on a Mac.

Couch Wars: Local Multiplayer Basics

Beginner Mini-Projects

PlayerControl

Code snippet

using UnityEngine;
using System.Collections;

public class Enemy : MonoBehaviour
{
    public float moveSpeed = 2f;        // The speed the enemy moves at.
    public int HP = 2;                  // How many times the enemy can be hit before it dies.
    public Sprite deadEnemy;            // A sprite of the enemy when it's dead.
    public Sprite damagedEnemy;         // An optional sprite of the enemy when it's damaged.
    public AudioClip[] deathClips;      // An array of audioclips that can play when the enemy dies.
    public GameObject hundredPointsUI;  // A prefab of 100 that appears when the enemy dies.
    public float deathSpinMin = -100f;          // A value to give the minimum amount of Torque when dying
    public float deathSpinMax = 100f;           // A value to give the maximum amount of Torque when dying


    private SpriteRenderer ren;         // Reference to the sprite renderer.
    private Transform frontCheck;       // Reference to the position of the gameobject used for checking if something is in front.
    private bool dead = false;          // Whether or not the enemy is dead.
    private Score score1;               // Reference to the Score script.
    private Score score2;               // Reference to the Score script.
    private int playerNum;

    
    void Awake()
    {
        // Setting up the references.
        ren = transform.Find("body").GetComponent<SpriteRenderer>();
        frontCheck = transform.Find("frontCheck").transform;
        score1 = GameObject.Find("Score1").GetComponent<Score>();
        score2 = GameObject.Find("Score2").GetComponent<Score>();
    }

    void FixedUpdate ()
    {
        // Create an array of all the colliders in front of the enemy.
        Collider2D[] frontHits = Physics2D.OverlapPointAll(frontCheck.position, 1);

        // Check each of the colliders.
        foreach(Collider2D c in frontHits)
        {
            // If any of the colliders is an Obstacle...
            if(c.tag == "Obstacle")
            {
                // ... Flip the enemy and stop checking the other colliders.
                Flip ();
                break;
            }
        }

        // Set the enemy's velocity to moveSpeed in the x direction.
        rigidbody2D.velocity = new Vector2(transform.localScale.x * moveSpeed, rigidbody2D.velocity.y); 

        // If the enemy has one hit point left and has a damagedEnemy sprite...
        if(HP == 1 && damagedEnemy != null)
            // ... set the sprite renderer's sprite to be the damagedEnemy sprite.
            ren.sprite = damagedEnemy;
            
        // If the enemy has zero or fewer hit points and isn't dead yet...
        if(HP <= 0 && !dead)
            // ... call the death function.
            Death ();
    }
    
    public void Hurt(int playerWhoShotMe)
    {

        // Reduce the number of hit points by one.
        HP--;
        playerNum = playerWhoShotMe;
    }
    
    void Death()
    {
        // Find all of the sprite renderers on this object and it's children.
        SpriteRenderer[] otherRenderers = GetComponentsInChildren<SpriteRenderer>();

        // Disable all of them sprite renderers.
        foreach(SpriteRenderer s in otherRenderers)
        {
            s.enabled = false;
        }

        // Re-enable the main sprite renderer and set it's sprite to the deadEnemy sprite.
        ren.enabled = true;
        ren.sprite = deadEnemy;

        // Increase the score by 100 points

        if (playerNum == 1)
        score1.score += 100;
        else
        score2.score += 100;

        // Set dead to true.
        dead = true;

        // Allow the enemy to rotate and spin it by adding a torque.
        rigidbody2D.fixedAngle = false;
        rigidbody2D.AddTorque(Random.Range(deathSpinMin,deathSpinMax));

        // Find all of the colliders on the gameobject and set them all to be triggers.
        Collider2D[] cols = GetComponents<Collider2D>();
        foreach(Collider2D c in cols)
        {
            c.isTrigger = true;
        }

        // Play a random audioclip from the deathClips array.
        int i = Random.Range(0, deathClips.Length);
        AudioSource.PlayClipAtPoint(deathClips[i], transform.position);

        // Create a vector that is just above the enemy.
        Vector3 scorePos;
        scorePos = transform.position;
        scorePos.y += 1.5f;

        // Instantiate the 100 points prefab at this point.
        Instantiate(hundredPointsUI, scorePos, Quaternion.identity);
    }


    public void Flip()
    {
        // Multiply the x component of localScale by -1.
        Vector3 enemyScale = transform.localScale;
        enemyScale.x *= -1;
        transform.localScale = enemyScale;
    }
}

LayBombs

Code snippet

using UnityEngine;
using System.Collections;

public class LayBombs : MonoBehaviour
{
    [HideInInspector]
    public bool bombLaid = false;       // Whether or not a bomb has currently been laid.
    public int bombCount = 0;           // How many bombs the player has.
    public AudioClip bombsAway;         // Sound for when the player lays a bomb.
    public GameObject bomb;             // Prefab of the bomb.
    public string bombButton = "Fire2_P1";


    private GUITexture bombHUD;         // Heads up display of whether the player has a bomb or not.


    void Awake ()
    {
        // Setting up the reference.
        bombHUD = GameObject.Find("ui_bombHUD").guiTexture;
    }


    void Update ()
    {
        // If the bomb laying button is pressed, the bomb hasn't been laid and there's a bomb to lay...
        if(Input.GetButtonDown(bombButton) && bombCount > 0)
        {
            // Decrement the number of bombs.
            bombCount--;

            // Play the bomb laying sound.
            AudioSource.PlayClipAtPoint(bombsAway,transform.position);

            // Instantiate the bomb prefab.
            Instantiate(bomb, transform.position, transform.rotation);
        }

        // The bomb heads up display should be enabled if the player has bombs, other it should be disabled.
        bombHUD.enabled = bombCount > 0;
    }
}

Gun

Code snippet

using UnityEngine;
using System.Collections;

public class Gun : MonoBehaviour
{
    public Rigidbody2D rocket;              // Prefab of the rocket.
    public float speed = 20f;               // The speed the rocket will fire at.
    public string gunButton = "Fire1_P1";


    private PlayerControl playerCtrl;       // Reference to the PlayerControl script.
    private Animator anim;                  // Reference to the Animator component.


    void Awake()
    {
        // Setting up the references.
        anim = transform.root.gameObject.GetComponent<Animator>();
        playerCtrl = transform.root.GetComponent<PlayerControl>();
    }


    void Update ()
    {
        // If the fire button is pressed...
        if(Input.GetButtonDown(gunButton))
        {
            // ... set the animator Shoot trigger parameter and play the audioclip.
            anim.SetTrigger("Shoot");
            audio.Play();

            // If the player is facing right...
            if(playerCtrl.facingRight)
            {
                // ... instantiate the rocket facing right and set it's velocity to the right. 
                Rigidbody2D bulletInstance = Instantiate(rocket, transform.position, Quaternion.Euler(new Vector3(0,0,0))) as Rigidbody2D;
                bulletInstance.velocity = new Vector2(speed, 0);
            }
            else
            {
                // Otherwise instantiate the rocket facing left and set it's velocity to the left.
                Rigidbody2D bulletInstance = Instantiate(rocket, transform.position, Quaternion.Euler(new Vector3(0,0,180f))) as Rigidbody2D;
                bulletInstance.velocity = new Vector2(-speed, 0);
            }
        }
    }
}

PlayerHealth

Code snippet

using UnityEngine;
using System.Collections;

public class PlayerHealth : MonoBehaviour
{   
    public float health = 100f;                 // The player's health.
    public float repeatDamagePeriod = 2f;       // How frequently the player can be damaged.
    public AudioClip[] ouchClips;               // Array of clips to play when the player is damaged.
    public float hurtForce = 10f;               // The force with which the player is pushed when hurt.
    public float damageAmount = 10f;            // The amount of damage to take when enemies touch the player
    public SpriteRenderer healthBar;            // Reference to the sprite renderer of the health bar.

    private float lastHitTime;                  // The time at which the player was last hit.
    private Vector3 healthScale;                // The local scale of the health bar initially (with full health).
    private PlayerControl playerControl;        // Reference to the PlayerControl script.
    private Animator anim;                      // Reference to the Animator on the player


    void Awake ()
    {
        // Setting up references.
        playerControl = GetComponent<PlayerControl>();
        //healthBar = GameObject.Find("HealthBar").GetComponent<SpriteRenderer>();
        anim = GetComponent<Animator>();

        // Getting the intial scale of the healthbar (whilst the player has full health).
        healthScale = healthBar.transform.localScale;
    }


    void OnCollisionEnter2D (Collision2D col)
    {
        // If the colliding gameobject is an Enemy...
        if(col.gameObject.tag == "Enemy")
        {
            // ... and if the time exceeds the time of the last hit plus the time between hits...
            if (Time.time > lastHitTime + repeatDamagePeriod) 
            {
                // ... and if the player still has health...
                if(health > 0f)
                {
                    // ... take damage and reset the lastHitTime.
                    TakeDamage(col.transform); 
                    lastHitTime = Time.time; 
                }
                // If the player doesn't have health, do some stuff, let him fall into the river to reload the level.
                else
                {
                    // Find all of the colliders on the gameobject and set them all to be triggers.
                    Collider2D[] cols = GetComponents<Collider2D>();
                    foreach(Collider2D c in cols)
                    {
                        c.isTrigger = true;
                    }

                    // Move all sprite parts of the player to the front
                    SpriteRenderer[] spr = GetComponentsInChildren<SpriteRenderer>();
                    foreach(SpriteRenderer s in spr)
                    {
                        s.sortingLayerName = "UI";
                    }

                    // ... disable user Player Control script
                    GetComponent<PlayerControl>().enabled = false;

                    // ... disable the Gun script to stop a dead guy shooting a nonexistant bazooka
                    GetComponentInChildren<Gun>().enabled = false;

                    // ... Trigger the 'Die' animation state
                    anim.SetTrigger("Die");
                }
            }
        }
    }


    void TakeDamage (Transform enemy)
    {
        // Make sure the player can't jump.
        playerControl.jump = false;

        // Create a vector that's from the enemy to the player with an upwards boost.
        Vector3 hurtVector = transform.position - enemy.position + Vector3.up * 5f;

        // Add a force to the player in the direction of the vector and multiply by the hurtForce.
        rigidbody2D.AddForce(hurtVector * hurtForce);

        // Reduce the player's health by 10.
        health -= damageAmount;

        // Update what the health bar looks like.
        UpdateHealthBar();

        // Play a random clip of the player getting hurt.
        int i = Random.Range (0, ouchClips.Length);
        AudioSource.PlayClipAtPoint(ouchClips[i], transform.position);
    }


    public void UpdateHealthBar ()
    {
        // Set the health bar's colour to proportion of the way between green and red based on the player's health.
        healthBar.material.color = Color.Lerp(Color.green, Color.red, 1 - health * 0.01f);

        // Set the scale of the health bar to be proportional to the player's health.
        healthBar.transform.localScale = new Vector3(healthScale.x * health * 0.01f, 1, 1);
    }
}

Related tutorials