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. Dismiss Notice

Stopping velocity on clones

Discussion in 'Scripting' started by danmct1995, Sep 4, 2020.

  1. danmct1995

    danmct1995

    Joined:
    Apr 21, 2020
    Posts:
    70
    I have a ball object that is to be stopped upon my level completing, which works fine, but my object will sometimes be cloned and the clones remain at the end of the level. I have set the velocity to 0 of my main object by calling this upon the level ending:

    Code (CSharp):
    1.             if (_ball != null)
    2.             {
    3.                 // remove ball velocity on level completion
    4.                 Rigidbody2D ballBody = _ball.GetComponent<Rigidbody2D>();
    5.                 if (ballBody != null)
    6.                 {
    7.                     ballBody.velocity = Vector2.zero;
    8.                 }
    9.             }
    I would like to do the same to a cloned object, but was not sure how to reference them.
     
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,697
    Save a reference to the clones as you clone them. Add them to a list if necessary. Then iterate through the list of all clones and set their velocities to zero when the level is complete.
     
    danmct1995 likes this.
  3. danmct1995

    danmct1995

    Joined:
    Apr 21, 2020
    Posts:
    70
    I understand most of that, but the part I don't understand how to do is to get the reference to cloned object in the first place. Like how do I reference a clone of my object (in this case I'm using the class "Ball" and cloning it)?
     
  4. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,697
    The instantiate method returns a reference to the clone.
    Code (CSharp):
    1. var clone = Instantiate(original);
    Then if you have a list of clones you can add the clone to the list:
    Code (CSharp):
    1. listOfClones.Add(clone);
     
    danmct1995 likes this.
  5. danmct1995

    danmct1995

    Joined:
    Apr 21, 2020
    Posts:
    70
    I think I might be stuck right now. I've been working on this a little bit and this is what I have right now:

    Code (CSharp):
    1.             var clone = Instantiate(_ball);
    2.             _listOfClones.Add(clone);
    3.             if (_ball != null)
    4.             {
    5.                 foreach (Ball ball in _listOfClones)
    6.                 {
    7.                     Rigidbody2D ballBody = _ball.GetComponent<Rigidbody2D>();
    8.                     if (ballBody != null)
    9.                     {
    10.                         ballBody.velocity = Vector2.zero;
    11.                     }
    12.                 }
    13.             }
    14.         }
    This is called within my level ending function upon the completion of a level. Should I be keeping the list elsewhere?
     
  6. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,697
    Well in the code you've shared it's not clear where you're "keeping" the list. Where is the
    _listOfClones
    variable declared? I would expect it to be an instance-level-field, which means it's declared inside the class, but outside any of the methods.

    It's also a little unclear to me why you're instantiating the ball here and also this is where you're stopping the velocities of the balls. But you're close in terms of the code that stops the balls. One problem is that you're not using the "ball" variable from the foreach to access the Rigidbody to stop it, you're accessing the prefab's Rigidbody, which won't actually do anything since the prefab itself is not typically an actual object in the scene. I would expect two different functions here like this:

    Code (CSharp):
    1. void SpawnABall() {
    2.   var clone = Instantiate(_ball);
    3.   _listOfClones.Add(clone);
    4. }
    Code (CSharp):
    1. void StopAllBalls() {
    2.   foreach (Ball ball in _listOfClones)
    3.   {
    4.     Rigidbody2D ballBody = ball .GetComponent<Rigidbody2D>();
    5.     if (ballBody != null)
    6.     {
    7.        ballBody.velocity = Vector2.zero;
    8.     }
    9.   }
    10. }
     
  7. danmct1995

    danmct1995

    Joined:
    Apr 21, 2020
    Posts:
    70
    I tried keeping the list within my manager script for the level. Would I be keeping this where the balls are created instead and call the list from my manager script?
     
  8. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,697
    I would probably keep it where the balls are created, yes.

    Then what you could do is create a StopAllBalls method, like i shared above, make it
    public
    , and have your level manager script call the StopAllBalls method on whatever component that is when the level is completed.
     
    danmct1995 likes this.
  9. danmct1995

    danmct1995

    Joined:
    Apr 21, 2020
    Posts:
    70
    I feel like I'm so close now with my code, but its still not stopping their velocity for some reason. Where I'm at with code now is :

    MANAGER SCRIPT
    Code (CSharp):
    1.         private void Update()
    2.         {
    3.                 ParametersForVictory();
    4.  
    5.                 // If no blocks remain, initiate end level method
    6.                 if (_noBlocksLeft == true)
    7.                 {
    8.                     EndLevel();
    9.                 }
    10.             }
    11.        
    12.         public void EndLevel()
    13.         {
    14.             if (_player != null)
    15.             {
    16.                 // disable the player controls
    17.                 KeyboardControls keyboardControls =
    18.                     _player.GetComponent<KeyboardControls>();
    19.  
    20.                 if (keyboardControls != null)
    21.                 {
    22.                     keyboardControls.enabled = false;
    23.                 }
    24.  
    25.                 // remove any existing motion on the player
    26.                 Rigidbody2D rbody = _player.GetComponent<Rigidbody2D>();
    27.                 if (rbody != null)
    28.                 {
    29.                     rbody.velocity = Vector2.zero;
    30.                 }
    31.             }
    32.             if (_ball != null)
    33.             {
    34.                 // remove ball velocity on level completion
    35.                 Rigidbody2D ballBody = _ball.GetComponent<Rigidbody2D>();
    36.                 if (ballBody != null)
    37.                 {
    38.                     ballBody.velocity = Vector2.zero;
    39.                 }
    40.             }
    41.             StopAllClones();
    42.         }
    43.        public void StopAllClones()
    44.         {
    45.             multiball = GetComponent<Multiball>();
    46.             foreach (Ball ball in multiball._listOfClones)
    47.             {
    48.                 Rigidbody2D ballBody = ball.GetComponent<Rigidbody2D>();
    49.                 if (ballBody != null)
    50.                 {
    51.                     ballBody.velocity = Vector2.zero;
    52.                 }
    53.             }
    54.         }
    MULTIBALL SCRIPT
    Code (CSharp):
    1.     Ball[] balls;
    2.     [SerializeField] Ball ball;
    3.     public List<Ball> _listOfClones = new List<Ball>();
    4.     public void Start()
    5.     {
    6.         Vector2 direction = Vector2.down;
    7.         Vector2 velocity = speed * direction;
    8.         GetComponent<Rigidbody2D>().velocity = velocity;
    9.     }
    10.     public void OnTriggerEnter2D(Collider2D PowerUp)
    11.     {
    12.         if (PowerUp.gameObject.name == "Paddle")
    13.         {
    14.             balls = FindObjectsOfType<Ball>();
    15.             FindObjectOfType<ScoreManager>().PowerupPoints();
    16.             foreach (Ball ball in balls)
    17.             {
    18.                 SpawnTwoBalls(ball.transform);
    19.             }
    20.             Destroy(gameObject);
    21.         }
    22.  
    23.         else if (PowerUp.transform.CompareTag("DeathBarrier"))
    24.         {
    25.             Destroy(gameObject);
    26.         }
    27.     }
    28.  
    29.     public void SpawnTwoBalls(Transform ballTransform)
    30.     {
    31.         Ball ball1 = Instantiate(ball, ballTransform.position, ballTransform.rotation);
    32.         ball1.GetComponent<Rigidbody2D>().velocity = new Vector2(ballTransform.GetComponent<Rigidbody2D>().velocity.x + 1, ballTransform.GetComponent<Rigidbody2D>().velocity.y + 1);
    33.         _listOfClones.Add(ball1);
    34.  
    35.         Ball ball2 = Instantiate(ball, ballTransform.position, ballTransform.rotation);
    36.         ball2.GetComponent<Rigidbody2D>().velocity = new Vector2(ballTransform.GetComponent<Rigidbody2D>().velocity.x - 1, ballTransform.GetComponent<Rigidbody2D>().velocity.y - 1);
    37.         _listOfClones.Add(ball2);
    38.     }
    39. }
    I think I may be accessing the clones wrong still, but I'm not sure why its not working right.
     
  10. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,697
    One thing that's a bit confusing to me is that the Multiball script reads kind of like a ball manager type of script that I would expect you to only have one of. However it has an OnTriggerEnter2D call that I would expect to see on for example, one of the balls themselves, especially since it is destroying the Multiball script when you call
    Destroy(gameObject);
    . It also has a Start() function that sets its movement speed. I think some of the logic is a bit mixed up here. Is there a Multiball script on the Ball prefab?

    I would expect the Manager script and the Multiball script to have only one instance each, sitting on a permanent GameObject. The balls I would expect to have a separate script that handles movement and collisions for that particular ball.

    As it is currently, you don't have any one place where all of your balls are kept track of. It seems like Multiball instance are created and destroyed all the time, and with them goes their list of balls they have instantiated.
     
  11. danmct1995

    danmct1995

    Joined:
    Apr 21, 2020
    Posts:
    70
    I see, this makes sense then. The multiball script is attached to a powerup in my game. After it is interacted with the powerup is destroyed, along with the game object. I'm assuming that you're saying my list is also deleted, so I no longer have a handle on any of the clones velocity and unity is trying to read a list that doesn't exist?

    If this is right, Would I have to create an entirely new manager for a powerup somehow? I'm confused on how to get around this, or could I preserve my list on another object somehow?
     
  12. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,697
    Your understanding of the situation seems spot on!

    The key is to separate everything according to its responsibility and lifecycle. Since, as you said, the powerup is a temporary object, it doesn't make sense to have that be the place to store a longer-lived list of all the active balls in the scene.

    Given what I've learned about your game in this thread, I think it would be appropriate to have the following components in your scene:

    • A Manager component, which lives on its own long-lived GameObject
    • A BallSpawner component, which lives on its own long-lived GameObject
    • A Multiball component, which is attached to the multiball powerup GameObject
    • Other components - Ball, ScoreManager, Paddle, etc...
    The BallSpawner should keep the list of spawned balls, and it should should expose some public methods that can be called by other scripts. For example StopAllBalls() and SpawnABall(). SpawnABall can accept the position where the new ball should be spawned.

    Your Multiball component, when it collides with the paddle, should call BallSpawner.SpawnABall(). If you want it to spawn two balls, have it call SpawnABall() twice. After that, it can destroy itself, as you are doing now.

    When the level ends, The Manager can call BallSpawner.StopAllBalls().

    You can use FindObjectOfType if you want to get the BallSpawner reference to make these calls, but I would suggest using FindObjectWithTag and GetComponent, as it will be a bit more performant. The best way would be to have BallSpawner use a Singleton pattern, but that can wait for another day.
     
    danmct1995 likes this.