Merry Fragmas: Multiplayer FPS, Part 3

Checked with version: 4.6

-

Difficulty: Intermediate

Merry Fragmas everyone! During this live training series, we will be building a multiplayer First Person Shooter game. We will be focusing on the animation, physics, networking (with Photon), and other tricks that make FPS games what they are. Content will range from beginner to intermediate+ (depending on what you consider difficult). We will be looking at a lot of scripting and a few pro features (though you should still be able to complete the training without pro). I have no idea how many days this will span, so I am scheduling it for 3. Please note that I do not know how many days will be required to complete this series and what is scheduled is just a guess. With that being said, let us crush our foes with holiday spirit! -Tutor: Mike Geig

Merry Fragmas: Multiplayer FPS, Part 3

Intermediate The Asset Store

NetworkManager

Code snippet

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using System.Collections.Generic;

public class NetworkManager : MonoBehaviour 
{
    [SerializeField] Text connectionText;
    [SerializeField] Transform[] spawnPoints;
    [SerializeField] Camera sceneCamera;

    [SerializeField] GameObject serverWindow;
    [SerializeField] InputField username;
    [SerializeField] InputField roomName;
    [SerializeField] InputField roomList;
    [SerializeField] InputField messageWindow;

    GameObject player;
    Queue messages;
    const int messageCount = 6;
    PhotonView photonView;


    void Start () 
    {
        photonView = GetComponent ();
        messages = new Queue (messageCount);

        PhotonNetwork.logLevel = PhotonLogLevel.Full;
        PhotonNetwork.ConnectUsingSettings ("1.0");
        StartCoroutine ("UpdateConnectionString");
    }

    IEnumerator UpdateConnectionString () 
    {
        while(true)
        {
            connectionText.text = PhotonNetwork.connectionStateDetailed.ToString ();
            yield return null;
        }
    }

    void OnJoinedLobby()
    {
        serverWindow.SetActive (true);
    }

    void OnReceivedRoomListUpdate()
    {
        roomList.text = "";
        RoomInfo[] rooms = PhotonNetwork.GetRoomList ();
        foreach(RoomInfo room in rooms)
            roomList.text += room.name + "\n";
    }

    public void JoinRoom()
    {
        PhotonNetwork.player.name = username.text;
        RoomOptions roomOptions = new RoomOptions() { isVisible = true, maxPlayers = 10 };
        PhotonNetwork.JoinOrCreateRoom (roomName.text, roomOptions, TypedLobby.Default);
    }

    void OnJoinedRoom()
    {
        serverWindow.SetActive (false);
        StopCoroutine ("UpdateConnectionString");
        connectionText.text = "";
        StartSpawnProcess (0f);
    }

    void StartSpawnProcess(float respawnTime)
    {
        sceneCamera.enabled = true;
        StartCoroutine ("SpawnPlayer", respawnTime);
    }

    IEnumerator SpawnPlayer(float respawnTime)
    {
        yield return new WaitForSeconds(respawnTime);

        int index = Random.Range (0, spawnPoints.Length);
        player = PhotonNetwork.Instantiate("FPSPlayer", spawnPoints[index].position, spawnPoints[index].rotation, 0);
        player.GetComponent ().RespawnMe += StartSpawnProcess;
        player.GetComponent ().SendNetworkMessage += AddMessage;
        sceneCamera.enabled = false;

        AddMessage ("Spawned player: " + PhotonNetwork.player.name);
    }

    void AddMessage(string message)
    {
        photonView.RPC ("AddMessage_RPC", PhotonTargets.All, message);
    }

    [RPC]
    void AddMessage_RPC(string message)
    {
        messages.Enqueue (message);
        if(messages.Count > messageCount)
            messages.Dequeue();

        messageWindow.text = "";
        foreach(string m in messages)
            messageWindow.text += m + "\n";
    }
}

PlayerNetworkMover

Code snippet

using UnityEngine;
using System.Collections;

public class PlayerNetworkMover : Photon.MonoBehaviour 
{
    public delegate void Respawn(float time);
    public event Respawn RespawnMe;
    public delegate void SendMessage(string MessageOverlay);
    public event SendMessage SendNetworkMessage;

    Vector3 position;
    Quaternion rotation;
    float smoothing = 10f;
    float health = 100f;
    bool aim = false;
    bool sprint = false;
    bool initialLoad = true;

    Animator anim;

    void Start()
    {
        anim = GetComponentInChildren ();
        if(photonView.isMine)
        {
            transform.Find ("Head Joint/First Person Camera/GunCamera/Candy-Cane").gameObject.layer = 11;
            transform.Find ("Head Joint/First Person Camera/GunCamera/Candy-Cane/Sights").gameObject.layer = 11;
            
            GetComponent ().useGravity = true;
            
            GetComponent ().enabled = true;
            GetComponent ().enabled = true;
            GetComponent ().enabled = true;
            GetComponentInChildren ().enabled = true;
            GetComponentInChildren ().enabled = true;
            
            foreach(SimpleMouseRotator rot in GetComponentsInChildren ())
                rot.enabled = true;
            
            foreach(Camera cam in GetComponentsInChildren())
                cam.enabled = true;
        }
        else
        {
            StartCoroutine("UpdateData");
        }
    }

    IEnumerator UpdateData () 
    {
        if(initialLoad)
        {
            initialLoad = false;
            transform.position = position;
            transform.rotation = rotation;
        }

        while(true)
        {
            transform.position = Vector3.Lerp(transform.position, position, Time.deltaTime * smoothing);
            transform.rotation = Quaternion.Lerp(transform.rotation, rotation, Time.deltaTime * smoothing);
            anim.SetBool("Aim", aim);
            anim.SetBool ("Sprint", sprint);
            yield return null;
        }
    }

    void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
        if (stream.isWriting)
        {
            stream.SendNext(transform.position);
            stream.SendNext(transform.rotation);
            stream.SendNext(health);
            stream.SendNext(anim.GetBool ("Aim"));
            stream.SendNext(anim.GetBool ("Sprint"));
        }
        else
        {
            position = (Vector3)stream.ReceiveNext();
            rotation = (Quaternion)stream.ReceiveNext();
            health = (float)stream.ReceiveNext();
            aim = (bool)stream.ReceiveNext();
            sprint = (bool)stream.ReceiveNext();
        }
    }

    [RPC]
    public void GetShot(float damage, string enemyName)
    {
        health -= damage;

        if (health <= 0 && photonView.isMine){

            if(SendNetworkMessage != null)
                SendNetworkMessage(PhotonNetwork.player.name + " was killed by " + enemyName);

            if(RespawnMe != null)
                RespawnMe(3f);

            PhotonNetwork.Destroy (gameObject);
        }
    }
}

PlayerShooting

Code snippet

using UnityEngine;
using System.Collections;

public class PlayerShooting : MonoBehaviour {

    public ParticleSystem muzzleFlash;
    public GameObject impactPrefab;

    Animator anim;
    GameObject[] impacts;
    int currentImpact = 0;
    int maxImpacts = 5;
    float damage = 25f;
    bool shooting = false;
    
    void Start () 
    {
        impacts = new GameObject[maxImpacts];
        for(int i = 0; i < maxImpacts; i++)
            impacts[i] = (GameObject)Instantiate(impactPrefab);

        anim = GetComponentInChildren ();
    }
    
    void Update () {
        
        if(Input.GetButtonDown ("Fire1") && !Input.GetKey(KeyCode.LeftShift))
        {
            muzzleFlash.Play();
            anim.SetTrigger("Fire");
            shooting = true;
        }
    }

    void FixedUpdate()
    {
        if(shooting)
        {
            shooting = false;
            
            RaycastHit hit;
            
            if(Physics.Raycast(transform.position, transform.forward, out hit, 50f))
            {
                if(hit.transform.tag == "Player")
                    hit.transform.GetComponent().RPC ("GetShot", PhotonTargets.All, damage, PhotonNetwork.player.name);
                
                impacts[currentImpact].transform.position = hit.point;
                impacts[currentImpact].GetComponent().Play();
                
                if(++currentImpact >= maxImpacts)
                    currentImpact = 0;
            }
        }
    }
}