Home > Enterprise >  Referencing Instantiated objects after their creation in Unity
Referencing Instantiated objects after their creation in Unity

Time:12-20

Hi!

After the discussion with Ruzihm in the comments. I've now created a simple version of my game to better ask the question I'm having.

The question now is, since I'm not able to manually create a connection to the testObject field in the inspector. How do I now tell Unity to use my instantiated objects while the game is running?

And is this a good solution for a RTS game that may have 100s of Units active at a time? The end goal here is to apply this force to a radius around the cursor. Which I was thinking of using Physics.OverlapSphere

Here's the minimal scenario of what I have:

  1. New Unity scene
  2. Attached the InputManager to the main camera.
  3. Created a capsule and a plane.
  4. Added ApplyForce to the Capsule
  5. Created a prefab from the capsule and deleted it from the scene.
  6. In the InputManager I added the ability to press space to Instantiate a capsule with the ApplyForce script attached..
  7. Drag the capsule prefab to the InputManager "objectToGenerate"
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace GL.RTS.Mites
{
    public class InputManager : MonoBehaviour
    {
        public GameObject testObject;

        public ApplyForce onSpawnTest;
        public GameObject objectToGenerate;

        void Start()
        {
            onSpawnTest = testObject.GetComponent<ApplyForce>();
        }

        void Update()
        {
            if(Input.GetKeyDown(KeyCode.Space))
            {
            Instantiate(objectToGenerate);
            }
            
            if (Input.GetMouseButton(0))
            {
                onSpawnTest.PushForward();
            }
        }
    }
}

The ApplyForce script that I attach to the Capsule:

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

namespace GL.RTS.Mites
{
    public class ApplyForce : MonoBehaviour
    {
        public float moveSpeed;
        Rigidbody rb;

        void Start()
        {
            rb = GetComponent<Rigidbody>();
            Debug.Log("A Mite has spawned!");
        }

        public void PushForward()
        {
            rb.AddRelativeForce(Vector3.up * moveSpeed * Time.deltaTime);
            Debug.Log("A force of: "   moveSpeed   " is being added.");
        }
    }
}

CodePudding user response:

Well, you are creating your new instances of your object, but your input manager immediately forgets about them (note that you do nothing with the return value). The InputManager only knows about the ApplyForce that was created in its Start (and then interacts with it depending on mouse input) and your ApplyForce script knows nothing about any InputManager. So, it should come as no surprise that only the first instance reacts to the mouse input.

So, something has to be done to your InputManager and/or your ApplyForce. Your InputManager could remember the instances it creates (which isn't enough, because what if for example, a map trigger creates new player controllable units) or it could go looking for units each time.

Your ApplyForce could register with the InputManager when they are created, but then you would need to loop through the units and find out which ones are under the mouse, anyway.

Since you only want to select ones based on what is near or under your cursor and only when input occurs and not like every frame, I would go with the simplest approach, just letting your InputManager find the units when it needs them. Something like below, explanation in comments:

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

namespace GL.RTS.Mites
{
    public class InputManager : MonoBehaviour
    {
        public GameObject testObject;

        public ApplyForce onSpawnTest;
        public GameObject objectToGenerate;
        
        private Camera mainCam;

        // which layers to consider for cursor detection
        [SerializeField] LayerMask cursorLayerMask;

        // how big for cursor detection
        [SerializeField] float cursorRadius;

        void Awake()
        {
            // cache main camera
            mainCam = Camera.main;
        }

        void Update()
        {
            if(Input.GetKeyDown(KeyCode.Space))
            {
                Instantiate(objectToGenerate);
            }
            
            if (Input.GetMouseButton(0))
            {
                Collider[] colls = FindCollidersUnderCursor();

                // check each collider for an applyforce and use it if present
                foreach( Collider coll in colls)
                {
                    ApplyForce af = coll.GetComponent<ApplyForce>();
                    if (af != null) 
                    {
                        af.PushForward();
                    }
                } 
            }
        }

        Collider[] FindCollidersUnderCursor()
        {
            // find ray represented by cursor position on screen
            // and find where it intersects with ground

            // This technique is great for if your camera can change
            // angle or distance from the playing field.

            // It uses mathematical rays and plane, no physics
            // calculations needed for this step. Very performant.
            Ray cursorRay = mainCam.ScreenPointToRay(Input.mousePosition);
            Plane groundPlane = new Plane(Vector3.up, Vector3.zero);
            if (groundPlane.Raycast(cursorRay, out float cursorDist))
            {
                Vector3 worldPos = cursorRay.GetPoint(cursorDist);

                // Check for triggers inside sphere that match layer mask
                return Physics.OverlapSphere(worldPos, cursorRadius, 
                        cursorLayerMask.value, QueryTriggerInteraction.Collide);
            }

            // if doesn't intersect with ground, return nothing
            return new Collider[0];
        }
    }
}

Of course, this will require that every unit you're interested in manipulating has a trigger collider.

CodePudding user response:

Well, in my opinion :

  • Unity using ECS, it mean that your game object in your game is an entity, and things it contains, for example, Rigidbody is a component. A script you attach to unity gameobject is also a component in this gameobject

  • In the ApplyForce script, you use those line

      public float moveSpeed;
      Rigidbody rb;
    
      void Start()
      {
          rb = GetComponent<Rigidbody>();
          Debug.Log("A Mite has spawned!");
      }
    
      public void PushForward()
      {
          rb.AddRelativeForce(Vector3.up * moveSpeed * Time.deltaTime);
          Debug.Log("A force of: "   moveSpeed   " is being added.");
      }
    
    • It means that you will get the 'Rigidbody' component in the Gameobject have this script. The GetComponent method is equal to this.gameObject.GetComponent, which means that it will refer to the Gameobject contain this script, and get the component called Rigidbody, and you have a method called : push forward to add a force using Rigidbody
  • In the InputManager class :

      public GameObject testObject;
    
      public ApplyForce onSpawnTest;
      public Unit _unit;
      public ApplyForce applyForce;
    
      // Start is called before the first frame update
      void Start()
      {
          onSpawnTest = testObject.GetComponent<ApplyForce>();
      }
    
      void Update()
      {
          if (Input.GetMouseButton(0))
          {
              onSpawnTest.PushForward();
          }
    
      }
    
    • You will refer to a gameobject, which you have declared in the 1st line of code.
    • The onSpawnText have the type of ApplyForce class, which you have declared in the following script
    • When you use GetComponent<>, the onSpawnText is assigned to a component in the testObject, according to your script, it is the ApplyForce script component. And you just use the function PushForward.

Is there anything you cannot understand ?. Just give me a comment

  • Related