#C5 Player Controller and Spawning

AdhisivanAdhisivan
8 min read

Introduction:

Welcome back to our development journey! In this update, we'll delve into the intricacies of player control and spawning, crucial aspects of any multiplayer game. Join us as we explore the implementation of the PlayerController and PlayerManager scripts, along with the spawn logic based on selected characters and team roles.


PlayerController Script:

The PlayerController script governs the behavior and interactions of individual players within the game world. Here's a breakdown of its key functionalities:

  • Input Handling: The script manages player input for movement, camera control, and interaction with in-game items.

  • Item Management: Players can equip different items, such as pistols, rifles, and knives, each with its own unique properties and usage mechanics.

  • Spike Interaction: Players can pick up, plant, and defuse a spike, a pivotal objective in the game. Various states and timers control these interactions, adding depth to the gameplay experience.

  • Health and Damage: The script tracks player health and handles damage calculation, including player death and respawn logic.

using Photon.Pun;
using Photon.Realtime;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using Hashtable = ExitGames.Client.Photon.Hashtable;

public class PlayerController : MonoBehaviourPunCallbacks, IDamageable
{
    [SerializeField] float mouseSensitivity, sprintSpeed, walkSpeed, jumpForce, smoothTime;
    [SerializeField] GameObject cameraHolder;

    public Item[] items;

    public Item Pistol, Rifle, Knife;

    int itemIndex;
    int previousItemIndex = -1;

    float verticalLookRotation;
    bool grounded;
    Vector3 smoothMoveVelocity;
    public Vector3 moveAmount;

    Rigidbody rb;
    PhotonView PV;

    const float maxHealth = 100f;
    float currentHealth = maxHealth;

    PlayerManager playerManager;

    float scaledJumpforce;

    public bool hasSpike;

    public GameObject SpikeCanvas;

    public bool CanPlant = false;
    public bool isPlanting = false;
    public float plantTimer = 0f, PlantMaxTime = 4f;
    public bool isPlanted = false;

    public bool CanDefuse =false;
    public bool isDefusing =false;
    public float defuseTimer = 0f, DefuseMaxTime = 4f;
    public bool isDefused = false;

    public bool isDead = false;

    private void Awake()
    {
        rb = GetComponent<Rigidbody>();
        PV = GetComponent<PhotonView>();

        playerManager = PhotonView.Find((int)PV.InstantiationData[0]).GetComponent<PlayerManager>();
    }

    private void Start()
    {
        if (PV.IsMine)
        {
            EquipItem(0);
        }
        else
        {
            Destroy(GetComponentInChildren<Camera>().gameObject);
            Destroy(rb);
        }


    }
    private void Update()
    {
        if (isDead == false)
        {
            if (!PV.IsMine)
            return;


            Look();
            Move();


            //Weapon Equip with scroll
            if (Input.GetAxisRaw("Mouse ScrollWheel") > 0f)
            {
                if (itemIndex >= items.Length - 1)
                {
                    EquipItem(0);
                }
                else
                {
                    EquipItem(itemIndex + 1);
                }
            }
            else if (Input.GetAxisRaw("Mouse ScrollWheel") < 0f)
            {
                if (itemIndex <= 0)
                {
                    EquipItem(items.Length - 1);
                }
                else
                {
                    EquipItem(itemIndex - 1);
                }

            }

            if (Input.GetMouseButtonDown(0))
            {
                items[itemIndex].Use();
            }

            // - - - -

            //Planting Update
            if (isPlanted == false)
            {
                if (hasSpike == true && CanPlant == true && isPlanting == false && Input.GetKeyDown(KeyCode.P))
                {
                    StartPlanting();
                    Debug.Log("Planting Started");
                }

                if (isPlanting)
                {
                    UpdatePlantTimer();
                }

                if (isPlanting)
                {
                    if (!Input.GetKey(KeyCode.P))
                    {
                        isPlanting = false;
                        Debug.Log("Plant interupted");
                    }
                }

            }


            // - - - -

            //Defuse Update
            if (isPlanted == true && isDefused == false)
            {
                if (CanDefuse == true && isDefusing == false && Input.GetKeyDown(KeyCode.P))
                {
                    StartDefusing();
                    Debug.Log("Defusing started");
                }

                if (isDefusing)
                {
                    UpdateDiffuseTimer();
                    if (!Input.GetKey(KeyCode.P))
                    {
                        isDefusing = false;
                        Debug.Log("Defuse Interupted");
                    }
                }

            }


        }

        // Only look after dead
        if(isDead == true)
        {
            Look();
        }   


    }

    void Look()
    {
        transform.Rotate(Vector3.up * Input.GetAxisRaw("Mouse X") * mouseSensitivity);

        verticalLookRotation += Input.GetAxisRaw("Mouse Y") * mouseSensitivity;
        verticalLookRotation = Mathf.Clamp(verticalLookRotation, -70f, 70f);

        cameraHolder.transform.localEulerAngles = Vector3.left * verticalLookRotation;

    }

    void Move()
    {
        Vector3 moveDir = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical")).normalized;
        moveAmount = Vector3.SmoothDamp(moveAmount, moveDir * (Input.GetKey(KeyCode.LeftShift) ? sprintSpeed : walkSpeed), ref smoothMoveVelocity, smoothTime);

    }

    void Jump()
    {
        if (Input.GetKey(KeyCode.Space) && grounded)
        {
            scaledJumpforce = jumpForce * Time.fixedDeltaTime;
            rb.AddForce(transform.up * scaledJumpforce, ForceMode.Impulse);
        }
    }

    public void EquipItem(int _index)
    {
        if (previousItemIndex == _index)
            return;

        itemIndex = _index;
        items[itemIndex].itemGameObject.SetActive(true);

        if (previousItemIndex != -1)
        {
            items[previousItemIndex].itemGameObject.SetActive(false);
        }

        previousItemIndex = itemIndex;

        if (PV.IsMine)
        {
            Hashtable hash = new Hashtable();
            hash.Add("ItemIndex", itemIndex);
            PhotonNetwork.LocalPlayer.SetCustomProperties(hash);
        }

    }


    //Sync Guns when changed across players
    public override void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
    {

        if (!PV.IsMine && targetPlayer == PhotonNetwork.LocalPlayer)
        {
            EquipItem((int)changedProps["ItemIndex"]);
        }
    }


    public void SetGroundedState(bool _grounded)
    {
        grounded = _grounded;
    }

    private void FixedUpdate()
    {
        Jump();

        if (!PV.IsMine)
            return;

        rb.MovePosition(rb.position + transform.TransformDirection(moveAmount) * Time.fixedDeltaTime);

        if (transform.position.y < -10f)
        {
            Die();
        }
    }

    //PickUp Spike
    public void PickUpSpike(GameObject gameObject)
    {
        hasSpike = true;
        gameObject.transform.parent = transform;
        gameObject.transform.GetComponentInChildren<MeshRenderer>().enabled = false;
        gameObject.transform.GetComponent<BoxCollider>().enabled = false;
        int ownerViewID = PV.ViewID;
        Debug.Log("SPike picked - Own");

        SpikeCanvas.SetActive(true);
        PV.RPC("RPC_Sync_Spike", RpcTarget.OthersBuffered, ownerViewID);
    }      



   //Planting Spike

    public void StartPlanting()
    {
        isPlanting = true;
        plantTimer = 0f;
    }

    //Defuse Start
    public void StartDefusing()
    {
        isDefusing = true;
        defuseTimer = 0f;
    }

    //Update plant
    public void UpdatePlantTimer()
    {
        plantTimer += Time.deltaTime;
        if ((plantTimer >= PlantMaxTime))
        {
            SpikePlanted();
            isPlanting=false;
            isPlanted = true;
            plantTimer = 0;
        }

    }


    //Update defuse
    public void UpdateDiffuseTimer()
    {
        defuseTimer += Time.deltaTime;
        if( defuseTimer > DefuseMaxTime )
        {
            SpikeDefused(); 

        }
    }

    //Spike defused
    public void SpikeDefused()
    {
        isDefused = true;
        Debug.Log("Spike Defused");

        MatchManager matchManager = FindObjectOfType<MatchManager>();
        matchManager.EndTimerStart("Defense");

        //RPC Call
        PV.RPC("RPC_Defused", RpcTarget.OthersBuffered);
    }

    //Spike planted - Local
    public void SpikePlanted()
    {

        Transform oldSpike = transform.Find("SpikeObj(Clone)");
        if (oldSpike != null)
        {
            oldSpike.parent = null;
            Transform NewSpikeSpawn = oldSpike.transform;
            Destroy(GameObject.FindGameObjectWithTag("Spike"));
            Debug.Log("Destroyed Old spike");

            GameObject NewSpike = PhotonNetwork.Instantiate(Path.Combine("PhotonPrefabs", "NewSpike"), NewSpikeSpawn.position, NewSpikeSpawn.rotation);
            Debug.Log("Spawned New Spike");

            GameObject[] PlantAreas = GameObject.FindGameObjectsWithTag("PlantArea");
            Destroy(PlantAreas[0]);
            Destroy(PlantAreas[1]);            

            isPlanted = true;
            MatchManager matchManager = FindObjectOfType<MatchManager>();
            matchManager.isMatchPhase = false;
            matchManager.DefuseTimeStart();

            PV.RPC("RPC_Test", RpcTarget.OthersBuffered);

            int PVID = PV.ViewID;
            PV.RPC("RPC_Planted", RpcTarget.OthersBuffered, PVID);

            SpikeCanvas.SetActive(false);

            isPlanted = true;
        }
        else
        {
            Debug.LogError("SpikeObj(Clone) GameObject not found!");
        }

        Debug.Log("Spike planted");
    }

    //Spike drop - Local
    public void DropSpike()
    {

        Transform oldSpike = transform.Find("SpikeObj(Clone)");
        if(oldSpike != null)
        {
            oldSpike.parent = null;
            oldSpike.GetComponentInChildren<MeshRenderer>().enabled = true;
            oldSpike.GetComponent<BoxCollider>().enabled = true;
            oldSpike.GetComponent<SpikeScript>().isPicked = false;
            Debug.Log("Spike droppped - own");
        }
        else
        {
            Debug.Log("Error - Couldn't find spike");
        }

        int PViewID = PV.ViewID;
        PV.RPC("RPC_Drop", RpcTarget.OthersBuffered, PViewID);

        hasSpike = false;
    }


    //Take Damage
    public void TakeDamage(float damage)
    {

        PV.RPC("RPC_TakeDamage", RpcTarget.All, damage);
    }


    //Die
    void Die()
    {
        if(PV.GetComponent<PlayerController>().hasSpike == true)
        {
            DropSpike();
        }
        playerManager.Die();

    }


    [PunRPC]
    void RPC_TakeDamage(float damage)
    {
        if (!PV.IsMine)
            return;

        Debug.Log("Took Damage: " + damage);

        currentHealth -= damage;

        if (currentHealth <= 0f)
        {            
            Die();
        }
    }


    [PunRPC]
    public void RPC_Sync_Spike(int ownerViewID)
    {
        GameObject gameObject = GameObject.FindGameObjectWithTag("Spike");
        PhotonView ownerView = PhotonView.Find(ownerViewID);
        if (ownerView != null)
        {
            Transform ownerTransform = ownerView.transform;
            gameObject.GetComponentInChildren<MeshRenderer>().enabled = false;
            gameObject.transform.GetComponent<BoxCollider>().enabled = false;
            gameObject.transform.parent = ownerTransform;
            if ((string)PhotonNetwork.LocalPlayer.CustomProperties["PlayMode"] == "Attack")
            {
                ownerView.GetComponent<PlayerController>().SpikeCanvas.SetActive(true);
                ownerView.GetComponent<PlayerController>().hasSpike = true;
            }            
        }
        Debug.Log("Spike picked up - sync");
        return;
    }

    [PunRPC]
    public void RPC_Test()
    {
        Debug.Log("Test RPC call working");
    }

    [PunRPC]
    public void RPC_Planted(int PVID)
    {
        Debug.Log("Sync Start ///");

        PlayerController plantedPlayerController = PhotonView.Find(PVID).GetComponent<PlayerController>();
        GameObject plantedGO = plantedPlayerController.gameObject;

        plantedPlayerController.SpikeCanvas.SetActive(false);

        GameObject spike = GameObject.FindGameObjectWithTag("Spike");
        Destroy(spike); 
        Debug.Log("Old spike destroyed");

        PV.GetComponent<PlayerController>().isPlanted = true;
        Debug.Log("isPlanted: " + PV.GetComponent<PlayerController>().isPlanted.ToString());


        MatchManager matchManager2 = FindObjectOfType<MatchManager>();
        matchManager2.isMatchPhase = false;
        matchManager2.DefuseTimeStart();

        Debug.Log("Sync End ///");
    }

    [PunRPC]
    public void RPC_Drop(int viewID)
    {
        PlayerController plantedPlayerController = PhotonView.Find(viewID).GetComponent<PlayerController>();
        GameObject plantedGO = plantedPlayerController.gameObject;


        GameObject spike = GameObject.FindGameObjectWithTag("Spike");
        if(spike != null)
        {
            spike.transform.parent = null;
            spike.GetComponentInChildren<MeshRenderer>().enabled = true;
            spike.GetComponent<BoxCollider>().enabled = true;
            spike.GetComponent<SpikeScript>().isPicked = false;
        }
        else
        {
            Debug.Log("Couldn't find spike");
        }


        plantedPlayerController.hasSpike = false;
        plantedPlayerController.SpikeCanvas.SetActive(false);

        Debug.Log("Spike droppped - sync");

    }

    [PunRPC]
    public void RPC_Defused()
    {
        PV.GetComponent<PlayerController>().isDefused = true;
        Debug.Log("Spike Defused");

        MatchManager matchManager = FindObjectOfType<MatchManager>();
        matchManager.EndTimerStart("Defense");

        Debug.Log("End synced");
    }

    [PunRPC]
    public void SyncItems(Item[] updatedItems)
    {
        // Update the items array with the synchronized values
        items = updatedItems;
    }

}

PlayerManager Script:

The PlayerManager script oversees player instantiation and management. Here's a summary of its main functionalities:

  • Player Instantiation: Upon joining the game, the script creates a player controller based on the selected character and team role, ensuring proper initialization and setup.

  • Player Death: When a player dies, the script handles death-related actions, such as displaying death UI elements and managing respawns.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using System.IO;

public class PlayerManager : MonoBehaviour
{

    PhotonView PV;
    GameObject controller;

    private void Awake()
    {
        PV = GetComponent<PhotonView>();
    }

    void Start()
    {
        if(PV.IsMine)
        {
            CreateController();
        }    
    }

    void CreateController()
    {        
        Transform spawnpoint = SpawnManager.instance.GetSpawnPoint();
        string CharName = (string)PhotonNetwork.LocalPlayer.CustomProperties["SelectedAgent"];
        if(CharName == "Connor")
        {
            controller = PhotonNetwork.Instantiate(Path.Combine("PhotonPrefabs", "ConnorPlayerController"), spawnpoint.position, spawnpoint.rotation, 0, new object[] { PV.ViewID });
        }
        else if(CharName == "Chloe")
        {
            controller = PhotonNetwork.Instantiate(Path.Combine("PhotonPrefabs", "ChloePlayerController"), spawnpoint.position, spawnpoint.rotation, 0, new object[] { PV.ViewID });
        }
        else if(CharName == "Ryan")
        {
            controller = PhotonNetwork.Instantiate(Path.Combine("PhotonPrefabs", "RyanPlayerController"), spawnpoint.position, spawnpoint.rotation, 0, new object[] { PV.ViewID });
        }

        Debug.Log(CharName + "Player Controller Created");
    }

    public void Die()
    {        
        MenuManager.instance.OpenMenu("DeadPanel");
        controller.GetComponent<PlayerController>().isDead = true;        

        Debug.Log("Player dead");

    }

}

SpawnManager Script:

The SpawnManager script manages player spawning logic, ensuring fair and balanced gameplay. Here's an overview of its role:

  • Spawnpoint Selection: The script determines the appropriate spawnpoints based on the player's selected character and team role, ensuring that players spawn in relevant locations.

  • Spawnpoint Retrieval: It provides the appropriate spawnpoint for each player, considering factors such as character selection and team affiliation.

using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices.WindowsRuntime;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using Hashtable = ExitGames.Client.Photon.Hashtable;

public class SpawnManager : MonoBehaviour
{
    public static SpawnManager instance;
    public GameObject AttackSpawn, DefenceSpawn;

    Spawnpoint[] spawnpoints;

    string PlayModeAttack = "Attack";
    string PlayModeDefense = "Defense";

    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }


        int TeamIndex = Random.Range(0, 1);
        string[] currentAttack = { "TeamA", "TeamB" };



        if (PhotonNetwork.LocalPlayer.CustomProperties["Team"].ToString() == currentAttack[TeamIndex])
        {
            spawnpoints = AttackSpawn.GetComponentsInChildren<Spawnpoint>();
            Debug.Log("No of Spawnpoints: "+spawnpoints.Length);
            PhotonNetwork.LocalPlayer.SetCustomProperties(new Hashtable { { "PlayMode", PlayModeAttack } });
        }
        else
        {
            spawnpoints = DefenceSpawn.GetComponentsInChildren<Spawnpoint>();
            Debug.Log("No of Spawnpoints: " + spawnpoints.Length);
            PhotonNetwork.LocalPlayer.SetCustomProperties(new Hashtable { { "PlayMode", PlayModeDefense } });
        }
    }



    public Transform GetSpawnPoint()
    {
        string Agent = (string)PhotonNetwork.LocalPlayer.CustomProperties["SelectedAgent"];
        int index = 0;
        if(Agent == "Connnor")
        {
            index = 0;
        }
        else if(Agent == "Chloe")
        {
            index= 1;
        }
        else if(Agent == "Ryan")
        {
            index = 2;
        }

        Debug.Log("Index:"+index+"Name: "+Agent);

        return spawnpoints[index].transform;
    }
}

Conclusion: With the implementation of player control, spawning, and associated management scripts, our game takes significant strides towards offering a compelling multiplayer experience. Players can now navigate the game world, interact with objectives, and engage in intense battles with their chosen characters. In the next update, we'll explore further enhancements, including game mode mechanics and objective-based gameplay elements. Stay tuned for more exciting developments as our game continues to evolve!


0
Subscribe to my newsletter

Read articles from Adhisivan directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Adhisivan
Adhisivan