Cannot assign "Death" because its a method group - c#

I have recently started to learn c# but I have encountered an error which I hope some of you will be able to help me with. We are developing a game in Unity5 and when the players health hits 0 the rag-doll function will kick in but unfortunately I cannot call death as it is a method group... Any ideas? Cheers. Here is the code(I commented it out so I could enter play mode in Unity because the error wasn't letting me in line 47 and 53):
using UnityEngine;
using System.Collections;
public class EnemyAI_Basic : MonoBehaviour
{
private EnemyAI_Basic enemyAI_Basic;
Animator controller;
float health;
Animator anim;
bool Alive = true;
public bool Dead = false;
int pointValue = 5;
private Collider myCollider;
private Rigidbody myRigidbody;
CapsuleCollider capsuleCollider;
void Start()
{
controller = GetComponentInParent<Animator>();
health = 40;
capsuleCollider = GetComponent<CapsuleCollider>();
anim = GetComponent<Animator>();
}
void Update()
{
if (!Dead)
{
anim.SetTrigger("Alive");
}
}
void Death()
{
Dead = true;
Alive = false;
capsuleCollider.isTrigger = true;
anim.SetTrigger("Dead");
Destroy(gameObject, 4f);
}
void OnEnable()
{
SetInitialReferences();
enemyAI_Basic.Death += ActivateRagdoll;
}
void OnDisable()
{
enemyAI_Basic.Death -= ActivateRagdoll;
}
void SetInitialReferences()
{
enemyAI_Basic = transform.root.GetComponent<EnemyAI_Basic>();
if (GetComponent<Collider>() != null)
{
myCollider = GetComponent<Collider>();
}
if (GetComponent<Rigidbody>() != null)
{
myRigidbody = GetComponent<Rigidbody>();
}
}
void ActivateRagdoll()
{
if (myRigidbody != null)
{
myRigidbody.isKinematic = false;
myRigidbody.useGravity = true;
}
if (myCollider != null)
{
myCollider.isTrigger = false;
myCollider.enabled = true;
}
}
}

I see your problem.
You're treating the death method like a delegate, which it isn't, in this case. You can only register methods to either delegates or events, you can't register methods to method groups.
For your situation, there are a few ways to resolve this.
First solution, and by far the easiest, put in a call to ActivateRagdoll inside the Death function, like so:
void Death()
{
Dead = true;
Alive = false;
capsuleCollider.isTrigger = true;
anim.SetTrigger("Dead");
ActivateRagdoll();
Destroy(gameObject, 4f);
}
Then remove the:
enemyAI_Basic.Death += ActivateRagdoll;
and
enemyAI_Basic.Death -= ActivateRagdoll;
from the OnEnable and OnDisable methods, that should remove any compiler errors.
Now, in order to use this method, all you have to do is call it from somewhere in code. Depending on how you kill the Ai, you may consider making that method public, or have some death logic inside your update loop,
if(health <= 0)
{
Death();
enabled = false; //disable the component so the update loop doesn't repeat the death procedure multiple times, Unity might not be pleased.
}
The other way, and the way that you're trying to do it, is to create an event that classes can subscribe methods to. This event will call all of the subscribed methods, providing them with a means of being notified, whenever something interesting happens, like the Ai dying in this case. This can be useful for external classes that shouldn't have to constantly check the death status of the Ai, after all, they may not be concerned with the Ai if its on full health. They only need to know, when the Ai does die. Events are ideal for this situation.
To make an event you'll be doing something like this:
public class EnemyAI_Basic : MonoBehaviour
{
//your other variables
public delegate void EnemyDies(); //declare the method types that can be registered to the event
public event EnemyDies onEnemyDeath; //declare event, using the delegate for the method type
//other methods here
private void OnEnable()
{
//must be the same method type as the event, the compiler will let you know if it isn't
onEnemyDeath += Death;
onEnemyDeath += ActivateRagdoll;
}
private void OnDisable()
{
onEnemyDeath -= Death;
onEnemyDeath -= ActivateRagdoll;
}
//other things here
//doesn't have to be public, just in case you want a bullet to kill the enemy
public void KillAi()
{
//checking for null basically says: is anybody registered? If not, nothing to invoke.
//It will get upset if you try and invoke an event without any registered methods, hence the check.
if(onEnemyDeath != null)
{
onEnemyDeath();
}
}
void Death()
{
Dead = true;
Alive = false;
capsuleCollider.isTrigger = true;
anim.SetTrigger("Dead");
//ActivateRagdoll(); You don't have to manually call this now, invoking the event will call it for you.
//Registration order is important, me thinks. Experiment :)
Destroy(gameObject, 4f);
}
}
There are several other ways to rig up a system like this, but, in your case this may be what you're after.
Some reading:
Events: https://www.codeproject.com/Articles/11541/The-Simplest-C-Events-Example-Imaginable
https://www.tutorialspoint.com/csharp/csharp_events.htm
Delegates: https://msdn.microsoft.com/en-us/library/ms173171.aspx
Method groups: What is a method group in C#?
Hopefully this helps.

Related

How to repeatedly execute a method until said otherwise

I have an EventSystem for managing my turn-based game in Unity.
public class EventSystem : MonoBehaviour
{
private static List<Action> _commandsQueue = new List<Action>();
private bool _canExecuteCommand = true;
public void AddToQueue(Action command)
{
_commandsQueue.Add(command);
}
private void StartCommandExecution()
{
_commandsQueue[0]();
_canExecuteCommand = false;
}
public void CommandExecutionComplete()
{
_canExecuteCommand = true;
}
public void PlayFirstCommandFromQueue()
{
if (_commandsQueue.Any() && _canExecuteCommand)
{
StartCommandExecution();
}
else
{
Debug.LogError("No Command In Queue");
}
}
}
How do I put a method in Update() until _canExecuteCommand is true again but only for some methods?
It is quite broad what you are trying to do but in general you would use an endless loop within a Coroutine.
You can create a generic routine which invokes any Action you pass in as parameter once a frame like e.g.
private IEnumerator InvokeEveryFrame(Action action)
{
// This looks strange but is okey in a Coroutine as long as you yield somewhere within
while(true)
{
action?.Invoke();
// This tells Unity to "pause" this routine here
// render the current frame and continue from here in the next frame
yield return null;
}
}
So all that's left is starting the routine using MonoBehaviour.StartCoroutine like e.g.
Coroutine routine = StartCoroutine(SomeParameterlessMethod);
or if you need parameters
Coroutine routine = StartCoroutine(() => SomeMethod(x, y, z));
and then at some point later stop it using MonoBehaviour.StopCoroutine and the stored Coroutine reference like e.g.
StopCoroutine(routine);
how exactly you store that reference is up to you of course up to you.

What is a correct way to pass an event as parameter?

First of all, I don't know if I stated the question correcly. I will try to explain my situation below.
I have a method:
public void FocusTargetZoom(Transform target,
float focusTime,
float zoomFactorPercentage,
OnStopFocusing onStopFocusing = null,
bool lockMovement = true)
{
if (lockMovement)
{
Character.Instance.LockMovement();
}
if(onStopFocusing != null)
{
//StopFocusing = onStopFocusing;
}
zoomFactor = initFOV * zoomFactorPercentage / 100f;
FocusOnTarget(target, focusTime);
}
where OnStopFocusing is my delegate void. This FocusTargetZoom method will be called from any other object, for example an object, who has OnTriggerEnter and OnTriggerExit events. Now, what I am trying to do is pass the OnTriggerExit event to the FocusTargetZoom method, so anytime my OnTriggerExit method will be called, it would stop zooming. Basically, the plan is:
Call FocusTargetZoom method from Caller;
Wait till Caller do something and trigger passed event to the method;
Stop zooming.
Yet I'm not sure if this is the right approach for this kind of problem. If you have some insights, please share!
An event is something that happens and triggers something else, that listens to it. So an event has a Listener and not a Caller.
You're using an Action here (a void delegate is basically an Action), and not an event.
To achieve your desired result you want a check to see if your target is still valid, so you need a delegate that returns something (such as a Func<bool>).
This Func<bool>, needs to returns false when we have the OnTriggerExit.
Also, you need to pass this Func<bool> to the method who is doing the focusing (FocusOnTarget in your case).
public void FocusTargetZoom(Transform target, float focusTime, float zoomFactorPercentage, Func<bool> whenToStopFunc, bool lockMovement = true)
{
if (lockMovement)
{
Character.Instance.LockMovement();
}
StopFocusing = whenToStopFunc;
zoomFactor = initFOV * zoomFactorPercentage / 100f;
FocusOnTarget(target, focusTime, whenToStopFunc);
}
private void FocusOnTarget(Transform target, float focusTime, Func<bool> whenToStopFunc)
{
while(notYetFocused)
{
if(whenToStopFunc != null && !whenToStopFunc())
{ KeepFocusing(); }
}
}
And the caller MonoBehaviour requires to be like this:
public void Caller : MonoBehaviour
{
bool isSelected = false;
void IsThisSelected() => isSelected;
private CallTheFocus() => _focusClass.FocusTargetZoom(transform, 1f, 1f, IsThisSelected);
private void OnTriggerEnter() => isSelected = true;
private void OnTriggerExit() => isSelected = false;
}

Unity Networked Ready check

Trying to create a ready check using PUN2 so that all players will load into the game scene at the same time, but I do not understand how to check another players custom property and keep a count of how many players are currently ready and if all are ready then start the game. I think I have a custom property set up for every player that should be but I am unsure if it working at all.
public class HeroSelectController : MonoBehaviour
{
[HideInInspector]
public string selectedHero;
private PhotonView PV;
private bool PlayerReady = false;
private ExitGames.Client.Photon.Hashtable _playerCustomProperties = new ExitGames.Client.Photon.Hashtable();
private void Update()
{
Debug.Log("Player Ready = " + _playerCustomProperties["PlayerReady"]);
}
private void HeroSelect()
{
PlayerReady = true;
selectedHero = "PlayerTest";
PhotonNetwork.SetPlayerCustomProperties(_playerCustomProperties);
_playerCustomProperties["PlayerReady"] = PlayerReady;
}
public void OnClickHeroButton()
{
HeroSelect();
if (PhotonNetwork.IsMasterClient)
{
foreach (var photonPlayer in PhotonNetwork.PlayerList)
{
photonPlayer.CustomProperties["PlayerReady"] = true;
PhotonNetwork.LoadLevel(3);
}
}
}
}
What is currently happening is that the master client can start the game regardless of everyone else's state. Feel like I might be overthinking all of this and there is a much similar solution as I would expect a function like this to be common place as I would expect something similar to be used in many online games so if I am going about completely the wrong way please point me a more suitable direction
Don't know exactly how Photon works but I assume these properties are already synchronized and as far as I understand you simply want some kind of a check like e.g.
private bool AllPlayersReady
{
get
{
foreach (var photonPlayer in PhotonNetwork.PlayerList)
{
if(photonPlayer.CustomProperties["PlayerReady"] == false) return false;
}
return true;
}
}
Which you could probably also shorten using Linq like
using System.Linq;
...
private bool AllPlayersReady => PhotonNetwork.PlayerList.All(player => player.CustomProperties["PlayerReady"] == true);
And then use it like
public void OnClickHeroButton()
{
HeroSelect();
if (!PhotonNetwork.IsMasterClient) return;
if(!AllPlayersReady)
{
return;
}
PhotonNetwork.LoadLevel(3);
}
This can still be enhanced since now the master has to press repeatedly until maybe everyone is ready. I would additionally use a Coroutine like
public void OnClickHeroButton()
{
HeroSelect();
if (!PhotonNetwork.IsMasterClient) return;
StartCoroutine (WaitAllReady ());
}
private IEnumerator WaitAllReady()
{
yield return new WaitUntil (() => AllPlayersReady);
PhotonNetwork.LoadLevel(3);
}
Thanks to Iggy:
Instead of a routine which checks every frame you can use OnPlayerPropertiesUpdate in order to check for the ready state
void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
{
if (!PhotonNetwork.IsMasterClient) return;
// you can even limit the check to make it
// only if "PlayerReady" is among the changed properties
if(!changedProps.Contains("PlayerReady")) return;
if(!AllPlayersReady) return;
PhotonNetwork.LoadLevel(3);
}
Note: Typed on smartphone but I hope the idea gets clear

Unity WebGL Gameobject disabled from somewhere

So, I have a update running on a monobehaviour script(Let's call it Script A) that's in the scene. This update fires a event, so other non-monobehaviour scripts can listen to this when they need a update for something. However, at the start of the app it runs the update on Script A for a short time, but after a while it randomly stops calling the update on Script A.
So I started to place some debugs in standard monobehaviour functions like awake, start, disable, enable etc. Turns out the object is disabled from somewhere without me using or calling it anywhere. This problem only occurs on WebGL. In the editor the OnDisable event is never called and the update keeps running fine.
I tried various fixes, like all the debugs logs everywhere, I tried debugging the stacktrace to find out what is calling this OnDisable, saidly this doesn't show in the WebGL console. It just says: StackTrace:
public class UpdateManager : MonoBehaviour
{
private static UpdateManager _instance;
public static UpdateManager Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType<UpdateManager>();
if (_instance == null)
{
GameObject updateManager = new GameObject("UpdateManager");
_instance = updateManager.AddComponent<UpdateManager>();
}
}
return _instance;
}
}
public delegate void OnUpdateEvent();
public event OnUpdateEvent OnUpdate;
private void Awake()
{
Debug.Log("UpdateManager: Awake!");
}
private void Start()
{
Debug.Log("UpdateManager: Start!");
}
private void OnEnable()
{
Debug.Log("UpdateManager: OnEnable!");
}
private void OnDisable()
{
Debug.Log("UpdateManager: OnDisable!, Stacktrace: " + UnityEngine.StackTraceUtility.ExtractStackTrace());
}
private void Update()
{
Debug.Log("UpdateManager: Update!");
OnUpdate?.Invoke();
}
I would like to know what is disabling this object since I have no clue. Good to note here is that it's only disabled once at the beginning, and after I enable it again it runs fine for the remaining time.
The only places I call this script is in a static class somewhere:
private static void initializeRefreshCycle(int expiresIn)
{
if (expiresIn > 0)
{
if (_refreshRoutine != null)
{
CoroutineManager.Instance.Stop(_refreshRoutine);
_refreshRoutine = null;
}
DateTime expirationDateTime = DateTime.Now;
_refreshTime = expirationDateTime.AddSeconds(expiresIn * 0.85f);
Debug.Log(string.Format("<< API TOKEN WILL BE AUTOMATICALLY REFRESHED IN {0} SECONDS >>", expiresIn * 0.85f));
UpdateManager.Instance.OnUpdate += OnUpdateEarly;
}
}
And then the function that actually listens to the call:
public static void OnUpdateEarly()
{
if (!HasAlmostExpired) return;
SSOCommunicator.RefreshToken(_refresh);
UpdateManager.Instance.OnUpdate -= OnUpdateEarly;
}
Looks like the first time it stops somewhere, and once it reached the subscribing again, it runs fine for the remaining time.
I found out it's a garbage collection issue. Since I would always call UpdateManager.Instance to call a function I never had a hard reference to that instance. So it was somehow destroyed at start only on WebGL. I solved it by keeping a hard reference in the awake of a script. So just having a simgple _updatamanager = Updatemanager.Instance solved it.

How to check a bool or call a function in script of Unity on Windows App Solution side

I need to call a function on Windows Store app side by checking a boolean from unity script. How to do that i used this code but it gives errors. I just need a simple solution in which i check a bool declared in unity script to call a function at ported windows store app end
using System;
using UnityEngine;
public class SphereScript : MonoBehaviour
{
private bool m_IsMoving = true;
private bool m_IsMovingLeft = false;
public Camera GameCamera;
public event Action<bool> SphereStateChanged;
public bool IsSphereMoving { get { return m_IsMoving; } }
void Start()
{
if (GameCamera == null)
{
throw new Exception("Camera is not attached to the sphere script!");
}
}
void FixedUpdate()
{
if (!m_IsMoving)
{
return;
}
if (m_IsMovingLeft)
{
transform.position -= new Vector3(0.2f, 0.0f);
if (GameCamera.WorldToScreenPoint(transform.position).x < 100.0f)
{
m_IsMovingLeft = false;
}
}
else
{
transform.position += new Vector3(0.2f, 0.0f);
if (GameCamera.WorldToScreenPoint(transform.position).x > Screen.width - 100.0f)
{
m_IsMovingLeft = true;
}
}
}
void OnGUI()
{
var buttonText = m_IsMoving ? "Stop sphere" : "Start sphere movement";
if (GUI.Button(new Rect(0, 0, Screen.width, 40), buttonText))
{
m_IsMoving = !m_IsMoving;
if (SphereStateChanged != null)
{
SphereStateChanged(m_IsMoving);
}
}
}
}
Complete code is here
The way I understand you question is that when something happens on the Unity side, you'd like to call a method on the Windows Store app side, right?
A nice way to do this is to have an event in your Unity code that your Win code can register for. For the sake of this example I'm going to call your event ButtonClicked.
First you need a static class that the event will be in. Create a new C# script in the Unity Assets folder and open it up. I called mine StaticInterop. Clear the code that was generated, and make it this:
public class StaticInterop
{
public static event EventHandler ButtonClicked;
public static void FireButtonClicked()
{
if (ButtonClicked != null)
{
ButtonClicked(null, null);
}
}
}
Now, in Unity whenever that thing happens (in this case when the button is clicked), do this line of code:
StaticInterop.FireButtonClicked();
That is everything on the Unity side done. So create a build, and open up the VS Windows Store app project it creates.
In the Win code you can now sign up for the event like this:
StaticInterop.ButtonClicked+= StaticInterop_ButtonClicked;
And declare this method for it to run:
void StaticInterop_ButtonClicked(object sender, EventArgs e)
{
// Do whatever you need here.
}
The important thing to note is that the StaticInterop static class is showing up in your Win code. This means that you can use it to transfer anything between the Unity and Win side. You could even have a method in the class called something like PauseGame(), and then run that from the Win side with StaticInterop.PauseGame().

Categories

Resources