0

Context

Hello, its me again. I have been playing around with "Object Pool" and it is fascinating the amount of CPU performance can save up. However, I have encountered a new "feature" where it scores once for the object being called and going through the pipe.

In my object pool, I have a pipe GameObject where is being Instantiated and set its method Active(false) until it is spawned. Once spawned, my prefabs will be filled accordingly to the Object Pool's size.

Problem

Once it spawns, it does what it should do, both scoring and the same mechanic as "Flappy Bird". However, once the object gets Active again, it doesn't seem to score anymore because it was passed by a player. What I have done is to have a flag that checks if the player (bird) has passed the pipe or not. However, when I pass through it, it will update it as if it was 6 times (one point per frame). You may ask "have you done another flag for the pipe itself?" then the answer is yes. I tried that way also, but it will only keep the score once and not increase further than 5.

I ran out of ideas with the object pool approach. It seems to work fine if it is WITHOUT object pooling, but the flaw here is that it costs me CPU performance.

enter image description here

It either increases it by just 1, or it increases it by 6 times (because for each frame the object is in the ray, it counts another point).

Attempts

I have browsed on the Unity Learning center to find out how to do the object pooling, and it wasn't too bad as I thought. Later, I found this issue and I thought it was a problem with my logic (which it can be). I have spent several hours already (first mistake) to think it is something easy to fix, but apparently it wasn't due the time I have spent to just figure out why it is not working . I have been fiddling around my RayCastDetection, SpawnManager, ObjectPooling, and PlayerControl logic that interacts accordingly to the way I want, but nada.

Code

ObjectPooling.cs

public static ObjectPooling sharedInstance;
public List<GameObject> pooledObjects;
public GameObject objectToPool;
private PlayerControl playerControllerScript;
public int amountToPool;

void Awake()
{
    sharedInstance = this;
}
void Start()
{
    playerControllerScript = GameObject.Find("Player").GetComponent<PlayerControl>();
    pooledObjects = new List<GameObject>();
    GameObject tmp;

    for (int i = 0; i < amountToPool; i++) //Add objects to the pool and turns them invisible (false)
    {
        tmp = Instantiate(objectToPool);
        tmp.SetActive(false);
        playerControllerScript.passedBeam = false;
        pooledObjects.Add(tmp);
    }
}
public GameObject GetPooledObject()
{
    for (int i = 0; i < amountToPool; i++)
    {
        if (!pooledObjects[i].activeInHierarchy)
            return pooledObjects[i];
    }
    return null;
}

RayCastDetection.cs

public class RayCastDetection : MonoBehaviour
{
    public UIManager UIManagerScript;
    public PlayerControl playerControlScript;
    private RaycastHit hit;
    public bool passed;

    void Start()
    {
        UIManagerScript = GameObject.Find("UI_Manager").GetComponent<UIManager>(); //Used for scoring
        playerControlScript = GameObject.Find("Player").GetComponent<PlayerControl>(); //used for player passing through the pipe
        passed = false;
        playerControlScript.passedBeam = false;
    }

    void Update()
{
    Vector3 beamDown = transform.TransformDirection(Vector3.down);
    Ray ray = new Ray(transform.position, beamDown);

    if (!passed)
    {
        if (Physics.Raycast(ray, out hit))
        {
            if (hit.collider.tag == "Player" && !playerControlScript.passedBeam)
            {
                playerControlScript.passedBeam = !playerControlScript.passedBeam;
                UIManagerScript.score++;
            }
            Debug.DrawRay(transform.position, hit.point - transform.position);
        }
    }
    else
        playerControlScript.passedBeam = false;
}
}

SpawnManager.cs

public class SpawnManager : MonoBehaviour
{
    public GameObject[] obstaclesPrefab;
    private PlayerControl playerControllerScript;
    private float startDelay = 1.69f;
    private float repeatRate = 1.1f;

    void Start()
    {
        playerControllerScript = GameObject.Find("Player").GetComponent<PlayerControl>();
        InvokeRepeating("SpawnObstacle", startDelay, repeatRate);
    }

    void Update()
    {
        
    }
    void SpawnObstacle()
    {
        // int obstaclesIndex = Random.Range(0, obstaclesPrefab.Length); //This is used only if I don't want to deal with object pooling, but the whole point is to use it. This is just a reference if I want to go back
        
        if (playerControllerScript.gameOver == false)
        {
            float randomY = Random.Range(-2f, 2f);
            Vector3 randomHeight = new Vector3(35, randomY, -7);
            GameObject pipe = ObjectPooling.sharedInstance.GetPooledObject();
            if (pipe != null)
            {
                pipe.transform.position = randomHeight;
                pipe.SetActive(true);
                //My guess is that I want to instantiate the object pipe's beam to false here 
            }
        }
            // Instantiate(obstaclesPrefab[obstaclesIndex], randomHeight, obstaclesPrefab[obstaclesIndex].transform.rotation); //This is used only if I don't want to deal with object pooling, but the whole point is to use it. This is just a reference if I want to go back
    }
}

Feel free to leave some suggestions in what I have missed out or any questions in regards to fill in. Thank you for your time!

Zeid Tisnes
  • 396
  • 5
  • 22
  • The `RaycastDetection` sets `passed` to `true`, and never sets it too false after that. Why not move the `bool passed` to the obstacle, so each obstacle has its own `passed` flag? Set the `passed` in the obstacle to true if you have ray casted the obstacle to prevent double, triple, ... scoring. – rotgers Nov 01 '22 at 14:06
  • Thanks for the quick response. I edited the code a bit in the `RayCastDetection` accordingly. The thing is when I turn it to `false`, I just get scored multiple times from the same pipe, but it fixes the issue. Are you suggesting to have the `passed` in the `SpawnManager.cs` instead? If so, if I call my `RayCastDetection.cs` to my `SpawnManager.cs`, it will complain to me for not being an GameObject, which is why I'm stuck. – Zeid Tisnes Nov 01 '22 at 14:19
  • Is the `RayCastDetection` attached to the `ObstacleObject`? Ideally each obstacle has this `bool passed`. Then you get `if(!passed) { score++; passed = true; }` to prevent double scoring. If you 'reset' the obstacle, you have to set `passed` to `false` again. – rotgers Nov 01 '22 at 14:24
  • It does have it, but it seems that it is not triggering it after and before the pipe is being called. However, when they become `SetActive`, their `passed` value doesn't change at all. What I feel I have to do is whenever I pass the pipe, I have to set that pipe that it went through until it is being called again to reset the `passed` to `false`. I want to add, there is this method from MonoBehavior called `OnEnable` or `OnDisable`. I'm reading into it and maybe see if I can apply to it. – Zeid Tisnes Nov 01 '22 at 14:43
  • I somewhat fixed it, but my `passedBeam` seems to have a delay after going to the 2nd pipe. I set my `passedBeam` to `false` in my `Start()` method of `RayCastDetection.cs`. Plus, I adjusted a little bit of the logic. I will update my question a bit more for current progress. – Zeid Tisnes Nov 01 '22 at 15:41
  • And if you set `passed` to `false` in the `SpawnObstacle` method, next to where you assign a random height? – rotgers Nov 01 '22 at 15:43
  • I tried before, but I will get a reference error message: `NullReferenceException: Object reference not set to an instance of an object SpawnManager.SpawnObstacle () (at Assets/Scripts/SpawnManager.cs:37)` after adding my `RayCastDetection` script in there and try to do it by reference – Zeid Tisnes Nov 01 '22 at 15:53
  • I made a VR version of this once, here is the `CubeController` which is an obstacle, and the `ScoreController` which counts the score. Hope it helps. (https://github.com/darkeclipz/clappy-bird-vr/blob/master/Assets/Scripts/ScoreController.cs, https://github.com/darkeclipz/clappy-bird-vr/blob/master/Assets/Scripts/CubeController.cs) – rotgers Nov 01 '22 at 16:14
  • Ok so from what I’m getting is a flag for incrementing score in my score file. I will give that a try once I’m done eating and feel less mad at this silly problem I have lol – Zeid Tisnes Nov 01 '22 at 16:27
  • 1
    Yes, and there is a `success` flag in the obstacle, so it doesn't call the flag in the score object repeatedly. – rotgers Nov 01 '22 at 16:28
  • Truly a savior, it worked! I will share and updated version tomorrow! – Zeid Tisnes Nov 01 '22 at 19:45
  • Awesome, nice to hear! – rotgers Nov 01 '22 at 20:02
  • instead of `Start` you could try and use `OnEnable` so it is called again after the object is pulled form the pool again ... in particular m guess would be that `passed = false;` needs to be reset – derHugo Nov 02 '22 at 11:57
  • I was thinking on `OnEnable` for a bit, but I did not bother further. Maybe in the future project, will keep it in mind – Zeid Tisnes Nov 02 '22 at 12:12

1 Answers1

0

I wanted to share some good news. My problems has been fixed!

Before I step into the solution, big thanks to rotgers and sharing me the approach he used. I actually was stubborn and wanted to feel unique in some way when it was coming to a solution. I had to step back a little bit and look at the big picture.

Reflection

If you ever struggle and spent like 8-12hrs like me for some logic nonsense and you feel it in the tip of the tongue, it is time to step a little bit back when you are not going anywhere after 1 hour.

Few tips to do (which I didn't do but I wish I do at the moment) was have drawings in how you are picturing it out mentally. Not drawing for the first time was my first mistake. I felt challenged by my own mind and sabotaging my own work to continuing being stubborn.

Code Solution

Now, the solution is that I added my UIManager script with my SpawnManager and RayCastDetection in order to make it work. Here is how I did it:

UIManager.cs

public class UIManager : MonoBehaviour
{
    public Text scoreBoardDisplay;
    public Text scoreBoardDisplayShadowed;
    public AudioSource sound_score;
    public string scoreText;
    public int score;
    public int oldScore;
    public bool success;
    public bool incrementScore;
    
    void Start()
    {
        success = false; //NEW
        incrementScore = false; //NEW
    }

    void Update()
    {
        if (score != oldScore)
        {
            sound_score.Play();
            scoreText = score.ToString();
            scoreBoardDisplay.text = scoreText;
            scoreBoardDisplayShadowed.text = scoreText;
            oldScore = score;
        }
    }
}

SpawnManager.cs

public class SpawnManager : MonoBehaviour
{
    public GameObject[] obstaclesPrefab;
    private PlayerControl playerControllerScript;
    public UIManager UIManagerScript; //NEW
    private float startDelay = 1.69f;
    private float repeatRate = 1.1f;
    void Start()
    {
        playerControllerScript = GameObject.Find("Player").GetComponent<PlayerControl>();
        UIManagerScript = GameObject.Find("UI_Manager").GetComponent<UIManager>();
        InvokeRepeating("SpawnObstacle", startDelay, repeatRate);
    }

    void SpawnObstacle()
    {
        if (playerControllerScript.gameOver == false)
        {
            float randomY = Random.Range(-2f, 2f);
            Vector3 randomHeight = new Vector3(35, randomY, -7);
            GameObject pipe = ObjectPooling.sharedInstance.GetPooledObject();
            if (pipe != null)
            {
                pipe.transform.position = randomHeight;
                pipe.SetActive(true);
                UIManagerScript.incrementScore = false; //NEW, where my problem happens to be solved
                UIManagerScript.success = false; //NEW, where my problems happens to be solved
            }
        }
    }
}

RayCastDetection.cs

public class RayCastDetection : MonoBehaviour
{
    public UIManager UIManagerScript;
    public PlayerControl playerControlScript;
    private RaycastHit hit;
    public bool passed;
    void Start()
    {
        UIManagerScript = GameObject.Find("UI_Manager").GetComponent<UIManager>();
        playerControlScript = GameObject.Find("Tomato").GetComponent<PlayerControl>();
        passed = false;
    }

    void Update()
    {
        Vector3 beamDown = transform.TransformDirection(Vector3.down);
        Ray ray = new Ray(transform.position, beamDown);

        if (!passed)
        {
            if (Physics.Raycast(ray, out hit))
            {
                if (hit.collider.tag == "Player" && !UIManagerScript.success) //Setting a condition to check if the beam hasn't been touched YET
                {
                    //Once touched, turn this true and increment
                    UIManagerScript.incrementScore = true;
                    UIManagerScript.score++;
                }
//If I already incremented my score, then there is no need to re-increment it again from the same ray. Thus, a flag for success is handled
                if (UIManagerScript.incrementScore)
                    UIManagerScript.success = true;
                Debug.DrawRay(transform.position, hit.point - transform.position);
            }
        }
    }
}

Again, thank you very much for guiding me to the right track :). If I think in something to add, I will update it under my Reflection header.

If you want to play around or use the code, feel free and play with it in my Git

Zeid Tisnes
  • 396
  • 5
  • 22