Hierarchy Climbing and Descending Unity - c#

I have the following hierarchy on my Player prefab for what will be a very simple multiplayer shooter.
It works this way, the Controller object has the scripts that deal with player input, the PlayerShip object has all of the turning, moving, shooting scripts etc and the Camera is just as it sounds, a camera with a few scripts on it for moving it about.
When a new Player is instantiated the Control script needs to locate its relevant PlayerShip, simple enough.
This can be achieved using the following code:
_playerShip = gameObject.transform.parent.gameObject.transform.FindChild("PlayerShip").GetComponent<PlayerShip>();
Which works fine, the only thing is, to me that looks very clunky, inelegant and brittle. Consequently, I'm wondering if there's a better, more efficient, less ugly way of achieving the same thing?

First of all,
_playerShip = gameObject.transform.parent.gameObject.transform.FindChild("playerShip").GetComponent<PlayerShip>();
is redundant. That can be reduced to
_playerShip = gameObject.transform.parent.FindChild("playerShip").GetComponent<PlayerShip>();
or even use '/' just like you do with folder names.
_playerShip = GameObject.Find("Player/playerShip").GetComponent<PlayerShip>();
Now, instead of instantiating the Object and searching for it later on, you can actually instantiate it and retrieve the reference at the-same time.
GameObject obj = Instantiate(prefab,Vector3.zero,Quaternion.identity) as GameObject;
_playerShip = obj.GetComponent<PlayerShip>();
If it is a network game that you instantiate with Network.Instantiate:
GameObject obj = Network.Instantiate(prefab,Vector3.zero,Quaternion.identity,0) as GameObject;
_playerShip = obj.GetComponent<PlayerShip>();
Now, send the PlayerShip reference to the Control script.

If this isn't being called every frame consider this:
_playerShip = GameObject.Find("PlayerShip").GetComponent>PlayerShip>();
Read more about the above here: https://docs.unity3d.com/ScriptReference/GameObject.Find.html
If it is being called every frame look in to this:
https://docs.unity3d.com/ScriptReference/GameObject.FindWithTag.html
Either one should be easier to understand.
EDIT: I may have misunderstood. Would rearranging the hierarchy so that controller is a parent of playerShip? Then you should be able to just look at the child, rather than going through a parent to a sibling.

Related

Destroying Instantiated Clone Components Breaks Original Game Object Components?

Maybe im missing something obvious but to keep it short. I have working code for a character. When you "dash" I want to leave behind an after-image type effect, by cloning the player, removing its unneeded components and then applying a shader. Issue is as soon as I instantiate the clone the original player stops functioning (cant move etc). It still has all its components and everything as normal, and the clone does get the correct components removed. But it still breaks. As soon as I remove that line, its back to normal. Any ideas?
This is the only relevant code in the script that instantiates the clone.
private void DodgeEffect()
{
GameObject _DodgeSFX = Instantiate(gameObject, transform.position, transform.rotation );
Destroy(_DodgeSFX.GetComponent<PlayerController>());
Destroy(_DodgeSFX.GetComponent<PlayerCombat>());
Destroy(_DodgeSFX.GetComponent<WarpController>());
Destroy(_DodgeSFX.GetComponent<Animator>());
}
its because you are making a copy of gameObject. while Instantiate() returns a GameObject, it also returns whatever you put into the first section of the method. instead, make a seperate gameObject than the player in the editor, and make _DodgeSFX public so you can put the copy into the slot. Then, just instantiate that seperate GameObject and you wont have to destroy the components through script(because you remove the components in the editor), saving time
Ok so from some testing I think its down to the Unity.InputSystem only allowing 1 instance of each Input to be enabled at once. When I instantiated a clone of the player, it still goes through the Enable/Awake functions before those components are destroyed, and since the Inputs on that clone were never disabled, that becomes the "main" one. If I set the main player's scripts with Inputs deactive and then active again, it all works as normal. Note that the script was still working on the main character just fine, it was only the inputs that was broken. Im still not sure why this is the case, if its a limitation of the input system, or intentional, cant seem to find documentation of this experience anywhere.

Spawning gameobjects relative to the position, width and length of another gameobject?

I'm currently developing a game in Unity using C# and I've run into a small problem.
I need to spawn a certain gameobjects relative to the Spawnposition and length of another game object. Now I figured that bounds.size would be the best function to use in this instance. As shown bellow I declare first the variable that uses this in my start method:
public void Start()
{
GameObject PointBar = (GameObject) Resources.Load("PointBar Horizontal");
PointBarVectorLength = PointBar.GetComponent<BoxCollider2D>().bounds.size.x;
PointBarVectorConv = Camera.main.WorldToViewportPoint(new Vector2(PointBarVectorLength, 0f));
}
However, the gameobjects in question are inactive at start's call and thus I presume don't return any value for bounds.size when called.
Does anyone know how I can access the bounds.size or equivalent of an inactive gameobject or is there something else I'm missing here?
As noted in the documentation for the Collider.bounds property, bounds "will be an empty bounding box if the collider is disabled or the game object is inactive". So your assumption was pretty much right. Since the object doesn't exist in worldspace that makes sense.
I'm not sure about the most elegant solution for your use case but two options spring to mind.
Calculate the object's spawn dimensions by accessing its transform.localScale and factoring in the scale of prospective parent objects. That could get messy but you could probably also write a helper method to make it more manageable.
Instantiate the object somewhere off screen after you load it, access its Collider.bounds property to get the information you need then move it where ever you want it or store the information for later.
There may be better solutions but that's what leaps to mind for me.
I solved the issue by using GetComponent().bounds.size.x;
instead of BoxCollider. This component can get accessed when the game object is not active.

Why are transforms typically passed when working with in-game objects rather than game objects?

I am well along in learning Unity basics but would like to nail down my understanding of the relation between components and the objects that own them. In the tutorials I've been watching typically use or pass the Transform component when working with objects pulled in code. For example:
void Explode ()
{
Collider[] colliders = Physics.OverlapSphere(transform.position, explosionRadius);
foreach (Collider collider in colliders)
{
if (collider.tag == "Enemy")
{
Damage(collider.transform);
}
}
}
which calls "Damage" with the transform on the colliders it finds:
void Damage (Transform enemy)
{
Enemy e = enemy.GetComponent<Enemy>();
if (e != null)
{
e.TakeDamage(damage);
}
}
Looking at this, it is clear that it is pulling a Component found on all game objects and then using "GetComponent" to pull another component instance by name since the "Enemy" component isn't going to have its own method. Why not just pass collider.gameObject though? I tried this (after changing the Damage to expect a GameObject) and it worked fine.
This seems more intuitive to me but all of the tutorials I've seen use the transform component instead. Does anyone have any insight into why this is so? It would help me deepen my understanding of how Unity structures its code.
I'm not sure what tutorials you are following, but the ones I followed when I first started with Unity used GameObject instead of Transform.
I agree that it is more intuitive to use GameObject. Transform is a part or Component of a GameObject, but since it's mandatory it will always be there, hence the public field .transform. But since a GameObject is technically the holder of all its Componenents, it would be most logical, architecture wise, to pass that as a parameter instead.
At the end of the day, it makes little difference in your examples since you can call a lot of the same methods on both Transform as GameObject.
TLDR:
Use whatever you feel makes most sense.
Short answer
GameObject is more intuitive as you say.
Long answer
I think (my opinion) it's best to pass the most specific and relevant component.
For example, assume you have a function for a character to pick up a crate:
public void PickUp(Transform crate)
{
crate.SetParent(this.transform);
crate.localPosition = Vector3.zero; // or whatever your attachment point is
}
Here it makes sense in my mind that the Transform is passed, because it's the most specific component that will be needed.By passing GameObject here you are only delaying the inevitable GetComponent<Transform> or go.transform.
On the other hand, if you have a function to hide a create then passing the game object would be the bare minimum you need to achieve this:
public void Hide(GameObject crate)
{
crate.SetActive(false);
}
Passing anything else just delays the inevitable x.gameObject
In the explosion example I don't think that I would pass either to be honest. I would probably do this:
void Explode ()
{
Collider[] colliders = Physics.OverlapSphere(transform.position, explosionRadius);
var enemies = colliders
.Select(x => x.GetComponent<Enemy>())
.OfType<Enemy>(); // Or IHealth, IDamageable, ITarget, etc if you have other things that can take damage.
foreach (var enemy in enemies) // If empty, nothing will happen by default
{
enemy.TakeDamage(damage);
}
}
With this approach you can see that there is no need to check tags or nulls. The enemies enumerable is guaranteed to contain either enemies or nothing at all.
By always passing gameObject/transforms you will always have to worry about what it is that you are really receiving at the destination component. You will also open yourself to situations where you are not sure anymore where certain changes to you gameObjects are being made because it can be anything in the system that's making those changes. Your ColorChangerComponent could actually also be moving the object around, destroying some other components, etc. By giving the component a Renderer, it more naturally limits the component to changes on the Renderer only (although you could obviously violate this limitation unless you perform actions against appropriate interfaces).
The only time it really makes sense to pass a generic component is when you are broadcasting this 'event' to a bunch of possible receivers that will each to a different thing with the object. I.e. you can't be certain at compile time what the gameobject will be used for, or you know that it will be used for many different things.
The fact that Unity itself returns Collider[] for the collision check sort of supports this line of thinking, otherwise it would've just returns GameObject[]. The same goes for OnTriggerEnter(Collider collider) or OnCollisionEnter(Collision collision) etc.

Multiplayer Game with players on separate versions of the same map

I'm making/want to make a bullet-hell game where there are essentially two players, each own their own identical copy of a map competition for the high score. And the game ends whenever a player dies.
I have all of this working in multiplayer, but my approach seems a bit hacky, so I'm wondering if there's a better way for me to accomplish this.
At first I was using just unity's network manager, and in my player Start() function I checked if(!isLocal), and if so I set the gameObject enabled to false. This worked great, but like I said, felt a little hacky.
Example of the code:
if (!isLocalPlayer) {
gameObject.SetActive(false);
return;
}
Next I moved to unity's LobbyManager. This is where things got really sticky. Now on the Host, the game loads fine, but on the client, only one game object is created, and it's set as disabled which leads me to believe that it is the 'enemy' or not local player object.
I slowly figured out that the cause of this was setting the enemy game object active to false. If I left it true, both players would spawn on both screens. My solution now is to not disable the enemy player object, but every component on it so it doesn't get in the way.
Again this feels very hacky, and like it could lead to problems down the road. Is this really the best option, or am I missing something obvious?
Example of the Code:
if (!isLocalPlayer) {
gameObject.GetComponent<MeshRenderer>().enabled = false;
gameObject.GetComponent<BoxCollider>().enabled = false;
gameObject.GetComponent<CharacterController>().enabled = false;
gameObject.GetComponent<PlayerController>().enabled = false;
gameObject.GetComponent<Rigidbody>().detectCollisions = false;
guns = gameObject.GetComponentsInChildren<MeshRenderer>();
foreach (MeshRenderer gun in guns) {
gun.enabled = false;
}
}
Thanks in advance! Sorry this is so long, hopefully it isn't a chore to read.
Make an empty scene with nothing but a score manager and what else you need to transfer.
Have another scene as an asset in your project folder, this is where the map is.
When a player joins, have them join the scene with the score manager.
If they also are the local player, they should load the map scene.
That way, both players will be in the same scene while also having their own instance of the map.
You can load scenes in asynchronously and additively, which would be ideal for your situation.

Unity 2D: Destroy + Instantiate new GameObject vs Changing state and sprite

I am creating prefabs for farmland in my 2D game. Since I want all ground tiles to turn into farmable-land when hit with a hoe, I am worried about performance (Since there will be hundreds of these GameObjects in a scene).
Would the best thing be to Destroy the ground tile and Instantiate a farm tile in it's position, or would it be better to create a more generic Script that is attached to every Ground tile(?), which has states like:
GROUND, FARMABLE, PLANTED
, then depending on the state I change behaviour and set a sprite like: tile.GetComponent<Image>().sprite = Resources.Load<Sprite>(pathToSprite);
Maybe I'm missing a better option but these are the ones I can think of.
Destroyin and instantiating hundreds of game objects during runtime is a recipe for disaster, memory fragmentation and GC going wild which will kill performance.
The second solution is way better. Use an enum for all the possible states of a tile in your game logic, and then change the Sprite field of the Sprite Renderer component accordingly.
PSA: Don't use GetComponent and Resources.Load every time you need to change a sprite, get a reference to the Sprite Renderer component and to a Sprite[] array which contains all possible state images to use in Awake, and then use those references to change the sprite image when needed.
Edit: Answering your question in the comment.
Be sure that the Sprites are all in the Resources folder of your
project, they can be in a subfolder, i.e.: Resources/Sprites.
Check that the path string is correct, i.e. if the sprite asset is called Circle, and it's in the Resources/Sprites folder,
path must be "Sprites/Circle".
Code example:
public class MyClass : MonoBehaviour {
Sprite[] spritesArray = new Sprite[10];
void Awake() {
spritesArray[0] = Resources.Load<Sprite>("Sprites/Circle");
}
}
Try This:{ tile.GetComponent(/).sprite = Resources.Load(pathtosprite); }

Categories