For this lesson on how to network Among Us with Photon in Unity, I will show you how to sync the kill mechanic with an RPC. This will make it so when the imposter kills a player it happens across all clients and a body prefab is instantiated across the network as well.
Unlock Code and Member Content
AU_PlayerController.cs
using Photon.Pun; using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEngine.InputSystem; public class AU_PlayerController : MonoBehaviour, IPunObservable { [SerializeField] bool hasControl; public static AU_PlayerController localPlayer; //Components Rigidbody myRB; Animator myAnim; Transform myAvatar; //Player movement [SerializeField] InputAction WASD; Vector2 movementInput; [SerializeField] float movementSpeed; float direction = 1; //Player Color static Color myColor; SpriteRenderer myAvatarSprite; //Player Hat static Sprite myHatSprite; SpriteRenderer myHatHolder; //Role [SerializeField] bool isImposter; [SerializeField] InputAction KILL; List<AU_PlayerController> targets; [SerializeField] Collider myCollider; bool isDead; [SerializeField] GameObject bodyPrefab; public static List<Transform> allBodies; List<Transform> bodiesFound; [SerializeField] InputAction REPORT; [SerializeField] LayerMask ignoreForBody; //Interaction [SerializeField] InputAction MOUSE; Vector2 mousePositionInput; Camera myCamera; [SerializeField] InputAction INTERACTION; [SerializeField] LayerMask interactLayer; //Networking PhotonView myPV; [SerializeField] GameObject lightMask; [SerializeField] lightcaster myLightCaster; private void Awake() { KILL.performed += KillTarget; REPORT.performed += ReportBody; INTERACTION.performed += Interact; } private void OnEnable() { WASD.Enable(); KILL.Enable(); REPORT.Enable(); MOUSE.Enable(); INTERACTION.Enable(); } private void OnDisable() { WASD.Disable(); KILL.Disable(); REPORT.Disable(); MOUSE.Disable(); INTERACTION.Disable(); } // Start is called before the first frame update void Start() { myPV = GetComponent<PhotonView>(); if(myPV.IsMine) { localPlayer = this; } myCamera = transform.GetChild(1).GetComponent<Camera>(); targets = new List<AU_PlayerController>(); myRB = GetComponent<Rigidbody>(); myAnim = GetComponent<Animator>(); myAvatar = transform.GetChild(0); myAvatarSprite = myAvatar.GetComponent<SpriteRenderer>(); myHatHolder = transform.GetChild(0).GetChild(1).GetComponent<SpriteRenderer>(); if (!myPV.IsMine) { myCamera.gameObject.SetActive(false); lightMask.SetActive(false); myLightCaster.enabled = false; return; } if (myColor == Color.clear) myColor = Color.white; myAvatarSprite.color = myColor; if(allBodies == null) { allBodies = new List<Transform>(); } bodiesFound = new List<Transform>(); if (myHatSprite != null) myHatHolder.sprite = myHatSprite; } // Update is called once per frame void Update() { myAvatar.localScale = new Vector2(direction, 1); if (!myPV.IsMine) return; movementInput = WASD.ReadValue<Vector2>(); myAnim.SetFloat("Speed", movementInput.magnitude); if (movementInput.x != 0) { direction = Mathf.Sign(movementInput.x); } if(allBodies.Count > 0) { BodySearch(); } if(REPORT.triggered) { if (bodiesFound.Count == 0) return; Transform tempBody = bodiesFound[bodiesFound.Count - 1]; allBodies.Remove(tempBody); bodiesFound.Remove(tempBody); tempBody.GetComponent<AU_Body>().Report(); } mousePositionInput = MOUSE.ReadValue<Vector2>(); } private void FixedUpdate() { if (!myPV.IsMine) return; myRB.velocity = movementInput * movementSpeed; } public void SetColor(Color newColor) { myColor = newColor; if (myAvatarSprite != null) { myAvatarSprite.color = myColor; } } public void SetHat(Sprite newHat) { myHatSprite = newHat; myHatHolder.sprite = myHatSprite; } public void SetRole(bool newRole) { isImposter = newRole; } private void OnTriggerEnter(Collider other) { if (other.tag == "Player") { AU_PlayerController tempTarget = other.GetComponent<AU_PlayerController>(); if (isImposter) { if (tempTarget.isImposter) return; else { targets.Add(tempTarget); } } } } private void OnTriggerExit(Collider other) { if (other.tag == "Player") { AU_PlayerController tempTarget = other.GetComponent<AU_PlayerController>(); if (targets.Contains(tempTarget)) { targets.Remove(tempTarget); } } } void KillTarget(InputAction.CallbackContext context) { if (!myPV.IsMine) return; if (!isImposter) return; if (context.phase == InputActionPhase.Performed) { if (targets.Count == 0) return; else { if (targets[targets.Count - 1].isDead) return; transform.position = targets[targets.Count - 1].transform.position; //targets[targets.Count - 1].Die(); targets[targets.Count - 1].myPV.RPC("RPC_Kill", RpcTarget.All); targets.RemoveAt(targets.Count - 1); } } } [PunRPC] void RPC_Kill() { Die(); } public void Die() { if (!myPV.IsMine) return; //AU_Body tempBody = Instantiate(bodyPrefab, transform.position, transform.rotation).GetComponent<AU_Body>(); AU_Body tempBody = PhotonNetwork.Instantiate(Path.Combine("PhotonPrefabs", "AU_Body"), transform.position, transform.rotation).GetComponent<AU_Body>(); tempBody.SetColor(myAvatarSprite.color); isDead = true; myAnim.SetBool("IsDead", isDead); gameObject.layer = 9; myCollider.enabled = false; } void BodySearch() { foreach(Transform body in allBodies) { RaycastHit hit; Ray ray = new Ray(transform.position, body.position - transform.position); Debug.DrawRay(transform.position, body.position - transform.position, Color.cyan); if(Physics.Raycast(ray, out hit, 1000f, ~ignoreForBody)) { if (hit.transform == body) { Debug.Log(hit.transform.name); Debug.Log(bodiesFound.Count); if (bodiesFound.Contains(body.transform)) return; bodiesFound.Add(body.transform); } else { bodiesFound.Remove(body.transform); } } } } private void ReportBody(InputAction.CallbackContext obj) { if (bodiesFound == null) return; if (bodiesFound.Count == 0) return; Transform tempBody = bodiesFound[bodiesFound.Count - 1]; allBodies.Remove(tempBody); bodiesFound.Remove(tempBody); tempBody.GetComponent<AU_Body>().Report(); } void Interact(InputAction.CallbackContext context) { if (context.phase == InputActionPhase.Performed) { //Debug.Log("Here"); RaycastHit hit; Ray ray = myCamera.ScreenPointToRay(mousePositionInput); if (Physics.Raycast(ray, out hit,interactLayer)) { if (hit.transform.tag == "Interactable") { AU_Interactable temp = hit.transform.GetComponent<AU_Interactable>(); temp.PlayMiniGame(); } } } } public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) { if (stream.IsWriting) { stream.SendNext(direction); } else { this.direction = (float)stream.ReceiveNext(); } } }
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 != "" && 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 < 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 }