Merry Fragmas 3.0: Lobby, Player Data, and Deathmatch Rules

確認済のバージョン: 5.5

-

難易度: 中級

In this multi-day session we will be building a Networked Multiplayer First Person Shooter game from the ground up. This is the final session in and it we cover how to add a lobby the our game, how to get custom player data into a match, the structure of simple bots, and the rules for a deathmatch. Insert Christmas pun here!

Merry Fragmas 3.0: Lobby, Player Data, and Deathmatch Rules

中級 Multiplayer Networking

Bot

Code snippet

using UnityEngine;
using UnityEngine.Networking;

public class Bot : NetworkBehaviour 
{
    public bool botCanShoot = true;

    [SerializeField] float shotCooldown = 1f;

    PlayerShooting playerShooting;
    NetworkAnimator anim;            
    float ellapsedTime = 0f;


    void Awake()
    {
        playerShooting = GetComponent ();
        anim = GetComponent (); 

        GetComponent ().playerName = "Bot";
    }

    [ServerCallback]
    void Update()
    {
        anim.animator.SetFloat("Speed", 0f);
        anim.animator.SetFloat ("Strafe", 0f);

        if (Input.GetKey (KeyCode.Keypad8))
            anim.animator.SetFloat ("Speed", 1f);

        if (Input.GetKey (KeyCode.Keypad2))
            anim.animator.SetFloat ("Speed", -1f);

        if (Input.GetKey (KeyCode.Keypad4))
            anim.animator.SetFloat ("Strafe", -1f);

        if (Input.GetKey (KeyCode.Keypad6))
            anim.animator.SetFloat ("Strafe", 1f);

        if (Input.GetKeyDown (KeyCode.Keypad7))
            anim.SetTrigger ("Died");

        if (Input.GetKeyDown (KeyCode.Keypad9))
            anim.SetTrigger ("Restart");


        BotAutoFire ();
    }

    [Server]
    void BotAutoFire()
    {
        ellapsedTime += Time.deltaTime;

        if (ellapsedTime < shotCooldown)
            return;

        ellapsedTime = 0f;
        if(playerShooting.enabled)
            playerShooting.FireAsBot ();
    }
}

BotSpawner

Code snippet

using UnityEngine;
using UnityEngine.Networking;

public class BotSpawner : NetworkBehaviour 
{
    [SerializeField] GameObject botPrefab;

    [ServerCallback]
    void Start()
    {
        GameObject obj = Instantiate (botPrefab, transform.position, transform.rotation);
        obj.GetComponent ().localPlayerAuthority = false;
        obj.AddComponent ();
        NetworkServer.Spawn (obj);
    }
}

FragmasLobbyHook

Code snippet

using UnityEngine;
using Prototype.NetworkLobby;
using UnityEngine.Networking;

public class FragmasLobbyHook : LobbyHook 
{
    public override void OnLobbyServerSceneLoadedForPlayer(NetworkManager manager, GameObject lobbyPlayer,
        GameObject gamePlayer)
    {
        LobbyPlayer lPlayer = lobbyPlayer.GetComponent ();
        Player gPlayer = gamePlayer.GetComponent ();

        gPlayer.playerName = lPlayer.playerName;
        gPlayer.playerColor = lPlayer.playerColor;
    }
}

LookAtCamera

Code snippet

//This script is used to make UI elements face the player's camera

using UnityEngine;

public class LookAtCamera: MonoBehaviour 
{
    Transform mainCamera;   //The camera's transform


    void Start()
    {
        //Set the Main Camera as the target
        mainCamera = Camera.main.transform;
    }

    //Update after all other updates have run
    void LateUpdate()
    {
        if (mainCamera == null)
            return;
        
        //Apply the rotation needed to look at the camera. Note, since pointing a UI text element
        //at the camera makes it appear backwards, we are actually pointing this object
        //directly *away* from the camera.
        transform.rotation = Quaternion.LookRotation (transform.position - mainCamera.position);
    }
}

Player

Code snippet

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

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

public class Player : NetworkBehaviour 
{
    [SyncVar (hook = "OnNameChanged")] public string playerName;
    [SyncVar (hook = "OnColorChanged")] public Color playerColor;

    [SerializeField] ToggleEvent onToggleShared;
    [SerializeField] ToggleEvent onToggleLocal;
    [SerializeField] ToggleEvent onToggleRemote;
    [SerializeField] float respawnTime = 5f;

    static List players = new List ();

    GameObject mainCamera;
    NetworkAnimator anim;

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

        EnablePlayer ();
    }

    [ServerCallback]
    void OnEnable()
    {
        if (!players.Contains (this))
            players.Add (this);
    }

    [ServerCallback]
    void OnDisable()
    {
        if (players.Contains (this))
            players.Remove (this);
    }

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

        anim.animator.SetFloat ("Speed", Input.GetAxis ("Vertical"));
        anim.animator.SetFloat ("Strafe", Input.GetAxis ("Horizontal"));
    }

    void DisablePlayer()
    {
        if (isLocalPlayer) 
        {
            PlayerCanvas.canvas.HideReticule ();
            mainCamera.SetActive (true);
        }

        onToggleShared.Invoke (false);

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

    void EnablePlayer()
    {
        if (isLocalPlayer) 
        {
            PlayerCanvas.canvas.Initialize ();
            mainCamera.SetActive (false);
        }

        onToggleShared.Invoke (true);

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

    public void Die()
    {
        if(isLocalPlayer || playerControllerId == -1)
            anim.SetTrigger ("Died");
        
        if (isLocalPlayer) 
        {
            PlayerCanvas.canvas.WriteGameStatusText ("You Died!");
            PlayerCanvas.canvas.PlayDeathAudio ();
        }

        DisablePlayer ();

        Invoke ("Respawn", respawnTime);
    }

    void Respawn()
    {
        if(isLocalPlayer || playerControllerId == -1)
            anim.SetTrigger ("Restart");

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

        EnablePlayer ();
    }

    void OnNameChanged(string value)
    {
        playerName = value;
        gameObject.name = playerName;
        GetComponentInChildren (true).text = playerName;
    }

    void OnColorChanged(Color value)
    {
        playerColor = value;
        GetComponentInChildren ().ChangeColor (playerColor);
    }

    [Server]
    public void Won()
    {
        for (int i = 0; i < players.Count; i++)
            players [i].RpcGameOver (netId, name);

        Invoke ("BackToLobby", 5f);
    }

    [ClientRpc]
    void RpcGameOver(NetworkInstanceId networkID, string name)
    {
        DisablePlayer ();

        Cursor.lockState = CursorLockMode.None;
        Cursor.visible = true;

        if (isLocalPlayer)
        {
            if (netId == networkID)
                PlayerCanvas.canvas.WriteGameStatusText ("You Won!");
            else
                PlayerCanvas.canvas.WriteGameStatusText ("Game Over!\n" + name + " Won");
        }
    }

    void BackToLobby()
    {
        FindObjectOfType ().SendReturnToLobby ();
    }
}

PlayerShooting

Code snippet

using UnityEngine;
using UnityEngine.Networking;

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

    [SyncVar (hook = "OnScoreChanged")] int score;

    Player player;
    float ellapsedTime;
    bool canShoot;

    void Start()
    {
        player = GetComponent ();
        shotEffects.Initialize ();

        if (isLocalPlayer)
            canShoot = true;
    }

    [ServerCallback]
    void OnEnable()
    {
        score = 0;
    }

    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) 
            {
                bool wasKillShot = enemy.TakeDamage ();

                if (wasKillShot && ++score >= killsToWin)
                    player.Won ();          
            }
        }

        RpcProcessShotEffects (result, hit.point);
    }

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

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

    void OnScoreChanged(int value)
    {
        score = value;
        if (isLocalPlayer)
            PlayerCanvas.canvas.SetKills (value);
    }

    public void FireAsBot()
    {
        CmdFireShot (firePosition.position, firePosition.forward);
    }
}

Multiplayer Networking

  1. Merry Fragmas 3.0: Multiplayer FPS Foundation
  2. Merry Fragmas 3.0: UI, Graphics, and Animations
  3. Merry Fragmas 3.0: Lobby, Player Data, and Deathmatch Rules