I have been repeatedly getting an error, when trying to add a script to a gameObject. I have so far tried changing the MonoBehavior class name to what I had as the scripts name, and checked my code for errors, though none are shown in the terminal. Here`s my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace LE
{
public class InputHandler : MonoBehaviour
{
public float horizontal;
public float vertical;
public float moveAmountl;
public float mouseX;
public float mouseY
PlayerControls inputActions;
Vector2 movementInput;
Vector2 cameraInput
public void private void OnEnable() {
{
if (inputActions == null)
{
inputActions = new PlayerControls();
inputActions.PlayerMovement.Movement.performed += inputActions => movementInput = inputActions.ReadValue<Vector2>();
inputActions.PlayerMovement.Camera.performed += i => cameraInput = i.ReadValue<Vector2>();
}
inputActions.Enable();
}
private void private void OnDisable()
{
inputActions.Disable();
}
}
public void TickInput(float delta)
{
MoveInput(delta);
}
private void MoveInput(float delta)
{
horizontal = movementInput.x;
vertical = movementInput.y;
moveAmountv= Mathf.Clamp01(Mathf.Abs(horizontal) + Mathf.abs(vertical));
mouseX = cameraInput.x;
mouseY = cameraInput.y;
}
}
}
It could be this (note typed twice private and public void):
public void private void OnEnable() {
private void private void OnDisable()
{
inputActions.Disable();
}
should be
private void OnEnable() {
private void OnDisable()
{
inputActions.Disable();
}
Related
I'm making a multiplayer game, when I create a room, there is an error, but I can still enter the room. Does the error affect the room? or do I just let it go? or is there something wrong with my coding? please help me fix it.
This is the error :
CreateRoom failed. Client is on MasterServer (must be Master Server
for matchmaking)but not ready for operations (State: Joining). Wait
for callback: OnJoinedLobby or OnConnectedToMaster.
UnityEngine.Debug:LogError (object)
Photon.Pun.PhotonNetwork:CreateRoom
(string,Photon.Realtime.RoomOptions,Photon.Realtime.TypedLobby,string[])
(at Assets/Photon/PhotonUnityNetworking/Code/PhotonNetwork.cs:1782)
Launcher:CreateRoom (int) (at Assets/Script/Launcher.cs:63)
Launcher:b__13_1 () (at Assets/Script/Launcher.cs:35)
UnityEngine.EventSystems.EventSystem:Update () (at
Library/PackageCache/com.unity.ugui#1.0.0/Runtime/EventSystem/EventSystem.cs:501)
This is the code :
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Photon.Pun;
using TMPro;
using Photon.Realtime;
using System.Linq;
public class Launcher : MonoBehaviourPunCallbacks
{
public static Launcher Instance;
[SerializeField] TMP_InputField roomNameInputField;
[SerializeField] TMP_Text errorText;
[SerializeField] TMP_Text roomNameText;
[SerializeField] Transform roomListContent;
[SerializeField] GameObject roomListItemPrefab;
[SerializeField] GameObject PlayerListItemPrefab;
[SerializeField] Transform playerListContent;
[SerializeField] GameObject startGameButton;
public GameObject twoPlayerButton;
public GameObject threePlayerButton;
public GameObject fourPlayerButton;
void Awake(){
Instance = this;
}
// Start is called before the first frame update
void Start()
{
Debug.Log("Connecting to Master");
PhotonNetwork.ConnectUsingSettings();
twoPlayerButton.GetComponent<UnityEngine.UI.Button>().onClick.AddListener(() => CreateRoom(2));
threePlayerButton.GetComponent<UnityEngine.UI.Button>().onClick.AddListener(() => CreateRoom(3));
fourPlayerButton.GetComponent<UnityEngine.UI.Button>().onClick.AddListener(() => CreateRoom(4));
}
public override void OnConnectedToMaster()
{
Debug.Log("Connected to Master");
PhotonNetwork.JoinLobby();
PhotonNetwork.AutomaticallySyncScene = true;
}
public override void OnJoinedLobby()
{
MenuSetting.Instance.OpenMenu("firstmenu");
Debug.Log("Joined Lobby");
}
public void CreateRoom(int numPlayers){
if(string.IsNullOrEmpty(roomNameInputField.text)){
return;
}
RoomOptions roomOptions = new RoomOptions();
roomOptions.MaxPlayers = (byte)numPlayers;
ExitGames.Client.Photon.Hashtable customProperties = new ExitGames.Client.Photon.Hashtable();
customProperties.Add("MinPlayers", 2);
roomOptions.CustomRoomProperties = customProperties;
PhotonNetwork.CreateRoom(roomNameInputField.text + numPlayers, roomOptions);
MenuSetting.Instance.OpenMenu("Loading");
}
public override void OnJoinedRoom()
{
MenuSetting.Instance.OpenMenu("room");
roomNameText.text = PhotonNetwork.CurrentRoom.Name;
Player[] players = PhotonNetwork.PlayerList;
foreach(Transform child in playerListContent)
{
Destroy(child.gameObject);
}
for(int i = 0; i < players.Count(); i++){
Instantiate(PlayerListItemPrefab, playerListContent).GetComponent<PlayerListItem>().SetUp(players[i]);
}
if (players.Count() >= 2)
{
startGameButton.SetActive(PhotonNetwork.IsMasterClient); //only show the button if there are more than 2 players
}
else
{
startGameButton.SetActive(false); // hide the button if there are less than 2 players
}
}
public override void OnMasterClientSwitched(Player newMasterClient)
{
startGameButton.SetActive(PhotonNetwork.IsMasterClient);
}
public override void OnCreatedRoom()
{
Debug.Log("Room created successfully!");
}
public override void OnCreateRoomFailed(short returnCode, string message)
{
errorText.text = "Room Creation Failed: " + message;
MenuSetting.Instance.OpenMenu("error");
}
public void StartGame()
{
PhotonNetwork.LoadLevel(1);
}
public void LeaveRoom()
{
PhotonNetwork.LeaveRoom();
MenuSetting.Instance.OpenMenu("main_menu");
}
public void JoinRoom(RoomInfo info ){
PhotonNetwork.JoinRoom(info.Name);
MenuSetting.Instance.OpenMenu("loading");
}
public override void OnLeftRoom()
{
MenuSetting.Instance.OpenMenu("loading");
}
public override void OnRoomListUpdate(List<RoomInfo> roomList)
{
foreach(Transform trans in roomListContent){
Destroy(trans.gameObject);
}
for(int i = 0; i < roomList.Count; i++){
if(roomList[i].RemovedFromList)
continue;
Instantiate(roomListItemPrefab, roomListContent).GetComponent<RoomListItem>().SetUp(roomList[i]);
}
}
public override void OnPlayerEnteredRoom(Player newPlayer)
{
Instantiate(PlayerListItemPrefab, playerListContent).GetComponent<PlayerListItem>().SetUp(newPlayer);
}
}
So I have Ctr C Ctr V an Interaction System from YT channel.
I have perfectly copied line by line But the only issue was that he was using the old input system and used KeyPressedDown and keyPressedUp to make 2 boolean's which change according what state is the keypressed, I tried to mimic it with new Input system by making it from a button to a Axis value which follows the logic of if its ClickedAxis>0.4 its true and ClockedAxis==0 false, as a quick press it worked so I was happy BUT here comes the bug
My if statement is Being Spammed Constantly like none stop. My statement basically say's that if the Interacting is true Do :
bool interacting;
If(interacting)
{
float HoldingDownTimer+=Time.DeltaTime;
if(HoldingDownTimer>=5)
{
//Do your task;
}
}
but When I run the statement For some reason it prints 0.0110823 values which are stuck at that range of number's no lower no higher, My theory is that its been spam called.
Here's the Script
If your interested for all the components the YT channel is this guy's VeryHotShark
using UnityEngine;
using NaughtyAttributes;
public class LocomotionScript : MonoBehaviour
{
#region Data
[BoxGroup("Animation Handling Data")]
Animator animator;
int isWalkingHash;
[SerializeField] bool movementPressed;
[BoxGroup("Input Handling Data")]
private CharacterController controller;
public MovementInput input;
Vector2 currentMovement;
[SerializeField] float speed = 1;
private Vector3 velocity;
private float gravity = -9.81f;
public Transform ground;
public float distanceToGround = 0.4f;
public LayerMask groundMask;
private Vector2 smoothinMovement;
private float smoothInputSpeed;
private Vector2 tempCurrentMovement;
private bool isGrounded;
public InteractionInputData interactionInput;
#endregion
private void Awake()
{
animator = GetComponent<Animator>();
input = new MovementInput();
controller = GetComponent<CharacterController>();
input.KeyBoard.ASWD.performed += ctx =>
{
currentMovement = ctx.ReadValue<Vector2>();
movementPressed = currentMovement.x != 0 || currentMovement.y != 0;
};
}
private void Start()
{
interactionInput.Reset();
}
void GetInteractionInputData()
{
interactionInput.InteractedClicked = input.KeyBoard.Interact.ReadValue<float>() > 0.1f;
interactionInput.InteractedReleased = input.KeyBoard.Interact.ReadValue<float>() == 0f;
}
private void Update()
{
LocoMotion();
Grav();
Interact();
GetInteractionInputData();
}
void Grav()
{
isGrounded = Physics.CheckSphere(ground.position, distanceToGround, groundMask);
if (isGrounded && velocity.y<0)
{
velocity.y = -2f;
}
velocity.y += gravity * Time.deltaTime;
controller.Move(velocity * Time.deltaTime);
}
void Interact()
{
}
void LocoMotion()
{
tempCurrentMovement=Vector2.SmoothDamp(tempCurrentMovement, currentMovement, ref smoothinMovement, smoothInputSpeed);
Vector3 movement = (tempCurrentMovement.y * transform.forward) + (tempCurrentMovement.x * transform.right);
WalkAnimation();
controller.Move(movement * speed * Time.deltaTime);
}
void WalkAnimation()
{
animator.SetBool("WalkingHush", movementPressed);
}
private void OnEnable()
{
input.KeyBoard.ASWD.Enable();
input.KeyBoard.Interact.Enable();
}
private void OnDisable()
{
input.KeyBoard.ASWD.Enable();
input.KeyBoard.Interact.Enable();
}
}
//
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace th
{
public class InteractionController : MonoBehaviour
{
#region Variables
[Header("Data")]
public InteractionData interactionData;
public InteractionInputData interactionInputData;
[Space]
[Header("RaySetting's")]
public float rayDistance;
public float raySphereRadius;
public LayerMask interactableLayer;
#endregion
#region Private
private Camera m_cam;
private bool m_interacting;
private float m_holderTimer = 0.0f;
#endregion
#region BuildIn
private void Awake()
{
m_cam = FindObjectOfType<Camera>();
}
private void Update()
{
CheckForInteractable();
CheckForInteractableInput();
}
#endregion
#region Crafted Methodds
void CheckForInteractable()
{
Ray _ray = new Ray(m_cam.transform.position, m_cam.transform.forward);
RaycastHit _hitInfo;
bool _hitSomething = Physics.SphereCast(_ray, raySphereRadius, out _hitInfo,
rayDistance,interactableLayer);
if (_hitSomething)
{
InteractableBase _interactable = _hitInfo.transform.GetComponent<InteractableBase>();
if (_interactable != null)
{
if (interactionData.isEmpty())
{
interactionData.Interactable = _interactable;
}
else
{
if (!interactionData.IsSameInteractible(_interactable))
interactionData.Interactable = _interactable;
}
}
}
else
{
interactionData.ResetData();
}
Debug.DrawRay(_ray.origin, _ray.direction * rayDistance, _hitSomething ? Color.green : Color.red);
}
void CheckForInteractableInput()
{
if (interactionData.isEmpty())
{
return;
}
if (interactionInputData.InteractedClicked)
{
m_interacting = true;
m_holderTimer = 0f;
}
if (interactionInputData.InteractedReleased)
{
m_interacting = false;
m_holderTimer = 0f;
}
if (m_interacting)
{
if (!interactionData.Interactable.IsInteractible)
return;
if (interactionData.Interactable.HoldInteract)
{
m_holderTimer += Time.deltaTime;
Debug.Log(m_holderTimer);
if (m_holderTimer >= interactionData.Interactable.holdDuration)
{
interactionData.Interact();
m_interacting = false;
}
}
else
{
interactionData.Interact();
m_interacting = false;
}
}
}
#endregion
}
}
///
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace th
{
[CreateAssetMenu(fileName ="Interaction Data",menuName = "InteractionSystem/InteractionData")]
public class InteractionData : ScriptableObject
{
private InteractableBase m_interactible;
public InteractableBase Interactable
{
get => m_interactible;
set => m_interactible = value;
}
public void Interact()
{
m_interactible.OnInteract();
ResetData();
}
public bool IsSameInteractible(InteractableBase _newInteractible) => m_interactible == _newInteractible;
public bool isEmpty() => m_interactible == null;
public void ResetData()=> m_interactible = null;
}
}
//
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "InteractionInputData", menuName = "InteractionSystem/InputData")]
public class InteractionInputData : ScriptableObject
{
private bool m_interactedClicked;
private bool m_interactRelease;
public bool InteractedClicked
{
get => m_interactedClicked;
set => m_interactedClicked = value;
}
public bool InteractedReleased
{
get => m_interactRelease;
set => m_interactRelease = value;
}
public void Reset()
{
m_interactedClicked = false;
m_interactRelease = false;
}
}
//
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace th
{
public interface IInteractible
{
float HoldDurration { get; }
bool HoldInteract { get; }
bool MultipleUse { get;}
bool IsInteractible { get; }
void OnInteract();
}
}
//
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace th{
public class InteractableBase : MonoBehaviour,IInteractible
{
#region Variables
[Header("Interactible Settings")]
public float holdDuration;
[Space]
public bool holdInteract;
public bool multipleUse;
public bool isInteractible;
#endregion
#region Properties
public float HoldDurration => holdDuration;
public bool HoldInteract => holdInteract;
public bool MultipleUse => multipleUse;
public bool IsInteractible => isInteractible;
#endregion
#region Methods
public void OnInteract()
{
Debug.Log("Interacted: " + gameObject.name);
}
#endregion
}
}
Well without parsing through your entire code:
once
HoldingDownTimer >= 5
is true you still keep adding more time in the next frame so the condition is still true in the next frame => you will keep calling it all the following frames as well.
You could introduce a flag somewhat like e.g.
private bool alreadyCalled;
and then
if(interacting)
{
if(!alreadyCalled)
{
HoldingDownTimer += Time.DeltaTime;
if(HoldingDownTimer >= 5)
{
alreadyCalled = true;
//Do your task;
}
}
}
else
{
alreadyCalled = false;
}
As far as I understood your code I think:
I found the issue in it actually you are checking if the 'm_holdTimer' >= 'holdDuration' then perform interaction and you are making it equal to 0 on click start and click end.
So it means if the player has continuously held the button then it will not reset 'm_holdTimer'... so it will continue to call the function recursively because player hasn't actually ended the click so there is not any single function which is reseting 'm_holdTimer' to 0. still if you didn't get my point just copy this function and paste it instead of your own and see if it works, if it do work then you can check the difference between line of code then you'll understand it for sure.
void CheckForInteractableInput()
{
if (interactionData.isEmpty())
{
return;
}
if (interactionInputData.InteractedClicked)
{
m_interacting = true;
m_holderTimer = 0f;
}
if (interactionInputData.InteractedReleased)
{
m_interacting = false;
m_holderTimer = 0f;
}
if (m_interacting)
{
if (!interactionData.Interactable.IsInteractible)
return;
if (interactionData.Interactable.HoldInteract)
{
m_holderTimer += Time.deltaTime;
Debug.Log(m_holderTimer);
if (m_holderTimer >= interactionData.Interactable.holdDuration)
{
m_holderTimer = 0f;
interactionData.Interact();
m_interacting = false;
}
}
else
{
interactionData.Interact();
m_interacting = false;
}
}
}
Hope it helps... Happy coding :)
I have class UnitComponent that contains references to every script of the unit. Every unit has scripts UnitUI and UnitStats. So I can access UnitUI methods from UnitStats using UnitComponent class (for example: unitComponent.unitUI.SetHealthUI()). Of course, every script has a reference to UnitComponent class as well to be able to use it. These three scripts have children (PlayerComponent, PlayerUI, PlayerStats). These classes have new methods, for example PlayerUI has SetManaUI() method. But I can't use SetManaUI() using PlayerComponent without creating new variable with PlayerUI class because UnitUI variable doesn't have this method. Is there any option to make new methods accessible through variable unitUI by changing class to PlayerUI or I have to create new PlayerUI variable and somehow get rid of unitUI.
I've tried to make virtual empty method SetManaUI() in UnitUI class and override it in PlayerUI class to make everything work, but it seems like a bad practise because UnitUI can become full of useless empty virtual methods.
Simplified version of scripts
Script that connects scripts together
using UnityEngine;
public class Collector : MonoBehaviour
{
public Stats stats;
public UI ui;
}
UI script
using UnityEngine;
public class UI : MonoBehaviour
{
Collector collector;
public void UIMethod()
{
Debug.Log("UI method");
}
}
Stats script
using UnityEngine;
public class Stats : MonoBehaviour
{
Collector collector;
public void StatsMethod()
{
Debug.Log("Stats method");
}
}
Child of collector script
public class ChildCollector : Collector
{
public string someNewData;
public string someNewData1;
public string someNewData2;
public void Start()
{
ui.ChildUIMethod(); //has to be executed and write "Child UI method"
stats.ChildStatsMethod(); //has to be executed and write "Child Stats method"
}
}
Child of UI script
using UnityEngine;
public class ChildUI : UI
{
ChildCollector childCollector;
public void ChildUIMethod()
{
Debug.Log("Child UI Method");
}
}
Child of Stats script
using UnityEngine;
public class ChildStats : Stats
{
ChildCollector childCollector;
public void ChildStatsMethod()
{
Debug.Log("Child Stats Method");
}
}
Full version of scripts
Unit Component script
using UnityEngine;
public class UnitComponent : MonoBehaviour
{
public Animator animator;
public UnitStats unitStats;
public UnitUI unitUI;
}
UnitStats script
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
public class UnitStats : MonoBehaviour
{
public UnitComponent unitComponent;
public float maxHealth;
public float health;
public List<GameObject> lootDrop;
protected virtual void Start()
{
if (health <= 0 || health > maxHealth)
health = maxHealth;
unitComponent.unitUI.SetHealthUI(health, maxHealth);
CheckDeath();
}
private void CreateDamagePopup(float takenDamage)
{
GameObject damagePopupCanvas = Instantiate(unitComponent.unitUI.damagePopupCanvasPref, transform.position, Quaternion.identity, transform);
damagePopupCanvas.GetComponentInChildren<Text>().text = takenDamage.ToString();
Vector3 randomPosOffset = new Vector3(Random.Range(-0.1f, 0.1f), Random.Range(-0.1f, 0.1f), 0f);
damagePopupCanvas.transform.position += randomPosOffset;
Destroy(damagePopupCanvas, 3f);
}
public float CalculateHealthPercentage()
{
return (health / maxHealth);
}
protected void CheckDeath()
{
if (health <= 0)
{
unitComponent.animator.SetTrigger("isDead");
DropLoot();
}
}
protected void CheckOverheal()
{
if (health > maxHealth)
{
health = maxHealth;
}
}
public void DealDamage(int damage)
{
unitComponent.unitUI.healthBar.SetActive(true);
health -= damage;
unitComponent.unitUI.SetHealthUI(health, maxHealth);
CheckDeath();
CreateDamagePopup(damage);
}
public void HealCharacter(int heal)
{
health += heal;
CheckOverheal();
unitComponent.unitUI.SetHealthUI(health, maxHealth);
}
public void DropLoot()
{
foreach (GameObject item in lootDrop)
Instantiate(item, transform.position, Quaternion.identity);
}
}
UnitUI script
using UnityEngine;
using UnityEngine.UI;
public class UnitUI : MonoBehaviour
{
public UnitComponent unitComponent;
public GameObject damagePopupCanvasPref;
public GameObject healthBar;
protected Slider healthBarSlider;
virtual public void Start()
{
healthBarSlider = healthBar.GetComponent<Slider>();
}
public virtual void SetHealthUI(float health, float maxHealth)
{
if (healthBarSlider)
healthBarSlider.value = health / maxHealth;
}
public virtual void SetCoinsUI(int coinAmount) { } //Necessary for PlayerUI script working
public virtual void SetManaUI(float mana, float maxMana) { } //Necessary for PlayerUI script working
}
PlayerComponent script
using UnityEngine;
public class PlayerComponent : UnitComponent
{
public PlayerMovement playerMovement;
public PlayerSlot playerSlot;
public Rigidbody2D rb;
}
PlayerStats script
using UnityEngine;
public class PlayerStats : UnitStats
{
protected int coins;
public float mana;
public float maxMana;
public float manaRegenPerSec;
protected override void Start()
{
UI = gameObject.GetComponent<PlayerUI>();
animator = gameObject.GetComponent<Animator>();
if (health <= 0 || health > maxHealth)
health = maxHealth;
UI.SetHealthUI(health, maxHealth);
UI.SetManaUI(mana, maxMana);
CheckDeath();
}
public void Update()
{
if (mana < maxMana)
{
mana += manaRegenPerSec * Time.deltaTime;
UI.SetManaUI(mana, maxMana);
}
}
private void Awake()
{
DontDestroyOnLoad(this);
}
public void AddCoins(int amount)
{
coins += amount;
UI.SetCoinsUI(amount);
}
}
PlayerUI script
using System;
using UnityEngine;
using UnityEngine.UI;
public class PlayerUI : UnitUI
{
[SerializeField] protected GameObject manaBar;
[SerializeField] protected Text manaValue;
[SerializeField] protected Text healthValue;
[SerializeField] protected Text coinValue;
protected Slider manaBarSlider;
public override void Start()
{
healthBarSlider = healthBar.GetComponent<Slider>();
manaBarSlider = manaBar.GetComponent<Slider>();
}
public override void SetHealthUI(float health, float maxHealth)
{
healthBarSlider.value = health / maxHealth;
healthValue.text = health.ToString() + "/" + maxHealth.ToString();
}
public override void SetManaUI(float mana, float maxMana)
{
manaBarSlider.value = mana / maxMana;
manaValue.text = Math.Round(mana).ToString() + "/" + Math.Round(maxMana).ToString();
}
public override void SetCoinsUI(int coinAmount)
{
coinValue.text = coinAmount.ToString();
}
}
I followed this video on how to create health bars automatically for units.
This is my HealthBar class.
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
public class HealthBar : MonoBehaviour
{
#region SerializeFields
[SerializeField] private Image foregroundImage;
[SerializeField] private float updateSpeedInSec = 0.5f;
[SerializeField] private float positionOffset = 1f;
#endregion
#region NonSerializeFields
private Health health;
#endregion
public void SetHealth(Health healthToSet)
{
health = healthToSet;
healthToSet.OnHealthPctChanged += HandleHealthChanged;
}
private void HandleHealthChanged(float pct)
{
StartCoroutine(ChangeToPct(pct));
}
private IEnumerator ChangeToPct(float pct)
{
float preChangedPct = foregroundImage.fillAmount;
float elapsedTime = 0f;
while (elapsedTime < updateSpeedInSec)
{
elapsedTime += Time.deltaTime;
foregroundImage.fillAmount = Mathf.Lerp(preChangedPct, pct, elapsedTime / updateSpeedInSec);
yield return null;
}
foregroundImage.fillAmount = pct;
}
private void LateUpdate()
{
var worldToScreenPoint = Camera.main.WorldToScreenPoint(health.transform.position + (Vector3) Vector2.up * positionOffset);
transform.position = worldToScreenPoint;
}
private void OnDestroy()
{
health.OnHealthPctChanged -= HandleHealthChanged;
}
}
So this is my HealthBarController class, which is where the SetHealth method is called, put on a canvas.
using System.Collections.Generic;
using UnityEngine;
public class HealthBarController : MonoBehaviour
{
#region SerializeFields
[SerializeField] private HealthBar healthBar;
#endregion
#region NonSerializeFields
private Dictionary<Health, HealthBar> healthBars = new Dictionary<Health, HealthBar>();
#endregion
private void Awake()
{
Health.OnHealthAdded += AddHealthBar;
Health.OnHealthRemoved += RemoveHealthBar;
}
private void AddHealthBar(Health health)
{
if (healthBars.ContainsKey(health)) return;
var newHealthBar = Instantiate(healthBar, transform);
healthBars.Add(health, newHealthBar);
healthBar.SetHealth(health);
}
private void RemoveHealthBar(Health health)
{
if (!healthBars.ContainsKey(health)) return;
Destroy(healthBars[health].gameObject);
healthBars.Remove(health);
}
}
And this is my Health class on a player character.
using System;
using UnityEngine;
public class Health : MonoBehaviour, IDamageable
{
#region SerializeFields
[SerializeField] protected int maxHealth = 100;
public static event Action<Health> OnHealthAdded = delegate { };
public static event Action<Health> OnHealthRemoved = delegate { };
public event Action<float> OnHealthPctChanged = delegate { };
#endregion
#region NonSerializeFields
protected int currentHealth;
#endregion
private void OnEnable()
{
currentHealth = maxHealth;
OnHealthAdded(this);
}
public void TakeDamage(int damage)
{
currentHealth -= damage;
float currentHealthPct = (float) currentHealth / maxHealth;
OnHealthPctChanged(currentHealthPct);
if (currentHealth <= 0)
{
Die();
}
}
protected void Die()
{
OnHealthRemoved(this);
Destroy(gameObject);
}
}
The problem I'm having is in the LateUpdate method, the health field is null, even though in the SetHealth method, it was set properly.
In the AddHealthBar method of your HealthBarController.cs script you are assigning the Health class of your character to the Prefab healthBar rather than the newly created Health instance newHealthBar.
Simply replace healthBar.SetHealth(health); with newHealthBar.SetHealth(health); (line 30).
Ok, I'm confused a bit. I studied UnityEvent and Messaging System by this tutorial. But I have one question, that I cannot understand. How can I pass arguments to Invoking function?
For example(I have EventManager like in tutorial):
void OnEnable() {
EventManager.StartListening ("OnPlayerTeleport", TeleportPlayer);
}
void OnDisable() {
EventManager.StopListening ("OnPlayerTeleport", TeleportPlayer);
}
void TeleportPlayer () {
float yPos = transform.position.y;
yPos += 20.0f;
transform.position = new Vector3 (transform.position.x, yPos, transform.position.z);
}
And I have trigger:
void Update () {
if (Input.GetButtonDown ("Teleport")) {
EventManager.TriggerEvent ("OnPlayerTeleport");
}
}
But, what if I want to pass 'height' value to function 'TeleportPlayer':
void TeleportPlayer (float h) {
float yPos = transform.position.y;
yPos += h;
transform.position = new Vector3 (transform.position.x, yPos, transform.position.z);
}
How can I do this?
Use C# delegate/Action instead of Unity's UnityEvent. It is faster than Unity's event. I ported it few days ago. You just need to modify that a little bit to get what you are looking for.
1.Make Action take float by changing all Action declaration to Action<float> which means that it will allow functions with float parameter.
2.Now, make the TriggerEvent function take a float parameter. by changing
public static void TriggerEvent(string eventName)
to
public static void TriggerEvent(string eventName, float h)
Here is the new EventManager script.
using UnityEngine;
using System.Collections.Generic;
using System;
public class EventManager : MonoBehaviour
{
private Dictionary<string, Action<float>> eventDictionary;
private static EventManager eventManager;
public static EventManager instance
{
get
{
if (!eventManager)
{
eventManager = FindObjectOfType(typeof(EventManager)) as EventManager;
if (!eventManager)
{
Debug.LogError("There needs to be one active EventManger script on a GameObject in your scene.");
}
else
{
eventManager.Init();
}
}
return eventManager;
}
}
void Init()
{
if (eventDictionary == null)
{
eventDictionary = new Dictionary<string, Action<float>>();
}
}
public static void StartListening(string eventName, Action<float> listener)
{
Action<float> thisEvent;
if (instance.eventDictionary.TryGetValue(eventName, out thisEvent))
{
thisEvent += listener;
}
else
{
thisEvent += listener;
instance.eventDictionary.Add(eventName, thisEvent);
}
}
public static void StopListening(string eventName, Action<float> listener)
{
if (eventManager == null) return;
Action<float> thisEvent;
if (instance.eventDictionary.TryGetValue(eventName, out thisEvent))
{
thisEvent -= listener;
}
}
public static void TriggerEvent(string eventName, float h)
{
Action<float> thisEvent = null;
if (instance.eventDictionary.TryGetValue(eventName, out thisEvent))
{
thisEvent.Invoke(h);
}
}
}
Test:
public class Test : MonoBehaviour
{
void OnEnable()
{
EventManager.StartListening("OnPlayerTeleport", TeleportPlayer);
}
void OnDisable()
{
EventManager.StopListening("OnPlayerTeleport", TeleportPlayer);
}
void Update()
{
if (Input.GetButtonDown("Teleport"))
{
EventManager.TriggerEvent("OnPlayerTeleport", 5);
}
}
void TeleportPlayer(float h)
{
float yPos = transform.position.y;
yPos += h;
transform.position = new Vector3(transform.position.x, yPos, transform.position.z);
}
}
Create a typed subclass of unityevent to take a parameter that is passed through to listeners and invoke that instead.
Documentation with an example using integer data is here
https://docs.unity3d.com/ScriptReference/Events.UnityEvent_1.html