Hello everyone!
I have a problem I can’t solve on my own.
When an apple reaches a basket I need to update a score and then inform ui-text that it needs to update it’s text.
This is a part of GameMaster’s code:
[System.Serializable]
public class ScoreUpdateEvent: UnityEvent<int, int> {}
public ScoreUpdateEvent ScoreChanged = new ScoreUpdateEvent ();
public void OnAppleCatch() {
score += 10;
ScoreChanged.Invoke(score, highscore);
}
Here, OnAppleCatch is a callback for another event.
In GameMaster’s Inspector I link ScoreUpdateEvent callback to desired OnScoreUpdate method.
When game runs and I watch debugger I see that code in OnAppleCatch method is reached, but in OnScoreUpdate is not.
Then I wanted to experiment and placed ScoreChanged.Invoke(score, highscore); into Start method and it worked!
This seems to me as a bug in Unity itself, but maybe I’m doing something wrong?
Thank you in advance!
I’m not sure how you add listeners through the inspector, but I did your example through scripts and it works 100%
Basket Script
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class Basket : MonoBehaviour
{
UnityEvent AppleCatchEvent;
private void Awake()
{
AppleCatchEvent = new UnityEvent();
}
public void AddListener(UnityAction method)
{
AppleCatchEvent.AddListener(method);
}
private void OnCollisionEnter2D(Collision2D collision)
{
AppleCatchEvent.Invoke();
}
}
GameController Script
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class GameController : MonoBehaviour {
public Basket basket;
int score;
int highscore;
public class ScoreChangedEvent : UnityEvent<int, int> { };
ScoreChangedEvent scoreChanged;
private void Awake()
{
score = 0;
highscore = 1000;
scoreChanged = new ScoreChangedEvent();
}
private void Start()
{
basket.AddListener(UpdateScore);
}
public void AddListener(UnityAction<int,int> method)
{
scoreChanged.AddListener(method);
}
private void UpdateScore()
{
score += 10;
scoreChanged.Invoke(score, highscore);
}
}
ScoreDisplay Script
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Text;
public class ScoreDisplay : MonoBehaviour {
Text myText;
private void Awake()
{
myText = GetComponent<Text>();
}
private void Start()
{
GameObject controllerGO = GameObject.FindWithTag("GameController");
GameController controller = controllerGO.GetComponent<GameController>();
controller.AddListener(UpdateScore);
}
public void UpdateScore(int score, int highscore)
{
StringBuilder sb = new StringBuilder();
sb.Append("Score: ");
sb.Append(score);
sb.Append("\n");
sb.Append("HighScore: ");
sb.Append(highscore);
myText.text = sb.ToString();
}
}
To Create this project I made a new 2D project:
Created an Emtpy GameObject name Apple
Added A RigidBody2D to it
Added A SpriteRender and put a sprite in it
Added A BoxCollider2D
Moved it to the top of the screen
Created an Emtpy GameObject named Basket
Added a SpriteRender and put a sprite in it
Added a BoxCollider2D
Attached the Basket Script to it
Moved it to the bottom of the screen
Created an Empty GameObject named GameController
Attached the GameController script to it
Dragged the Basket Object to the appropriate spot in the Inspector
Created a Canvas and a UI Text Object named ScoreDisplay
Attached the ScoreDisplay script to ScoreDisplay
Moved it to the upper left corner of the Canvas
When I press play the apple falls, hits the basket and my score goes from 0 to 10
The basket has a Apple Catch Event and when it catches an apple it Invokes it.
The GameController is subscribed to this event and when it happens it updates the score.
It Also has a ScoreUpdate Event that it invokes when this happens.
The ScoreDisplay is subscribed to the ScoreUpdate Event and updates the score
Takatok, thank you, you modelled my situation precisely!
You can add listeners in Inspector if you declare UnityEvent field public (and add attribute [System.Serializable] in case of subclassing a generic UnityEvent).
I have a feeling that there is a bug hidden in this Inspector adding. When I get home I’ll check if my project works with suggested script adding.
Got… Here is your bug, its not a problem with the inspector.
When you declare something public behind the scenes the inspector is creating that object (using the new keyword). So it has something to work with so for example this would let you use the Event:
[System.Serializable]
public class ScoreUpdateEvent: UnityEvent<int, int> {}
public ScoreUpdateEvent ScoreChanged ;
public void OnAppleCatch() {
score += 10;
ScoreChanged.Invoke(score, highscore);
}
(Note i took out the new part of your code) This is because Unity creates it for you since its public. Now whats happening with your code is Unity is Creating the Object, you adding a listener but then once you actually hit play your creating ANOTHER UnityEvent with your new keyword. This pretty much erases everything you did in the Inspector. If you keep your code as is and hit play, watch over where you added the Callbacks… they will suddenly go blank.
Whats effectively happening is there are 2 instances of the UnityEvent ScoreChanged. The one the inspector creates and you add all the callbacks too. Then another one you create with the new keyword once the script starts running. Which is the one your using in your script and it has no callbacks
So TL:smile:R just take out the new keyword like the example I posted above.
Ok, so I came home and digged deep into that apple project.
I understand what you are saying about ‘new’ part, but my AppleCatcher script also had that part and invoked events just fine. So there’s question - what happens earlier, Unity creating something via Inspector or ‘= new’ part of my code. Still I don’t like this ambiguity so I will refrain from using ‘= new’ in declaration section of class. Anyway, taking out ‘new’ didn’t help.
…and then your word ANOTHER prompted the idea! I checked prefab.
I think I lack understanding of Prefabs, I thought if I added prefab as a listener in the Inspector, it would also add every created object of this prefab, and that was plain wrong.
So in the end my situation was as follows - AppleCatcher was invoking event into a prefab, but ScoreUpdater was listening to an object of that prefab, so in debug window I was observing behaviour of a prefab with ScoreUpdated event with no listeners. So I played with design a bit and it happened I didn’t need that prefab at all. After that, it worked!