Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Question Trying to create an event in response to reaching a certain amount of coins

Discussion in 'Scripting' started by ElijahHenson, Apr 17, 2023.

  1. ElijahHenson

    ElijahHenson

    Joined:
    Feb 1, 2023
    Posts:
    3
    Hey all,
    I have been creating a roll a ball game where as the player (a ball) you start in an area with 4 portals, all of which take you to a different path with obstacles, which all have a collectible coin somewhere along the path. I have an int variable named "coins" stored in a C# script which is attached to the player object, or the ball, which updates by 1 every time the player touches a coin. I have then been aiming to have a block move once the coins variable reaches 4, acting as a door, which reveals a secret level to finish the game. The issue I have been running into is trying to access this coins variable in the script on the door, where I have been trying to use GetComponent to get the variable from the player script, but for some reason it isnt working. Could anyone help me out as to how I could access the coins variable from the player script in the script I have attached to the door?
    Sorry if this is confusing as i'm new to unity scripting and the program as a whole.
     
  2. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    It's not very hard. You just need certain things to be set up properly in order to hook up any event to your system.

    1) Define your handler. That's how the method that is supposed to handle such an event should look.
    2) Declare an event.
    3) Raise the events in their place of origin.
    4) Designate a receiving object, and let it subscribe to the event.
    5) Finally process the event through a local handler.

    Here's an example. Imagine if you had 2 objects: Signal and Car. Signal would switch from red to green, and Car would want to react to this. So Car would like to subscribe to Signal's event of SignalChange. SignalChange has the following signature
    void event(SignalMode mode)
    .

    Code (csharp):
    1. public enum SignalMode {
    2.   Red = 0,
    3.   Green
    4. }
    5.  
    6. public class Signal {
    7.  
    8.   SignalMode _mode;
    9.   public SignalMode Mode {
    10.     get => _mode;
    11.     set {
    12.       _mode = value;
    13.       SignalChange?.Invoke(_mode); // raise event
    14.     }
    15.   }
    16.  
    17.   public delegate void SignalHandler(SignalMode mode); // define handler
    18.   public event SignalHandler SignalChange; // declare event
    19.  
    20. }
    21.  
    22. public class Car {
    23.  
    24.   Signal _signal;
    25.  
    26.   public Car(Signal signal) {
    27.     _signal.SignalChange += SignalReaction; // subscribe
    28.   }
    29.  
    30.   ~Car() => _signal.SignalChange -= SignalReaction; // unsubscribe
    31.  
    32.   public void SignalReaction(SignalMode mode) { // event handling
    33.     if(mode == SignalMode.Green) {
    34.       Debug.Log("I should accelerate");
    35.     } else {
    36.       Debug.Log("I should brake");
    37.     }
    38.   }
    39.  
    40. }
    Code (csharp):
    1. var signal = new Signal();
    2. var car = new Car(signal);
    3. signal.Mode = SignalMode.Green; // prints out I should accelerate
    4. signal.Mode = SignalMode.Red; // prints out I should brake
    Now this is the most broad application of the concept, implementation-wise. And obviously very contrived.

    Try learning more about events in C# because it's a deep topic and there are some shortcuts as well. For example, you are not required to define a delegate, you can also use the provided EventHandler template.

    In a nutshell, events are multicast delegates, to which you can hook a handler by using operator += . Here I also make sure to unsubscribe if the Car is destroyed, but perhaps that's an overkill.

    edit:
    Btw, SignalReaction can be made private, I don't know why I made it public.
     
    Last edited: Apr 17, 2023
    Olipool likes this.
  3. Olipool

    Olipool

    Joined:
    Feb 8, 2015
    Posts:
    316
    Events are a great tool and so useful for many more cases so I recommend looking into them and try the code from orionsyndrome.
    However, you were stating that you are just beginning. So it might be a bit overwhelming and you might also be interested in why YOUR solution did not work. So if you can post some of your code we may find a solution. GetComponent and accessing the coins variable from the player is an idea that should work, yo you are not completely off.
    Btw, do you want the door to open when the player approaches with 4 coins or when they pick up the 4th coin?
     
  4. ElijahHenson

    ElijahHenson

    Joined:
    Feb 1, 2023
    Posts:
    3
    My goal was to have the door open once the player picks up the 4th coin.

    Heres the code Ive been using for the door

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class door : MonoBehaviour
    6. {
    7.     public int rqm;
    8.     // Start is called before the first frame update
    9.     void Start()
    10.     {
    11.  
    12.     }
    13.  
    14.     // Update is called once per frame
    15.     void Update()
    16.     {
    17.         rqm = otherGameObject.GetComponent<coins>();
    18.  
    19.         if(rqm==4)
    20.         {
    21.             transform.position + new Vector3(5, 5, 10);
    22.         } else
    23.         {
    24.         }
    25.  
    26.     }
    27. }
    28.  
     

    Attached Files:

    • door.cs
      File size:
      371 bytes
      Views:
      107
  5. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Well, there are ways and ways to do this, but it's maybe useful to think in terms of having a dedicated "accomplishment" repository.

    For example, if you have a game level where there is a door that needs to be unlocked a-la Metroidvania, it makes sense to monitor this condition from an object that is on the same level of abstraction as the game level itself. Lets call this LevelMonitor, as an example.

    So it exists whenever this particular game level exist, and just sits there, as a read/write-friendly collector of all kinds of level-wide accomplishments and conditions.

    You can make an empty GameObject in your scene and attach this script to it
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class LevelMonitor : MonoBehaviour {
    4.  
    5.   public bool doorLocked = true;
    6.  
    7. }
    But now you need a way to be able to access this from your actual door object, so it makes sense to introduce a pseudo-singleton pattern. So let's do this
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class LevelMonitor : MonoBehaviour {
    4.  
    5.   static LevelMonitor _instance;
    6.   static public LevelMonitor Instance => _instance;
    7.  
    8.   public bool doorLocked = true;
    9.  
    10.   void Awake() {
    11.     // this just means that you mustn't have more than exactly one
    12.     // of these scripts, and it will notify you if you make this error
    13.     if(_instance is not null)
    14.       Debug.LogError($"Too many instances of {nameof(LevelMonitor)}");
    15.  
    16.     _instance = this;
    17.   }
    18.  
    19.   void OnDestroy() => _instance = null;
    20.  
    21. }

    You can access this from anywhere, i.e.
    Code (csharp):
    1. Debug.Log(LevelMonitor.Instance.doorLocked);
    Now you have a place to put your logic regarding the state of coins.
    But first, let's modify that bool into a read-only property.
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class LevelMonitor : MonoBehaviour {
    4.  
    5.   static LevelMonitor _instance;
    6.   static public LevelMonitor Instance => _instance;
    7.  
    8.   // doing this will prevent it from being inspectable,
    9.   // so you might still want to use the previous version
    10.   public bool DoorLocked { get; private set; }
    11.  
    12.   void Awake() {
    13.     if(_instance is not null)
    14.       Debug.LogError($"Too many instances of {nameof(LevelMonitor)}");
    15.  
    16.     _instance = this;
    17.     Initialize();
    18.   }
    19.  
    20.   void OnDestroy() => _instance = null;
    21.  
    22.   public void Initialize() {
    23.     DoorLocked = true;
    24.   }
    25.  
    26.   public void EvaluateState(int collectedCoins) {
    27.     DoorLocked = collectedCoins >= 4;
    28.   }
    29.  
    30. }
    Make sure you call this method EvaluateState whenever you collect a coin. Maybe you keep the actual quantity someplace else, and this is the value you pass in. (You can also call Initialize() to reset the internal state back to default.)
    Code (csharp):
    1. // somewhere
    2. when a coin is collected:
    3. LevelMonitor.Instance.EvaluateState(collectedCoins: myCoins);

    Finally, in your door script
    Code (csharp):
    1. when player interacts with the door:
    2. if(!LevelMonitor.Instance.DoorLocked) Open();

    This is a solution with no events involved whatsoever, but includes a paramount stateful object that governs the logic of the level. There is no limit to how much you can extend this class in terms of all kinds of additional states, switches, trackers and counters. But when extending it always make sure it stays simple so that you never miss to call the internal evaluator, and that the evaluated state(s) is correct at all times.
     
    Last edited: Apr 18, 2023
  6. Olipool

    Olipool

    Joined:
    Feb 8, 2015
    Posts:
    316
    This won't work for two reasons:
    - You need to define the otherGameObject, maybe make it public and drag the Player gameobject into that.
    - if you use GetComponent it will give you a component or script or class but not a single variable. So you need to get the script on the player that has this coin variable and then get the coin variable after that, for example:
    playerGameObject.GetComponent<CoinScript>().coins;

    That is just for your understanding of what went wrong, try to use the solution above if you are ready :)