I have been attempting to use event handling for a game's input. When seeing others use similar methods, they are able to add a void function to the delegate variable without an error.
Whenever I try to add the Move() function to OnAxisChange, I receive the following error:
Cannot implicitly convert type 'void' to 'CharacterView.InputAction'
public class CharacterView : MonoBehaviour {
public delegate void InputAction();
public static event InputAction OnAxisChange;
public Vector2 InputAxis
{
get
{
float x = Input.GetAxisRaw("Horizontal");
float y = Input.GetAxisRaw("Vertical");
return (new Vector2(x, y));
}
}
private void Update()
{
Vector2 input = InputAxis;
if (input.x != 0 || input.y != 0)
{
if (OnAxisChange != null)
{
OnAxisChange();
}
}
}
}
The following is the class that handles the event.
public class CharacterController : MonoBehaviour {
private void OnEnable()
{
CharacterView.OnAxisChange += Move();
}
private void OnDisable()
{
CharacterView.OnAxisChange -= Move();
}
public void Move()
{
Debug.Log("Entered the move function!");
}
}
Using delegates for event handling is still a bit foreign to me, so I assume I am misunderstanding something.
You need to remove () after Move. Like that you are calling your method and try to add the return type of it which is nothing. Just change it to the following.
CharacterView.OnAxisChange += Move;
CharacterView.OnAxisChange -= Move;
Related
I am trying to make a script that will display the direction of a force with an arrow. The arrow is a GameObject that is initialized at startup. Upon initialization the public static GameObject ForceArrowBase is set to the object that already exists. This object is then instantiated when calling CreateNewForceArrow.
The forceVector in the ForceArrow class should not be a value, but rather some sort of reference/pointer or something in that nature. So when forceArrow.forceVector = forceVector is being called in the CreateNewForceArrow method, it shouldn't set the value to be whatever the value is at that point in time. Rather, it should set it to refer to the part of the memory that holds the variable data of the caller.
So when WindForce's Wind.Data.Speed changes, the forceVector in the ForceArrow refers to the data point Wind.Data.Speed.
In a way, it would be: void ForceVector { get => Wind.Data.Speed; }. But of course, if the CreateNewForceArrow method was called by ForceCreatingScript2, then it should be: void ForceVector { get => instancedForceCreatingScript2.forceOutput; }
So, is there anyway to parse the variable in as a pointer-type-thingy rather than the value of the variable?
public class ForceArrow : MonoBehaviour {
public static GameObject ForceArrowBase;
private static bool _initialized = false;
public GameObject refObject;
public Vector3 forceVector;
public float force;
public void FixedUpdate() {
transform.rotation = Quaternion.LookRotation(forceVector);
}
public static void CreateNewForceArrow(string name, ref Vector3 forceVectorReference, GameObject forceObject) {
var forceArrowObject = Instantiate<GameObject>(ForceArrowBase);
var forceArrow = forceArrowObject.GetComponent<ForceArrow>();
forceArrowObject.transform.position = forceObject.transform.position;
forceArrowObject.gameObject.name = name + "_forceArrow";
forceArrow.forceVector = forceVectorReference;
forceArrow.refObject = forceObject;
}
}
public class ForceCreatingScript1 : MonoBehaviour {
public Vector3 forceVector;
void Start() {
ForceArrow.CreateNewForceArrow(name, ref forceVector, gameObject);
}
void FixedUpdate() {
forceVector *= 1.2f;
}
}
public class ForceCreatingScript2 : MonoBehaviour {
public Vector3 forceOutput;
void Start() {
ForceArrow.CreateNewForceArrow(name, ref forceOutput, gameObject);
}
void FixedUpdate() {
forceOutput *= 1.6f;
}
}
public class WindForce : MonoBehaviour {
void Start() {
ForceArrow.CreateNewForceArrow(name, ref Wind.Data.SpeedVector, gameObject);
}
void FixedUpdate() {
Wind.Data.Speed = Mathf.Clamp(Wind.Data.Speed + Wind.WindRandom(), 15, Wind.MaximumWindSpeed);
Wind.Data.SpeedVector = BISH_MathHelper.DegreeToVector3(Wind.Data.DegAngle);
Wind.Data.RadAngle = (Wind.Data.RadAngle + Wind.WindRandom() / 80f) * Mathf.Rad2Deg;
}
}
Thanks for any help!
how i can pass a simple boolean variable between two different object?
I can try this but didn't work...
First script:
public class CollisionController : MonoBehaviour
{
public PlayerMovement movement;
public bool active = false;
private void OnCollisionEnter(Collision collision)
{
if(collision.collider.tag == "Obstacle")
{
active = true;
}
}
}
Second script (that read the boolean variable "active")
public class EmptyControllerColl : MonoBehaviour
{
public CollisionController controller;
public PlayerMovement movement;
public bool activeLocal = false;
private void Start()
{
GetComponentInChildren<CollisionController>();
}
void Update()
{
activeLocal = controller.active;
if(activeLocal == false)
{
Debug.Log("Nothing...");
}
if(activeLocal == true)
{
Debug.Log("Game over");
}
}
}
When the variable bool "Active" change its status, the variable "activeLocal" don't change status.. How can I resolve this problem?
Collision Controller is "connect" to Cube Object.
EmptyControllerColl is "connect" to emptyGameObject (parent of Cube).
This line
_ = GameObject.Find("cubo Variant").GetComponent<CollisionController>().active;
makes no sense. First of all there is no field or variable declared with the name _ so this shouldn't even compile at all. And secondly what do you need this for? Rather store the according reference once in the controller field and reuse it later.
Then for your usecase there is no need at all to store the value in a local variable ... this makes things only more complicated. Simply where you need it get the value from controller.active.
Also do not use == for tags. Rather check via CompareTag. The problem is that == silently fails if you have any typo or the tag doesn't exist at all. CompareTag rather throws an error that the given tag is not valid.
public class EmptyControllerColl : MonoBehaviour
{
// Best already drag this in via the Inspector in Unity
[SerializeField] private CollisionController controller;
public PlayerMovement movement;
// As fallback get it ONCE on runtime
private void Awake()
{
// since you say the cube is a child of this empty object you do not use
// Find at all but can simply use GetComponentInChildren
if(!controller) controller = GetComponentInChildren<CollisionController>(true);
}
void Update()
{
// No need to store this in a local field at all
if(!controller.active)
{
Debug.Log("Nothing...");
}
// use if else since both cases are exclusive and you don't even need to check the value twice
else
{
Debug.Log("Game over");
}
}
}
Event Driven - part A
In general you should avoid poll checks for a bool value in Update and rather come up with a more event driven solution! An example could look like:
public class CollisionController : MonoBehaviour
{
public PlayerMovement movement;
// Here everyone who wants can add listeners that get called as soon as
// we invoke this event. We will do it everytime the 'active' value is changed
public event Action<bool> OnActiveStateChanged;
// Backing field for 'active'
private bool _active;
// Property that reads and writes '_active'
// Everytime it is assigned it also invokes 'OnActiveStateChanged'
private bool active
{
get { return _active; }
set
{
_active = value;
OnActiveStateChanged?.Invoke(_active);
}
}
private void OnCollisionEnter(Collision collision)
{
if(collision.collider.CompareTag("Obstacle"))
{
active = true;
}
}
}
Now you would register a listener for this event like
public class EmptyControllerColl : MonoBehaviour
{
// Best already drag this in via the Inspector in Unity
[SerializeField] private CollisionController controller;
public PlayerMovement movement;
// As fallback get it ONCE on runtime
private void Awake()
{
// since you say the cube is a child of this empty object you do not use
// Find at all but can simply use GetComponentInChildren
if(!controller) controller = GetComponentInChildren<CollisionController>(true);
// register a callback. It is allowed an save to unregister first
// which makes sure this is only registered exactly once
controller.OnActiveStateChanged -= HandleControlerActiveStateChanged;
controller.OnActiveStateChanged += HandleControlerActiveStateChanged;
}
private void HandleGameOver()
{
Debug.Log("Game over");
}
private void HandleControlerActiveStateChanged(bool value)
{
if(!value)
{
Debug.Log("Nothing...");
}
else
{
Debug.Log("Game over");
}
}
private void OnDestroy()
{
// always clean up listeners
controller.OnActiveStateChanged -= HandleControlerActiveStateChanged;
}
}
This now is way more efficient since you don't all time run an Update method. Instead the HandleControlerActiveStateChanged is only called when the value of active is actually changed.
Event Driven - part B
And then actually in your case there is need to use a bool at all you could use a simple event Action instead and remove all the bools entirely:
public class CollisionController : MonoBehaviour
{
public PlayerMovement movement;
public event Action OnGameOver;
private void OnCollisionEnter(Collision collision)
{
if(collision.collider.CompareTag("Obstacle"))
{
OnGameOver?.Invoke();
}
}
}
Now you would register a listener for this event like
public class EmptyControllerColl : MonoBehaviour
{
[SerializeField] private CollisionController controller;
public PlayerMovement movement;
private void Awake()
{
if(!controller) controller = GetComponentInChildren<CollisionController>(true);
controller.OnGameOver -= HandleGameOver;
controller.OnGameOver += HandleGameOver;
}
private void HandleGameOver()
{
Debug.Log("Game over");
}
private void OnDestroy()
{
controller.OnGameOver -= HandleGameOver;
}
}
using UnityEngine;
public class CollisionController : MonoBehaviour
{
void Start()
{
// Calls the function ApplyDamage with a value of 5
// Every script attached to the game object
// that has an ApplyDamage function will be called.
gameObject.SendMessage("ApplyDamage", 5.0);
}
}
public class EmptyControllerColl : MonoBehaviour
{
public void ApplyDamage(float damage)
{
print(damage);
}
}
https://docs.unity3d.com/ScriptReference/GameObject.SendMessage.html
I'm thinking to make FadeIn/Out effect by C#.
// so, If there's a code like this :
float targetAlpha = 0.7f;
Color targetColor = new Color();
public void FadeIn() {
if(color.a < targetAlpha) { color.a += Time.deltaTime; }
sprite.color = targetColor;
}
1) I don't want to put FadeIn() in Update() because I don't use this FadeIn() function often.
2) I don't want to use Coroutine because StartCoroutine() makes garbage. I will set active on/off this object very often.
3) Animator... There's no way, right?
So I'm going to make 1 event, which will always work on Update(), and then I will put everything in that event. (add when OnEnable(), remove when OnDisable())
Is there a better solution?
If you don't want to put it in update because it won't be used often, maybe you can consider a state machine approach. I would only consider this if performance really is more important than readability. Because this approach will add a lot of additional code.
For simplicity the code below is more verbose than it has to be.
public interface IState
{
void Update();
}
public class FadeInState : IState
{
private readonly float targetAlpha;
private readonly Sprite sprite;
private readonly Action onComplete;
public FadeInState(Sprite sprite, float targetAlpha, Action onComplete)
{
this.targetAlpha = targetAlpha;
this.sprite = sprite;
this.onComplete = onComplete;
}
public void Update()
{
// Your fade-in code
if (sprite.color.a < targetAlpha)
{
Color tmp = sprite.color;
tmp.a += Time.deltaTime;
sprite.color = tmp;
}
else
{
this.onComplete.Invoke();
}
}
}
public class FadeOutState : IState
{
private readonly float targetAlpha;
private readonly Sprite sprite;
private readonly Action onComplete;
public FadeOutState(Sprite sprite, float targetAlpha, Action onComplete)
{
this.targetAlpha = targetAlpha;
this.sprite = sprite;
this.onComplete = onComplete;
}
public void Update()
{
// Your fade-out code
if (sprite.color.a > targetAlpha)
{
Color tmp = sprite.color;
tmp.a -= Time.deltaTime;
sprite.color = tmp;
}
else
{
this.onComplete.Invoke();
}
}
}
public class DoNothingState : IState
{
public void Update()
{
// Do nothing
}
}
public class YourClass : MonoBehaviour
{
private IState currentState;
void Awake()
{
this.currentState = new DoNothingState();
}
void Update()
{
this.currentState.Update();
}
public void FadeIn(Sprite sprite, float targetAlpha)
{
this.currentState = new FadeInState(sprite, targetAlpha,
() =>
{
this.currentState = new DoNothingState();
});
}
public void FadeOut(Sprite sprite, float targetAlpha)
{
this.currentState = new FadeOutState(sprite, targetAlpha,
() =>
{
this.currentState = new DoNothingState();
});
}
}
Initially your class is in the DoNothing state. So update will effectively do nothing.
If someone calls FadeIn, your FadeInState will do your fading logic as if it were in the MonoBehaviour.Update().
The state takes an Action in the constructor that is executed when it is finished. This way you can control what happens after the animation completes from within YourClass. In the example I just set the state to DoNothing but you can probably disable the gameObject.
If you go with this approach and you start using it for other things, you should just look into some better StateMachine implementations. Otherwise you will eventually end up with tons of state classes. This one is decent.
I have TCP client (Unity c#) and server (WinForms app c#). I need my server sending some JSON commands, like this:
{ ""ObjName"": ""Cube_2"", ""Method"":""MoveLeft"", ""Delay"":0}
This certain command says to find GameObject "Cube_2" and fire method "MoveLeft".
When i recieve this from server, i convert it into my AOSCommand class:
public class AOSCommand
{
public string ObjName;
public string Method;
public int delay;
}
And then i do the following (which i think is not the best solution, so here is a question):
private void ProcessCommand(AOSCommand command)
{
GameObject cube = GameObject.Find(command.ObjName);
MonoBehaviour script = cube.GetComponent(command.ObjName.Split(new string[] {"_"}, StringSplitOptions.None)[0]) as MonoBehaviour;
script.Invoke(command.Method, command.delay);
}
How can i fire some method from AOSCommand.Method string in a better way?
The script attached to Cube_2 (and Cube_1 and may be attached to unknown count of other objects):
using UnityEngine;
public class Cube : MonoBehaviour {
private GameObject thisObj;
private void Start()
{
thisObj = this.gameObject;
}
public void MoveLeft()
{
thisObj.transform.Translate(new Vector3(1,0,0));
}
public void MoveRight()
{
thisObj.transform.Translate(new Vector3(-1, 0, 0));
}
}
It depends what you consider wrong.
You should have a single script that takes care of the parsing of the incoming data, this would remove the need to search for a component, it would always be the same.
Then you can have a dictionary of to replace the invoke call.
So your snippet turns into:
private void ProcessCommand(AOSCommand command)
{
GameObject cube = GameObject.Find(command.ObjName);
AOSDispatch dispatch = cube.GetComponent<AOSDispatch>()
if(dispatch == null){ return; } // or debug or exception
dispatch.Call(command);
}
this is on the main receiver. Then comes the script on the cubes:
public class AOSDispatch : MonoBehaviour
{
Dictionary<string, Action> dict;
void Start()
{
dict.Add("MoveLeft", MoveLeft);
dict.Add("MoveRight", MoveRight);
}
public void Call(AOSCommand command)
{
if(dict.Contains(command.Method) == false){ return; } //Or debug
// use the delay as well as you wish
dict[command.Method]();
}
private void MoveLeft(){}
private void MoveRight(){}
}
This is not necessarily better, just my two cents on it.
EDIT: there was comment mentioning the json could contain the script type to know what script to use. I would not go this way. AOSDispatch will take care of the dispatching of the message.
Message says MoveLeft, AOSDispatch can either treat the info or forward to a movement controller:
public class AOSDispatch : MonoBehaviour
{
[SerializeField] private MoveController moveCtrl = null;
Dictionary<string, Action> dict;
void Start()
{
dict.Add("MoveLeft", this.moveCtrl.MoveLeft);
dict.Add("MoveRight", this.moveCtrl.MoveRight);
}
public void Call(AOSCommand command)
{
if(dict.Contains(command.Method) == false){ return; } //Or debug
// use the delay as well as you wish
dict[command.Method]();
}
}
public class MoveController: MonoBehaviour
{
private void MoveLeft(){}
private void MoveRight(){}
}
there you go, message is forward and cleanly, the AOSDispatch does only the job it is meant to do, dispatch the AOS.
SECONDARY EDIT:
On second thought, here is an improved version.
Create a DispatchManager game object and add the following script:
public class AOSDispatch:MonoBehaviour
{
private IDictionary<string, AOSController> dict;
void Awake(){
this.dict = new Dictionary<string, AOSController>();
AOSController.RaiseCreation += ProcessCreation;
AOSController.RaiseDestruction += ProcessDestruction;
}
void OnDestroy()
{
AOSController.RaiseCreation -= ProcessCreation;
AOSController.RaiseDestruction -= ProcessDestruction;
}
private void ProcessCreation(AOSController controller){
this.dict.Add(controller.name, controller);
}
private void ProcessDestruction(AOSController controller){
AOSController temp= null;
if(this.dict.TryGetValue(controller.name, out temp) == true){
this.dict.Remove(name);
}
}
private void ProcessCommand(AOSCommand command)
{
AOSController controller = null;
if(this.dict.TryGetValue(command.ObjName, out controller) == true){
controller.Call(command);
return;
}
}
}
and then on the objects you have the AOSController that forwards the info as before (just renaming):
public class AOSController: MonoBehaviour
{
public static event Action<AOSController> RaiseCreation;
public static event Action<AOSController> RaiseDestruction;
[SerializeField] private MoveController moveCtrl = null;
Dictionary<string, Action> dict;
void Start()
{
if(RaiseCreation != null) { RaiseCreation(this); }
dict.Add("MoveLeft", this.moveCtrl.MoveLeft);
dict.Add("MoveRight", this.moveCtrl.MoveRight);
}
void OnDestroy()
{
if(RaiseDestruction != null) { RaiseDestruction(this); }
}
public void Call(AOSCommand command)
{
if(dict.Contains(command.Method) == false){ return; } //Or debug
// use the delay as well as you wish
dict[command.Method]();
}
}
public class MoveController: MonoBehaviour
{
private void MoveLeft(){}
private void MoveRight(){}
}
On Awake, the Dispatch registers to the static event from the AOSController. In the AOSController.Start, the object triggers the event and passes itself to the AOSDispatch. That one adds it to the dictionary. On destruction, the AOSDispatch gets the event and removes the AOSController.
Now you have a collection that at any given time contains all the AOSController in the scene.
As a result, you don't need to perform a GameObject.Find since you can get the object from the dictionary (real fast process).
There is issue that i facing with two objects and one button. One is cube second is ground when we click on button cube is collide with ground destroy and instantiate again. On Cube collision score is decrement.Also in hierarchy there is Empty game object which name is controller which has method of text score.Score is working fine but i want that when score is 0 then button click does not work and cube is not instantiate.
Cube :
Ground :
Controller :
CubeScript:
public class Cube : MonoBehaviour {
Rigidbody2D body;
void Start () {
body = GetComponent<Rigidbody2D>();
body.isKinematic = true;
}
}
Ground Script:
public class Ground : MonoBehaviour {
private Button button;
private BoxCollider2D collide;
public GameObject object1Clone;
void Start () {
collide = GetComponent<BoxCollider2D>();
collide.isTrigger = true;
button = GameObject.FindGameObjectWithTag ("Button").GetComponent<Button> ();
button.onClick.AddListener (() => Magnetic ());
}
void OnTriggerEnter2D(Collider2D target) {
Destroy (target.gameObject);
Instantiate (object1Clone, new Vector3 (0f, 4.12f, 0f), Quaternion.identity);
}
public void Magnetic(){
GameObject.FindGameObjectWithTag ("Player").GetComponent<Rigidbody2D> ().isKinematic = false;
}
}
ScoreScript:
public class ScoreScript : MonoBehaviour {
public static int Score=1;
void OnTriggerEnter2D(Collider2D target) {
if (Score <=0) {
} else {
Score--;
Controller.instance.SetScore(Score);
}
}
}
Controller:
public class Controller : MonoBehaviour {
public static Controller instance;
public Text scoreText;
void Start () {
scoreText.text = ""+1;
if(instance==null){
instance=this;
}
}
public void SetScore(int score){
scoreText.text =""+score;
}
}
First change the listener registration to this:
button.onClick.AddListener (Magnetic);
this will make it easier to remove the listener.
I will show you two ways of doing it, an easy one and a proper one a bit harder to grasp. So if you don't quite get it, use the first and learn about the second.
Every time you decrease the score, check for it and call for the appropriate action:
public class ScoreScript : MonoBehaviour {
public static int Score=1;
void OnTriggerEnter2D(Collider2D target)
{
Score--;
Controller.instance.SetScore(Score);
if(Score <= 0){
GameObject.Find("ground").GetComponent<Ground>().ClearButtonListener();
}
}
}
And in the Ground component:
public void ClearButtonListener()
{
button.onClick.RemoveListener (Magnetic);
}
Now the second more appropriate way would be to use event and listener
public class ScoreScript : MonoBehaviour, IScoreHandler {
public static int Score=1;
public event Action OnScoreZero = () => {};
void OnTriggerEnter2D(Collider2D target)
{
Score--;
Controller.instance.SetScore(Score);
if(Score <= 0){
OnScoreZero();
}
}
}
public interface IScoreHandler{ event Action OnScoreZero; }
And your listeners listens.
public class Ground : MonoBehaviour {
private Button button;
private BoxCollider2D collide;
public GameObject object1Clone;
private IScoreHandler scoreHandler = null;
void Start () {
scoreHandler = GameObject.Find("Score").GetComponent<IScoreHandler>();
if(scoreHandler != null){
scoreHandler.OnScoreZero += ClearButtonListener;
}
collide = GetComponent<BoxCollider2D>();
collide.isTrigger = true;
button = GameObject.FindGameObjectWithTag ("Button").GetComponent<Button> ();
button.onClick.AddListener (Magnetic);
}
void OnDestroy(){
if(scoreHandler != null){
scoreHandler.OnScoreZero -= ClearButtonListener;
}
}
}
Thanks to interface and event, your class is no more relying on another class but on an interface which makes it more flexible and scalable.
You need to set the field interactable of the UnityEngine.UI.Button object to false, see http://docs.unity3d.com/ScriptReference/UI.Button.html, i.e. use
void OnTriggerEnter2D(Collider2D target) {
if (Score <=0) {
/* disable the button */
GameObject.FindGameObjectWithTag ("Button").GetComponent<Button>().interactable = false;
}
in your ScoreScript.cs.