0
\$\begingroup\$

How is it possible that a create an item counter in unity?

My current Scripts -

Counter.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Counter : MonoBehaviour
{
    public int ScoreAmount;
    private Text ScoreText;
    // Start is called before the first frame update
    void Start()
    {
        ScoreText = GetComponent<Text>();
        ScoreAmount = 0;
    }

    // Update is called once per frame
    void Update()
    {
        ScoreText.text = "" + ScoreAmount;
    }

Where I have a text UI called ScoreText

ItemPickup.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ItemPickup : MonoBehaviour
{
    void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Fruit")) 
        {
            Debug.Log("Picking up fruit...");
            Counter.ScoreAmount += 1;
        }  
    }
}

But I get this error

"Assets/ItemPickup.cs(12,13): error CS0120: An object reference is required for the non-static field, method, or property 'Counter.ScoreAmount'"

How do I fix it please?

\$\endgroup\$
1
  • \$\begingroup\$ What solution would be ideal here would depends on which of these components is on which object. Looking at how the ItemPickup component works, it seems to be on the object which does the picking. But what about the Counter component? Is it on the same gameObject or is it on some UI element or invisible manager object? \$\endgroup\$
    – Philipp
    Feb 7, 2021 at 17:07

1 Answer 1

2
\$\begingroup\$

The problem is that Unity allows you to put your Counter script on as many gameObjects as you want, and each one would have an own ScoreAmount. So Counter.ScoreAmount is ambiguous. Unity doesn't know which one you mean.

So how could you fix that?

Solution A: Make ScoreAmount static.

When you declare a variable as static, then all instances of that script share the same value. That allows you to refer to that variable by the class name instead of any particular instance. So when you change public int ScoreAmount; to public static int ScoreAmount;, then you should get the behavior you want.

But while this might solve the problem for now, it might cause some problems later. For example when you want for some reason create a second object with an own count, like a 2nd player or an AI actor which can also collect things and count them.

Solution B: Make the ItemPickup get the component on the same object.

However, looking at your code, it seems like the ItemPickup component is on the gameObject which actually does the picking (and not on the item which can be picked up, which is why I would have done it, but I digress...). It is not clear from your question where the Counter component is housed, but if it is on the same gameObject, then you can use GetComponent<Counter>() to get a reference to it.

void OnTriggerEnter2D(Collider2D other)
{
    if (other.CompareTag("Fruit")) 
    {
        Debug.Log("Picking up fruit...");
        GetComponent<Counter>().ScoreAmount += 1;
    }  
}

Solution C: Give the FruitPickup script a reference to the Count script.

This solution allows multiple Count script, but does not requires that they are on the gameObject which does the collecting. So you can put it on a manager object or a UI element.

Give the FruitPickup script a variable public Count count and assign the object with the Count script using the inspector.

Solution D: Put the ItemPickup script on the items and have it detect which object collected it.

This is how I would do this, but it requires you to approach this a bit differently.

  1. Put the Counter object on the player
  2. Make the collider of the player not a trigger
  3. Put the FruitPickup component on object which can be picked up
  4. Make the colliders of the fruits triggers
  5. Write the FruitPickup class like this:
    void OnTriggerEnter2D(Collider2D other)
    {
        Counter counter = other.GetComponent<Counter>()
        if (counter != null) // the object is able to count fruit
        {
            counter.ScoreAmount += 1; // increase score of the object which collected this
            Destroy(gameObject); // self-destruct
        }  
    }

But now the Counter loses its connection to the Text element it used to control. The reason is that it used the method B to get a reference to its own Text component, which now won't work anymore. How do we fix that? By using the solution C and tell it which text object to use to visualize its state. Make the ScoreText variable public and remove its initialization in Start:

public class Counter : MonoBehaviour
{
    public int scoreAmount;
    public Text scoreText;
    void Start()
    {
        scoreAmount = 0;
    }

    void Update()
    {
        if (scoreText != null) { 
             scoreText.text = scoreAmount.ToString();
        }
    }
}

Then go to the Inspector and drag the Text object which you want to visualize the score on the "Score Text" inspector field of the Counter component.

Although personally I recommend to separate mechanics from UI visualization, so I would probably use a 3rd script for UI elements for visualizing the state of an arbitrary Counter on its own Text. But that's a different topic.

Why do I recommend option D?

  • an object should contain the components which dictate its own behavior, not that of other things. Increasing the player's score and then disappearing is something the fruits do, not something the player-character does. So this behavior should be defined in a component of the Fruit gameObjects. Also, this allows you to do things like making the amount by which a fruit increases the player's score a public variable of that script. So you can create prefabs for different fruits which increase the player's score by different amounts.

  • I would avoid putting a trigger on the player. Triggers should be on the objects in the world which other objects interact with. Not on the objects which do the interacting.

  • detecting what the other object is in a collision by seeing what components it has allows you to create very modular and reusable code. The fruit doesn't care if whatever picks it up is a "Player". It only cares that it has the components which require it to be able to collect it. So you can have multiple players which collect fruit and even NPC characters which collect fruit, and you don't need to change anything about the fruits to do that.

\$\endgroup\$
9
  • \$\begingroup\$ How do I display the score on the screen \$\endgroup\$
    – elliot727
    Feb 8, 2021 at 9:26
  • \$\begingroup\$ @elliot727 So you mean the Counter script you posted which didn't had an error did not actually work? What exactly is the problem with it? Do you already have a canvas with a Text object in the scene? Which of the solutions I outlined above did you choose to solve the error you had in your script? \$\endgroup\$
    – Philipp
    Feb 8, 2021 at 9:55
  • \$\begingroup\$ No error comes up but no score gets added it just sits at 0 \$\endgroup\$
    – elliot727
    Feb 8, 2021 at 10:54
  • \$\begingroup\$ @elliot727 Answer updated. Also, you did not tell us about the NullReferenceException which shows up in the log while the game is running. \$\endgroup\$
    – Philipp
    Feb 8, 2021 at 12:25
  • \$\begingroup\$ @elliot727 And by the way: If you had replied to the comment I posted on the question, I could have added that information yesterday. \$\endgroup\$
    – Philipp
    Feb 8, 2021 at 12:36

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .