Posted on

How to Code a Kill Feed UI in Unity

For this lesson on how to create game mechanics for Unity, I will show you how to create a Kill Feed UI as found in most first-person shooters and other fighting games. By the end of this tutorial, you will have a working kill feed prefab that you can add to any Unity project. This prefab is also very easy to use. Just add it to any scene you which to use it in and then there are three options for using this mechanic. You can have if display just names, names and how, or names and an icon of how each player was kill. There is then a public function for each of these options that you can call from anywhere in your project using the singleton of the prefab.

Unlock Code and Member Content

KillListing.cs

using UnityEngine;
using UnityEngine.UI;

//This script is attached to the kill listing prefab
public class KillListing : MonoBehaviour
{
    [SerializeField] Text killerDisplay; //The UI Text that displays the killer name
    [SerializeField] Text killedDisplay; //The UI Text that displays the Killed name

    [SerializeField] Text howDisplay;
    [SerializeField] Image howImageDisplay;

    private void Start()
    {
        Destroy(gameObject, 10f); //To keep our feed from getting too long we destroy old listings after a given time.
    }

    //This function is called by the AddNewKillListing function to set the two text variables.
    public void SetNames(string killerName, string killedName)
    {
        killerDisplay.text = killerName;
        killedDisplay.text = killedName;
    }

    public void SetNamesAndHow(string killerName, string killedName, string how)
    {
        killerDisplay.text = killerName;
        killedDisplay.text = killedName;

        howDisplay.text = how;
    }

    public void SetNamesAndHowImage(string killerName, string KilledName, Sprite howImage)
    {
        killerDisplay.text = killerName;
        killedDisplay.text = KilledName;
        howImageDisplay.sprite = howImage;
    }
}

KillFeed.cs

using UnityEngine;


//This script is attached to the Kill Feed object and is used to control the feed.
public class KillFeed : MonoBehaviour
{
    public static KillFeed instance; //Singleton of this script so you can call the AddNewKillListing function from anywhere in your project.
    [SerializeField] GameObject killListingPrefab; //This is a variable to hold the kill listing prefab.
    [SerializeField] GameObject killListingWithImagePrefab; //This is a variable to hold the kill listing prefab.
    [SerializeField] Sprite[] howImages;

    private void Start()
    {
        instance = this;
    }

    //To call this function use the following example code
    //KillFeed.instance.AddNewKillListing("KillerName", "KilledName");
    public void AddNewKillListing(string killer, string killed)
    {
        GameObject temp = Instantiate(killListingPrefab, transform);
        temp.transform.SetSiblingIndex(0);
        KillListing tempListing = temp.GetComponent<KillListing>();
        tempListing.SetNames(killer, killed);
    }


    //To call this function use the following example code
    //KillFeed.instance.AddNewKillListingWithHow("KillerName", "KilledName", "Splattered");
    public void AddNewKillListingWithHow(string killer, string killed, string how)
    {
        GameObject temp = Instantiate(killListingPrefab, transform);
        temp.transform.SetSiblingIndex(0);
        KillListing tempListing = temp.GetComponent<KillListing>();
        tempListing.SetNamesAndHow(killer, killed, how);
    }

    //To call this function use the following example code
    //KillFeed.instance.AddNewKillListingWithHowImage("KillerName", "KilledName", 0);
    public void AddNewKillListingWithHowImage(string killer, string killed, int howIndex)
    {
        GameObject temp = Instantiate(killListingWithImagePrefab, transform);
        temp.transform.SetSiblingIndex(0);
        KillListing tempListing = temp.GetComponent<KillListing>();
        tempListing.SetNamesAndHowImage(killer, killed, howImages[howIndex]);
    }
}
using Photon.Chat;
using Photon.Pun;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class PhotonChatManager : MonoBehaviour, IChatClientListener
{
    #region Setup

    [SerializeField] GameObject joinChatButton;
    ChatClient chatClient;
    bool isConnected;
    [SerializeField] string username;

    public void UsernameOnValueChange(string valueIn)
    {
        username = valueIn;
    }

    public void ChatConnectOnClick()
    {
        isConnected = true;
        chatClient = new ChatClient(this);
        //chatClient.ChatRegion = "US";
        chatClient.Connect(PhotonNetwork.PhotonServerSettings.AppSettings.AppIdChat, PhotonNetwork.AppVersion, new AuthenticationValues(username));
        Debug.Log("Connenting");
    }

    #endregion Setup

    #region General

    [SerializeField] GameObject chatPanel;
    string privateReceiver = "";
    string currentChat;
    [SerializeField] InputField chatField;
    [SerializeField] Text chatDisplay;

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        if (isConnected)
        {
            chatClient.Service();
        }

        if (chatField.text != "" &amp;&amp; Input.GetKey(KeyCode.Return))
        {
            SubmitPublicChatOnClick();
            SubmitPrivateChatOnClick();
        }
    }

    #endregion General

    #region PublicChat

    public void SubmitPublicChatOnClick()
    {
        if (privateReceiver == "")
        {
            chatClient.PublishMessage("RegionChannel", currentChat);
            chatField.text = "";
            currentChat = "";
        }
    }

    public void TypeChatOnValueChange(string valueIn)
    {
        currentChat = valueIn;
    }

    #endregion PublicChat

    #region PrivateChat

    public void ReceiverOnValueChange(string valueIn)
    {
        privateReceiver = valueIn;
    }

    public void SubmitPrivateChatOnClick()
    {
        if (privateReceiver != "")
        {
            chatClient.SendPrivateMessage(privateReceiver, currentChat);
            chatField.text = "";
            currentChat = "";
        }
    }

    #endregion PrivateChat

    #region Callbacks

    public void DebugReturn(DebugLevel level, string message)
    {
        //throw new System.NotImplementedException();
    }

    public void OnChatStateChange(ChatState state)
    {
        if(state == ChatState.Uninitialized)
        {
            isConnected = false;
            joinChatButton.SetActive(true);
            chatPanel.SetActive(false);
        }
    }

    public void OnConnected()
    {
        Debug.Log("Connected");
        joinChatButton.SetActive(false);
        chatClient.Subscribe(new string[] { "RegionChannel" });
    }

    public void OnDisconnected()
    {
        isConnected = false;
        joinChatButton.SetActive(true);
        chatPanel.SetActive(false);
    }

    public void OnGetMessages(string channelName, string[] senders, object[] messages)
    {
        string msgs = "";
        for (int i = 0; i &lt; senders.Length; i++)
        {
            msgs = string.Format("{0}: {1}", senders[i], messages[i]);

            chatDisplay.text += "\n" + msgs;

            Debug.Log(msgs);
        }

    }

    public void OnPrivateMessage(string sender, object message, string channelName)
    {
        string msgs = "";

        msgs = string.Format("(Private) {0}: {1}", sender, message);

        chatDisplay.text += "\n " + msgs;

        Debug.Log(msgs);
        
    }

    public void OnStatusUpdate(string user, int status, bool gotMessage, object message)
    {
        throw new System.NotImplementedException();
    }

    public void OnSubscribed(string[] channels, bool[] results)
    {
        chatPanel.SetActive(true);
    }

    public void OnUnsubscribed(string[] channels)
    {
        throw new System.NotImplementedException();
    }

    public void OnUserSubscribed(string channel, string user)
    {
        throw new System.NotImplementedException();
    }

    public void OnUserUnsubscribed(string channel, string user)
    {
        throw new System.NotImplementedException();
    }

    #endregion Callbacks
}

Posted on

How to Code and Create Zelda Hearts Health Bar in Unity

In this lesson on to create fun game mechanics in Unity, I will teach you how to code and create Zelda Hearts in Unity. The famous heart health can be found in every Zelda game since the original. This icon game mechanic and is loved by every Zelda fan and add a different take on a typical fill health bar.

To create the Zelda health bar we will need to create a UI panel for holding all the hearts. We will then create the heart prefab. Each heart prefab will form a single linked list. We will then create an algorithm that will recurse through each heart, filling them with the correct fill amount.

Unlock Code and Member Content

HeartContainer.cs

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

public class HeartContainer : MonoBehaviour
{
    public HeartContainer next;

    [Range(0, 1)] float fill;
    [SerializeField] Image fillImage;

    public void SetHeart(float count)
    {
        fill = count;
        fillImage.fillAmount = fill;
        count--;
        if (next != null)
        {
            next.SetHeart(count);
        }
    }
}

ZeldaHealthBar.cs

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

public class ZeldaHealthBar : MonoBehaviour
{
    public static ZeldaHealthBar instance;

    [SerializeField] GameObject heartContainerPrefab;

    [SerializeField] List<GameObject> heartContainers;
    int totalHearts;
    float currentHearts;
    HeartContainer currentContainer;

    // Start is called before the first frame update
    void Start()
    {
        instance = this;
        heartContainers = new List<GameObject>();
        
    }

    //ZeldaHealthBar.instance.SetupHearts(valueIn);
    public void SetupHearts(int heartsIn)
    {
        heartContainers.Clear();
        for(int i = transform.childCount -1; i >=0; i--)
        {  
            Destroy(transform.GetChild(i).gameObject);
        }

        totalHearts = heartsIn;
        currentHearts = (float)totalHearts;
        
        for (int i = 0; i < totalHearts; i++)
        {
            GameObject newHeart = Instantiate(heartContainerPrefab, transform);
            heartContainers.Add(newHeart);
            if(currentContainer != null)
            {
                currentContainer.next = newHeart.GetComponent<HeartContainer>();
            }
            currentContainer = newHeart.GetComponent<HeartContainer>();
        }
        currentContainer = heartContainers[0].GetComponent<HeartContainer>();

    }

    //ZeldaHealthBar.instance.SetCurrentHealth(valueIn);
    public void SetCurrentHealth(float health)
    {
        currentHearts = health;
        currentContainer.SetHeart(currentHearts);
        
    }

    //ZeldaHealthBar.instance.AddHearts(valueIn);
    public void AddHearts(float healthUp)
    {
        currentHearts += healthUp;
        if(currentHearts > totalHearts)
        {
            currentHearts = (float)totalHearts;
        }
        currentContainer.SetHeart(currentHearts);
    }

    //ZeldaHealthBar.instance.RemoveHearts(valueIn);
    public void RemoveHearts(float healthDown)
    {
        currentHearts -= healthDown;
        if(currentHearts < 0)
        {
            currentHearts = 0f;
        }
        currentContainer.SetHeart(currentHearts);
    }

    //ZeldaHealthBar.instance.AddContainer(valueIn);
    public void AddContainer()
    {
        GameObject newHeart = Instantiate(heartContainerPrefab, transform);
        currentContainer = heartContainers[heartContainers.Count - 1].GetComponent<HeartContainer>();
        heartContainers.Add(newHeart);
        

        if (currentContainer != null)
        {
            currentContainer.next = newHeart.GetComponent<HeartContainer>();
        }
    
        currentContainer = heartContainers[0].GetComponent<HeartContainer>();

        totalHearts++;
        currentHearts = totalHearts;
        SetCurrentHealth(currentHearts);
    }
}

DemoZeldaHealth.cs

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

public class DemoZeldaHealth : MonoBehaviour
{
    int total;
    float amountUp;
    float amountDown;

    public void TotalInput(string valueIn)
    {
        total = int.Parse(valueIn);
    }

    public void SubmitSetup()
    {
        ZeldaHealthBar.instance.SetupHearts(total);
    }

    public void UpAmountInput(string valueIn)
    {
        amountUp = float.Parse(valueIn);
    }

    public void SubmitUp()
    {
        ZeldaHealthBar.instance.AddHearts(amountUp);
    }

    public void DownAmountInput(string valueIn)
    {
        amountDown = float.Parse(valueIn);
    }

    public void SubmitDown()
    {
        ZeldaHealthBar.instance.RemoveHearts(amountDown);
    }

    public void AddHeartContainer()
    {
        ZeldaHealthBar.instance.AddContainer();
    }
}
using Photon.Chat;
using Photon.Pun;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class PhotonChatManager : MonoBehaviour, IChatClientListener
{
    #region Setup

    [SerializeField] GameObject joinChatButton;
    ChatClient chatClient;
    bool isConnected;
    [SerializeField] string username;

    public void UsernameOnValueChange(string valueIn)
    {
        username = valueIn;
    }

    public void ChatConnectOnClick()
    {
        isConnected = true;
        chatClient = new ChatClient(this);
        //chatClient.ChatRegion = "US";
        chatClient.Connect(PhotonNetwork.PhotonServerSettings.AppSettings.AppIdChat, PhotonNetwork.AppVersion, new AuthenticationValues(username));
        Debug.Log("Connenting");
    }

    #endregion Setup

    #region General

    [SerializeField] GameObject chatPanel;
    string privateReceiver = "";
    string currentChat;
    [SerializeField] InputField chatField;
    [SerializeField] Text chatDisplay;

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        if (isConnected)
        {
            chatClient.Service();
        }

        if (chatField.text != "" &amp;&amp; Input.GetKey(KeyCode.Return))
        {
            SubmitPublicChatOnClick();
            SubmitPrivateChatOnClick();
        }
    }

    #endregion General

    #region PublicChat

    public void SubmitPublicChatOnClick()
    {
        if (privateReceiver == "")
        {
            chatClient.PublishMessage("RegionChannel", currentChat);
            chatField.text = "";
            currentChat = "";
        }
    }

    public void TypeChatOnValueChange(string valueIn)
    {
        currentChat = valueIn;
    }

    #endregion PublicChat

    #region PrivateChat

    public void ReceiverOnValueChange(string valueIn)
    {
        privateReceiver = valueIn;
    }

    public void SubmitPrivateChatOnClick()
    {
        if (privateReceiver != "")
        {
            chatClient.SendPrivateMessage(privateReceiver, currentChat);
            chatField.text = "";
            currentChat = "";
        }
    }

    #endregion PrivateChat

    #region Callbacks

    public void DebugReturn(DebugLevel level, string message)
    {
        //throw new System.NotImplementedException();
    }

    public void OnChatStateChange(ChatState state)
    {
        if(state == ChatState.Uninitialized)
        {
            isConnected = false;
            joinChatButton.SetActive(true);
            chatPanel.SetActive(false);
        }
    }

    public void OnConnected()
    {
        Debug.Log("Connected");
        joinChatButton.SetActive(false);
        chatClient.Subscribe(new string[] { "RegionChannel" });
    }

    public void OnDisconnected()
    {
        isConnected = false;
        joinChatButton.SetActive(true);
        chatPanel.SetActive(false);
    }

    public void OnGetMessages(string channelName, string[] senders, object[] messages)
    {
        string msgs = "";
        for (int i = 0; i &lt; senders.Length; i++)
        {
            msgs = string.Format("{0}: {1}", senders[i], messages[i]);

            chatDisplay.text += "\n" + msgs;

            Debug.Log(msgs);
        }

    }

    public void OnPrivateMessage(string sender, object message, string channelName)
    {
        string msgs = "";

        msgs = string.Format("(Private) {0}: {1}", sender, message);

        chatDisplay.text += "\n " + msgs;

        Debug.Log(msgs);
        
    }

    public void OnStatusUpdate(string user, int status, bool gotMessage, object message)
    {
        throw new System.NotImplementedException();
    }

    public void OnSubscribed(string[] channels, bool[] results)
    {
        chatPanel.SetActive(true);
    }

    public void OnUnsubscribed(string[] channels)
    {
        throw new System.NotImplementedException();
    }

    public void OnUserSubscribed(string channel, string user)
    {
        throw new System.NotImplementedException();
    }

    public void OnUserUnsubscribed(string channel, string user)
    {
        throw new System.NotImplementedException();
    }

    #endregion Callbacks
}

Posted on

Among Us Multiplayer – Picking Imposters (PUN 2 in Unity)

For this lesson on how to create a multiplayer Among Us game in Unity, we will show you how to pick and sync an imposter across the network. Having a random imposter is vital for our among us game as the imposter is what makes the game challenging and gives the game an opposition.

First, we will pick a random number between zero and the number of players in our room. We will then send that number to all the clients and whichever client is that number in the room will become the imposter. That player will then be able to kill the other players.

Unlock Code and Member Content

AU_SpawnPoints.cs

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

public class AU_SpawnPoints : MonoBehaviour
{
    public static AU_SpawnPoints instance;

    public Transform[] spawnPoints;

    // Start is called before the first frame update
    void Start()
    {
        instance = this;
    }

}

MyPhotonPlayer.cs

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

public class MyPhotonPlayer : MonoBehaviour
{
    PhotonView myPV;
    GameObject myPlayerAvatar;

    Player[] allPlayers;
    int myNumberInRoom;

    // Start is called before the first frame update
    void Start()
    {
        myPV = GetComponent<PhotonView>();

        allPlayers = PhotonNetwork.PlayerList;
        foreach(Player p in allPlayers)
        {
            if(p!= PhotonNetwork.LocalPlayer)
            {
                myNumberInRoom++;
            }
        }


        if (myPV.IsMine)
        {
            myPlayerAvatar = PhotonNetwork.Instantiate(Path.Combine("PhotonPrefabs", "AU_Player"), AU_SpawnPoints.instance.spawnPoints[myNumberInRoom].position, Quaternion.identity);
        }
    }


}

AU_GameController.cs

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

public class AU_GameController : MonoBehaviour
{
    PhotonView myPV;

    int whichPlayerIsImposter;

    // Start is called before the first frame update
    void Start()
    {
        myPV = GetComponent<PhotonView>();
        if(PhotonNetwork.IsMasterClient)
        {
            PickImposter();
        }
    }

    void PickImposter()
    {
        whichPlayerIsImposter = Random.Range(0, PhotonNetwork.CurrentRoom.PlayerCount);
        myPV.RPC("RPC_SyncImposter", RpcTarget.All, whichPlayerIsImposter);
        Debug.Log("Imposter " + whichPlayerIsImposter);
    }

    [PunRPC]
    void RPC_SyncImposter(int playerNumber)
    {
        whichPlayerIsImposter = playerNumber;
        AU_PlayerController.localPlayer.BecomeImposter(whichPlayerIsImposter);
    }
}
using Photon.Chat;
using Photon.Pun;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class PhotonChatManager : MonoBehaviour, IChatClientListener
{
    #region Setup

    [SerializeField] GameObject joinChatButton;
    ChatClient chatClient;
    bool isConnected;
    [SerializeField] string username;

    public void UsernameOnValueChange(string valueIn)
    {
        username = valueIn;
    }

    public void ChatConnectOnClick()
    {
        isConnected = true;
        chatClient = new ChatClient(this);
        //chatClient.ChatRegion = "US";
        chatClient.Connect(PhotonNetwork.PhotonServerSettings.AppSettings.AppIdChat, PhotonNetwork.AppVersion, new AuthenticationValues(username));
        Debug.Log("Connenting");
    }

    #endregion Setup

    #region General

    [SerializeField] GameObject chatPanel;
    string privateReceiver = "";
    string currentChat;
    [SerializeField] InputField chatField;
    [SerializeField] Text chatDisplay;

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        if (isConnected)
        {
            chatClient.Service();
        }

        if (chatField.text != "" &amp;&amp; Input.GetKey(KeyCode.Return))
        {
            SubmitPublicChatOnClick();
            SubmitPrivateChatOnClick();
        }
    }

    #endregion General

    #region PublicChat

    public void SubmitPublicChatOnClick()
    {
        if (privateReceiver == "")
        {
            chatClient.PublishMessage("RegionChannel", currentChat);
            chatField.text = "";
            currentChat = "";
        }
    }

    public void TypeChatOnValueChange(string valueIn)
    {
        currentChat = valueIn;
    }

    #endregion PublicChat

    #region PrivateChat

    public void ReceiverOnValueChange(string valueIn)
    {
        privateReceiver = valueIn;
    }

    public void SubmitPrivateChatOnClick()
    {
        if (privateReceiver != "")
        {
            chatClient.SendPrivateMessage(privateReceiver, currentChat);
            chatField.text = "";
            currentChat = "";
        }
    }

    #endregion PrivateChat

    #region Callbacks

    public void DebugReturn(DebugLevel level, string message)
    {
        //throw new System.NotImplementedException();
    }

    public void OnChatStateChange(ChatState state)
    {
        if(state == ChatState.Uninitialized)
        {
            isConnected = false;
            joinChatButton.SetActive(true);
            chatPanel.SetActive(false);
        }
    }

    public void OnConnected()
    {
        Debug.Log("Connected");
        joinChatButton.SetActive(false);
        chatClient.Subscribe(new string[] { "RegionChannel" });
    }

    public void OnDisconnected()
    {
        isConnected = false;
        joinChatButton.SetActive(true);
        chatPanel.SetActive(false);
    }

    public void OnGetMessages(string channelName, string[] senders, object[] messages)
    {
        string msgs = "";
        for (int i = 0; i &lt; senders.Length; i++)
        {
            msgs = string.Format("{0}: {1}", senders[i], messages[i]);

            chatDisplay.text += "\n" + msgs;

            Debug.Log(msgs);
        }

    }

    public void OnPrivateMessage(string sender, object message, string channelName)
    {
        string msgs = "";

        msgs = string.Format("(Private) {0}: {1}", sender, message);

        chatDisplay.text += "\n " + msgs;

        Debug.Log(msgs);
        
    }

    public void OnStatusUpdate(string user, int status, bool gotMessage, object message)
    {
        throw new System.NotImplementedException();
    }

    public void OnSubscribed(string[] channels, bool[] results)
    {
        chatPanel.SetActive(true);
    }

    public void OnUnsubscribed(string[] channels)
    {
        throw new System.NotImplementedException();
    }

    public void OnUserSubscribed(string channel, string user)
    {
        throw new System.NotImplementedException();
    }

    public void OnUserUnsubscribed(string channel, string user)
    {
        throw new System.NotImplementedException();
    }

    #endregion Callbacks
}