Merry Fragmas 3.0: Multiplayer FPS Foundation

Checked with version: 5.5

-

Difficulty: Intermediate

In this multi-day session we will be building a Networked Multiplayer First Person Shooter game from the ground up. This first session covers the basics of our FPS game as well as the foundation for our multiplayer structure. In sessions two and three we will extend this to add polish and more robust multiplayer features. Let the slay bells ring!

Merry Fragmas 3.0: Multiplayer FPS Foundation

Intermediate Multiplayer Networking

Player

Code snippet

using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Events;

[System.Serializable]
public class ToggleEvent : UnityEvent{}

public class Player : NetworkBehaviour 
{
    [SerializeField] ToggleEvent onToggleShared;
    [SerializeField] ToggleEvent onToggleLocal;
    [SerializeField] ToggleEvent onToggleRemote;
    [SerializeField] float respawnTime = 5f;

    GameObject mainCamera;

    void Start()
    {
        mainCamera = Camera.main.gameObject;

        EnablePlayer ();
    }

    void DisablePlayer()
    {
        if (isLocalPlayer)
            mainCamera.SetActive (true);

        onToggleShared.Invoke (false);

        if (isLocalPlayer)
            onToggleLocal.Invoke (false);
        else
            onToggleRemote.Invoke (false);
    }

    void EnablePlayer()
    {
        if (isLocalPlayer)
            mainCamera.SetActive (false);

        onToggleShared.Invoke (true);

        if (isLocalPlayer)
            onToggleLocal.Invoke (true);
        else
            onToggleRemote.Invoke (true);
    }

    public void Die()
    {
        DisablePlayer ();

        Invoke ("Respawn", respawnTime);
    }

    void Respawn()
    {
        if (isLocalPlayer) 
        {
            Transform spawn = NetworkManager.singleton.GetStartPosition ();
            transform.position = spawn.position;
            transform.rotation = spawn.rotation;
        }

        EnablePlayer ();
    }
}

PlayerHealth

Code snippet

using UnityEngine;
using UnityEngine.Networking;

public class PlayerHealth : NetworkBehaviour 
{
    [SerializeField] int maxHealth = 3;

    Player player;
    int health;

    void Awake()
    {
        player = GetComponent ();
    }

    [ServerCallback]
    void OnEnable()
    {
        health = maxHealth;
    }

    [Server]
    public bool TakeDamage()
    {
        bool died = false;

        if (health <= 0)
            return died;

        health--;
        died = health <= 0;

        RpcTakeDamage (died);

        return died;
    }

    [ClientRpc]
    void RpcTakeDamage(bool died)
    {
        if (died)
            player.Die ();
    }
}

PlayerShooting

Code snippet

using UnityEngine;
using UnityEngine.Networking;

public class PlayerShooting : NetworkBehaviour 
{
    [SerializeField] float shotCooldown = .3f;
    [SerializeField] Transform firePosition;
    [SerializeField] ShotEffectsManager shotEffects;

    float ellapsedTime;
    bool canShoot;

    void Start()
    {
        shotEffects.Initialize ();

        if (isLocalPlayer)
            canShoot = true;
    }

    void Update()
    {
        if (!canShoot)
            return;

        ellapsedTime += Time.deltaTime;

        if (Input.GetButtonDown ("Fire1") && ellapsedTime > shotCooldown) 
        {
            ellapsedTime = 0f;
            CmdFireShot (firePosition.position, firePosition.forward);
        }
    }

    [Command]
    void CmdFireShot(Vector3 origin, Vector3 direction)
    {
        RaycastHit hit;

        Ray ray = new Ray (origin, direction);
        Debug.DrawRay (ray.origin, ray.direction * 3f, Color.red, 1f);

        bool result = Physics.Raycast (ray, out hit, 50f);

        if (result) 
        {
            PlayerHealth enemy = hit.transform.GetComponent ();

            if (enemy != null)
                enemy.TakeDamage ();
        }

        RpcProcessShotEffects (result, hit.point);
    }

    [ClientRpc]
    void RpcProcessShotEffects(bool playImpact, Vector3 point)
    {
        shotEffects.PlayShotEffects ();

        if (playImpact)
            shotEffects.PlayImpactEffect (point);
    }
}

ShotEffectsManager

Code snippet

using UnityEngine;

public class ShotEffectsManager : MonoBehaviour 
{
    [SerializeField] ParticleSystem muzzleFlash;
    [SerializeField] AudioSource gunAudio;
    [SerializeField] GameObject impactPrefab;

    ParticleSystem impactEffect;

    //Create the impact effect for our shots
    public void Initialize()
    {
        impactEffect = Instantiate(impactPrefab).GetComponent();
    }

    //Play muzzle flash and audio
    public void PlayShotEffects()
    {
        muzzleFlash.Stop(true);
        muzzleFlash.Play(true);
        gunAudio.Stop();
        gunAudio.Play();
    }

    //Play impact effect and target position
    public void PlayImpactEffect(Vector3 impactPosition)
    {
        impactEffect.transform.position = impactPosition;   
        impactEffect.Stop();
        impactEffect.Play();
    }
}