instantiate game object with children - c#

I have a GameObject(EnemyProducer) that instantiate another GameObject(EnemyFormation) which has several children (actual enemies).
However, when i instantiate EnemyFormation Gameobject it does not have any children!
EnemyFormation is a prefab with all the required children.
This is how it looks like:
Here is the EnemyProducer code that Instantiates EnemyFormation:
public class EnemyProducer : MonoBehaviour {
EnemyFormation enemyGroup;
Transform enemyFormationTransform;
public float speed;
float boundary, currentY;
bool goingDown = true;
public GameObject enemyFormation;
// Use this for initialization
void Start () {
// Create enemyformation
enemyFormation = Instantiate (enemyFormation);
enemyFormation.transform.parent = transform;
enemyGroup = enemyFormation.GetComponent<EnemyFormation>();
boundary = Camera.main.orthographicSize;
enemyFormationTransform = enemyFormation.transform;
}
void Update () {
// if all enemies are killed, create a new one
if (!enemyGroup.hasEnemy ()) {
enemyFormation = Instantiate (enemyFormation);
enemyFormation.transform.parent = transform;
enemyGroup = enemyFormation.GetComponent<EnemyFormation>();
enemyFormationTransform = enemyGroup.gameObject.transform;
}
}
}

The first instantiation may be successful because it cloned the prefab from the Editor but then you reassigned the newly cloned prefab to the enemyFormation. When all enemies (children) were destroyed (assume that you were using Destroy()) then enemyFormation would contain no child. Next time you Instantiate(enemyFormation) you were expected to get a gameObject without children because it were not the prefab from the Editor anymore (it was reassigned by you).
Sorry I have to delete the previous answer. It is wrong anyway.

Related

How to change the child character model in unity editor

I have a character creator script that creates a player gameobject with the character model as a child
public class CreatePlayer : MonoBehaviour
{
[MenuItem("GameObject/Create Player", false, 10)]
static void Create()
{
GameObject player = new GameObject("Hero");
player.tag = "Player";
GameObject character = Instantiate((GameObject)Resources.Load("SampleCharacter")); // Instantiate the 3D model
character.transform.parent = player.transform;
CharacterController cc = player.AddComponent<CharacterController>();
cc.center = new Vector3(0f, 0.9f, 0f);
cc.radius = 0.2f;
cc.height = 1.9f;
player.AddComponent<PlayerMovement>();
Animator animator = character.AddComponent<Animator>();
animator.runtimeAnimatorController = (RuntimeAnimatorController)Resources.Load("Animations/PlayerAnimation");
player.AddComponent<PlayerAnimations>();
player.AddComponent<PlayerHealth>();
Camera.main.gameObject.AddComponent<CameraController>();
Camera.main.GetComponent<CameraController>().followTarget = player.transform;
}
}
What I need is to be able to change the character model whenever needed after the character creation in the editor by dragging and dropping a character prefab. This should also apply the animator and animation controller as well to the new prefab added.
You've got two options.
Destroy the character entirely (with the Destroy(GameObject) function) and load in a new one as a child whenever you switch.
Call GetComponentInChildren< T>() to get the MeshRenderer and Animator components and swap out the necessary properties (sharedMesh and controller respectively) manually. That one may be a bit trickier and have some bugs to work out. You'll probably also need to destroy and load in the entire skeleton if you're using skinned meshes, and hook up the necessary references on the new one.

gameObject Transform in DontDestroyOnLoad

I use the following script to copy the transform of a gameObject.
public Transform alignment;
void Update()
{
DontDestroyOnLoad(transform.gameObject);
transform.position = new Vector3(alignment.position.x, alignment.position.y, alignment.position.z);
float y = alignment.transform.localRotation.eulerAngles.y;
float x = alignment.transform.localRotation.eulerAngles.x;
float z = alignment.transform.localRotation.eulerAngles.z;
transform.localEulerAngles = new Vector3(x, y, z);
}
I want to acces this transform in other scenes (apply it to gameObjects).
How can I reach the transforms, that are in DontDestroyOnLoad?
First of all, don't use DontDestroyOnLoad in your update function. Use it in your Start or Awake funciton.
Secondly you can access your Non-Destroyed object like you would any other object.
Only differences between the two is that the Non-Destroyed stays in the scene when changing scenes.
Here are some ways to access it:
var obj = GameObject.Find("My Non Destroyed Object Name");
Or my personal favorite, if the object is used for scoring or settings I'll create a new gameobject and add the following script to it:
public class GameManager : MonoDevelop {
/* These are all script on the same gameobject that is not being destroyed */
public static Settings settings;
public static PlayerMananger playerMananger;
public static UIManager uiManager;
void Start() {
DontDestroyOnLoad(this.gameobject);
// Get the components
settings = GetComponent<Settings>();
playerMananger = GetComponent<PlayerMananger>();
uiManager = GetComponent<UIManager>();
}
}
Now I can create a script from anywhere and I can access all these Manager and setting files simply using:
var settings = GameManager.settings;
You need to give alignment as parameter to the DontDestroyOnLoad method, not transform.gameObject.
DontDestroyOnLoad(alignment.gameObject);
Also, you don't need to call this method inside Update(). Just calling it once it enough. You can call it inside Start(), like this:
void Start()
{
DontDestroyOnLoad(alignment.gameObject);
}
Now your alignment gameobject will still be accessible when you change scenes. If you want to access it from other gameobjects in the new scene, you might want to give it a tag or make it a singleton.

PUN 2 | Cant dynamically add players child transform to transform array

Im using the following Camera script (link) for all players. The script zooms in and out to capture all players. There are 4 players total in game. Testing with 2 right now. I cannot get the child object of the network player (the Kitty_Orange's transform) to automatically attach to the camera.
Child object has the Player tag.
https://learn.unity.com/tutorial/camera-control?projectId=5c5149c5edbc2a001fd5be95#5c7f8528edbc2a002053b398
I have a GameSetupController.cs that instantiates the player into the scene. This seems the most appropriate place to add the transform of the avatar to the camera. When player enters game scene I get a null reference.
Error when trying to dynamically add transforms to Camera m_Targets transform array.
WITH DEBUG
Debug.Log("CC.m_Targets.Length" + CC.m_Targets.Length); //Troubleshooting
CC.m_Targets = new Transform[players.Length]; // array of size 1-4
NullReferenceException: Object reference not set to an instance of an object
GameSetupController.CreatePlayer () (at Assets/InfoGamerPhoton/Scripts/GameSetupController.cs:33)
GameSetupController.Start () (at Assets/InfoGamerPhoton/Scripts/GameSetupController.cs:14)
WITHOUT DEBUG
CC.m_Targets = new Transform[players.Length]; // array of size 1-4
NullReferenceException: Object reference not set to an instance of an object
GameSetupController.CreatePlayer () (at Assets/InfoGamerPhoton/Scripts/GameSetupController.cs:34)
GameSetupController.Start () (at Assets/InfoGamerPhoton/Scripts/GameSetupController.cs:14)
CameraControl.cs
public Transform[] m_Targets; // All the targets the camera needs to encompass. [HideInInspector]
GameSetupController.cs
using Photon.Pun;
using System.IO;
using UnityEngine;
public class GameSetupController : MonoBehaviour
{
private CameraControl CC;
public GameObject[] players;
// This script will be added to any multiplayer scene
void Start()
{
CC = GetComponent<CameraControl>();
CreatePlayer(); //Create a networked player object for each player that loads into the multiplayer scenes.
}
private void CreatePlayer()
{
Debug.Log("Creating Player");
PhotonNetwork.Instantiate(Path.Combine("PhotonPrefabs", "PhotonPlayer"), Vector3.zero, Quaternion.identity);
players = GameObject.FindGameObjectsWithTag("Player");
if (players.Length == 0)
{
return;
}
for (int i = 0; i < players.Length; i++)
{
Debug.Log("players.Length" + players.Length); //Troubleshooting
Debug.Log("CC.m_Targets.Length" + CC.m_Targets.Length); //Troubleshooting
CC.m_Targets = new Transform[players.Length]; // array of size 1-4
Debug.Log(CC.m_Targets.Length);
Debug.Log(players.Length);
CC.m_Targets[i] = players[i].transform;
Debug.Log("m_Targets : " + CC.m_Targets[i]);
Debug.Log("players : " + players[i]);
}
}
}
My Temp Solution - inside CameraControl.cs
private void FixedUpdate()
{
m_Targets = new List<Transform>();
//ADDS PLAYERS TO THE M_TARGETS LIST!
players = GameObject.FindGameObjectsWithTag("Player");
foreach (GameObject child in players)
{
//Debug.Log(child.gameObject.transform.GetChild(0));
m_Targets.Add(child.gameObject.transform.GetChild(0));
}
Your CC object is null, that's where the Null references are coming from. GetComponent will only find Components attached to the same GameObject as the script invoking it. If the objects are added to the scene statically in editor, you can use [SerializeField] attribute to make the private object visible in inspector and assign the reference manually. Find methods are generally expensive, so if you can avoid using them by storing the references in a common place or setting them up beforehand, that's the recommended approach.

Can only spawn one object at once in unity

In my game I have a game object called ExclamationMark which I want to spawn above enemies heads when the player gets into range and they become "Alerted".
I've made this simple script to do that, but for some reason it will only work on one game object.
My enemy script:
void CheckForPlayer()
{
// Define player and get position
var player = GameObject.FindWithTag("Player");
var playerPos = (int)player.transform.position.x;
if (transform.Find("Graphics"))
{
// Define gameobject position
var enemyPos = transform.Find("Graphics").gameObject.transform.position.x;
// Define range to spawn tiles in
var range = 5;
var rangeInfront = enemyPos + range;
var rangeBehind = enemyPos - range;
if (playerPos >= rangeBehind && playerPos <= rangeInfront)
{
enemyIsActive = true;
if (transform.Find("ExclamationMark"))
{
var exMark = transform.Find("ExclamationMark").gameObject.GetComponent<ExclamationMarkSpawn>();
exMark.SpawnExclamationMark();
}
}
else
{
enemyIsActive = false;
}
}
}
My ! script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ExclamationMarkSpawn : MonoBehaviour {
public GameObject spawnPos;
public GameObject exclamationMark;
public GameObject exclamationMarkAudio;
public void SpawnExclamationMark()
{
StartCoroutine(GameObject.FindGameObjectWithTag("MainCamera").GetComponent<CameraShake>().Shake(0.2f, 0.2f, 0.2f));
Instantiate(exclamationMark, spawnPos.transform.position, Quaternion.identity);
if (exclamationMarkAudio)
Instantiate(exclamationMarkAudio, spawnPos.transform.position, Quaternion.identity);
StartCoroutine(DestroyExclamationMark());
}
IEnumerator DestroyExclamationMark()
{
yield return new WaitForSeconds(1);
var children = new List<GameObject>();
foreach (Transform child in transform) children.Add(child.gameObject);
children.ForEach(child => Destroy(child));
}
}
Just to be sure: I assume every player has its own instance of both of your scripts attached (some maybe nested further in their own hierarchy).
I assume that since you are using transform.Find which looks for the object by name within it's own children.
In general using Find and GetComponent over and over again is very inefficient! You should in both classes rather store them to fields and re-use them. Best would be if you can actually already reference them via the Inspector and not use Find and GetComponent at all.
In general finding something by name is always error prone. Are you sure they are all called correctly? Or are others maybe further nested?
Note: Find does not perform a recursive descend down a Transform hierarchy.
I would prefer to go by the attached components. You say it has e.g. a RigidBody. If this is the only Rigidbody component in the hierarchy below your objects (usually this should be the case) then you could instead rather simply use
// pass in true to also get disabled or inactive children
Rigidbody graphics = GetComponentInChildren<Rigidbody>(true);
the same for the ExclamationMarkSpawn
// Would be even beter if you already reference these in the Inspector
[SerializeField] private Rigidbody graphics;
[SerializeField] private ExclamationMarkSpawn exclamationMark;
[SerializeField] private Transform player;
private void Awake()
{
if(!player) player = GameObject.FindWithTag("Player");
if(!graphics) graphics = GetComponentInChildren<Rigidbody>(true);
if(!exclamationMark) exclamationMark = GetComponentInChildren<ExclamationMarkSpawn>(true);
}
private void CheckForPlayer()
{
// If really needed you can also after Awake still use a lazy initialization
// this adds a few later maybe unnecessary if checks but is still
// cheaper then using Find over and over again
if(!player) player = FindWithTag("Player");
if(!graphics) graphics = GetComponentInChildren<Rigidbody>(true);
if(!exclamationMark) exclamationMark = GetComponentInChildren<ExclamationMarkSpawn>(true);
var playerPos = (int)player.position.x;
// always if making such a check also give a hint that something might be missing
if (!graphics)
{
// by adding "this" you can now simply click on the message
// in the console and it highlights the object where this is happening in the hierarchy
Debug.LogWarning("graphics is missing here :'( ", this);
return;
}
// Define gameobject position
var enemyPos = graphics.transform.position.x;
// Define range to spawn tiles in
// this entire block can be shrinked down to
if (Mathf.Abs(playerPos - enemyPos) <= 5)
{
enemyIsActive = true;
if (exclamationMark) exclamationMark.SpawnExclamationMark();
}
else
{
enemyIsActive = false;
}
}
The same also in ExclamationMarkSpawn.cs.
I would additionally only allow 1 exclamation mark being visible at the same time. For example when a player jitters in the distance especially assuming both, the player and the enemy, I would move the entire instantiation to the routine and use a flag. Especially since this is called every frame in Update while the player stays in the range!
Also re-check and make sure your enemies are not maybe referencing the same spawnPos and thus all instantiating their exclamation marks on top of each other.
public class ExclamationMarkSpawn : MonoBehaviour
{
public Transform spawnPos;
public GameObject exclamationMark;
public GameObject exclamationMarkAudio;
[SerializeField] private CameraShake cameraShake;
// only serialized for debug
[SerializeField] private bool isShowingExclamation;
private void Awake()
{
if(!cameraShake) cameraShake = Camera.main.GetComponent<CameraShake>();
// or assuming this component exists only once in the entire scene anyway
if(!cameraShake) cameraShake = FindObjectOfType<CameraShake>();
}
public void SpawnExclamationMark()
{
StartCoroutine(ShowExclamationMark());
}
private IEnumerator ShowExclamationMark()
{
// block concurrent routine call
if(isShowingExclamation) yield brake;
// set flag blocking concurrent routines
isShowingExclamation = true;
// NOTE: Also for this one you might want to rather have a flag
// multiple enemy instances might call this so you get concurrent coroutines also here
StartCoroutine(cameraShake.Shake(0.2f, 0.2f, 0.2f));
Instantiate(exclamationMark, spawnPos.position, Quaternion.identity);
if (exclamationMarkAudio) Instantiate(exclamationMarkAudio, spawnPos.position, Quaternion.identity);
yield return new WaitForSeconds(1);
var children = new List<GameObject>();
foreach (var child in transform.ToList()) children.Add(child.gameObject);
children.ForEach(child => Destroy(child));
// give the flag free
isShowingExclamation = false;
}
}
Try this;
if (transform.Find("ExclamationMark"))
{
var exMark = transform.Find("ExclamationMark").gameObject.GetComponent<ExclamationMarkSpawn>();
exMark.SpawnExclamationMark(transform.position); //Add transform.position here
}
public void SpawnExclamationMark(Vector3 EnemyPos)
{
StartCoroutine(GameObject.FindGameObjectWithTag("MainCamera").GetComponent<CameraShake>().Shake(0.2f, 0.2f, 0.2f));
Instantiate(exclamationMark, EnemyPos, Quaternion.identity);
if (exclamationMarkAudio)
Instantiate(exclamationMarkAudio, EnemyPos, Quaternion.identity);
StartCoroutine(DestroyExclamationMark());
}

How to preserve the full state of objects between scenes?

When loading a new Scene I run into the trouble of having my mouse drag not carry over to the next scene and having to re-click when the new scene is loaded.
I would like the mouse click to carry over seamlessly to the next scene without the player noticing and more generally I would like to know how best to preserve certain game objects and make them carry over to the next scene.
In essence, what I'm trying to do is have the entire game act like one big scene that the player can play trough but still be broken down into smaller scenes that could be accessed or transformed into levels at a later stage.
Thanks in advance.
This is the code I'm currently using
using UnityEngine;
using System.Collections;
using UnityEngine.Profiling;
public class MoveBall : MonoBehaviour
{
public static Vector2 mousePos = new Vector2();
private void OnMouseDrag()
{
mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
transform.position = mousePos;
DontDestroyOnLoad(this.gameObject);
}
}
Bellow is the script that is responsible for the loading of the scene:
public class StarCollision : MonoBehaviour
{
private bool alreadyScored = false;
private void OnEnable()
{
alreadyScored = false;
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("White Ball"))
{
if (!alreadyScored)
{
ScoreScript.scoreValue += 1;
StartCoroutine(ChangeColor());
alreadyScored = true;
}
}
if (ScoreScript.scoreValue > 4)
{
SceneManager.LoadScene(1);
}
}
private IEnumerator ChangeColor()
{
ScoreScript.score.color = Color.yellow;
yield return new WaitForSeconds(0.1f);
ScoreScript.score.color = Color.white;
gameObject.SetActive(false);
}
}
I think the main reason why it doesn't work is that you probably also have another Camera in the new Scene.
The OnMouseDrag rely on the Physics system internally using the objects Collider and raycasts from the Camera. Now if you switch Scene I'ld guess the one Camera gets disabled so your drag gets interrupted.
Also using LoadScene instead of LoadSceneAsync causes a visible lag and might also be related to the issue.
I have a maybe a bit more complex solution but that is what I usually do:
1. Have one Global Scene "MainScene"
This Scene contains stuff like e.g. the MainCamera, global ligthning, global manager components that should never be destroyed anyway.
2. Use additive async Scene loading
You said you do not want your user to not note when the scene switches so I would recommend using SceneManager.LoadSceneAsync anyway.
Then in order to not unload the before mentioned MainScene you pass the optional parameter LoadSceneMode.Additive. This makes the new Scene be loaded additional to the already present one. Then later you only have to exchange those by unloading the previously additive loaded scene.
I created a very simple static manager for this:
public static class MySceneManager
{
// store build index of last loaded scene
// in order to unload it later
private static int lastLoadedScene = -1;
public static void LoadScene(int index, MonoBehaviour caller)
{
caller.StartCoroutine(loadNextScene(index));
}
// we need this to be a Coroutine (see link below)
// in order to correctly set the SceneManager.SetActiveScene(newScene);
// after the scene has finished loading. So the Coroutine is required
// in order to wait with it until the reight moment
private static IEnumerator loadNextScene(int index)
{
// start loading the new scene async and additive
var _async = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);
// optionally prevent the scene from being loaded instantly but e.g.
// display a loading progress
// (in your case not but for general purpose I added it)
_async.allowSceneActivation = false;
while (_async.progress < 0.9f)
{
// e.g. show progress of loading
// yield in a Coroutine means
// "pause" the execution here, render this frame
// and continue from here in the next frame
yield return null;
}
_async.allowSceneActivation = true;
// loads the remaining 10%
// (meaning it runs all the Awake and OnEnable etc methods)
while (!_async.isDone)
{
yield return null;
}
// at this moment the new Scene is supposed to be fully loaded
// Get the new scene
var newScene = SceneManager.GetSceneByBuildIndex(index);
// would return false if something went wrong during loading the scene
if (!newScene.IsValid()) yield break;
// Set the new scene active
// we need this later in order to place objects back into the correct scene
// if we do not want them to be DontDestroyOnLoad anymore
// (see explanation in SetDontDestroyOnLoad)
SceneManager.SetActiveScene(newScene);
// Unload the last loaded scene
if (lastLoadedScene >= 0) SceneManager.UnloadSceneAsync(lastLoadedScene);
// update the stored index
lastLoadedScene = index;
}
}
This MySceneManager is a static class so it is not attached to any GameObject or Scene but simply "lives" in the Assets. You can now call it from anywhere using
MySceneManager.LoadScene(someIndex, theMonoBehaviourCallingIt);
The second parameter of type MonoBehaviour (so basically your scripts) is required because someone has to be responsible for running the IEnumerator Coroutine which can't be done by the static class itself.
3. DontDestroyOnLoad
Currently you are adding any GameObject you dragged at any time to DontDestroyOnLoad. But you never undo this so anything you touched meanwhile will be carried on from that moment ... forever.
I would rather use e.g. something like
public static class GameObjectExtensions
{
public static void SetDontDestroyOnLoad(this GameObject gameObject, bool value)
{
if (value)
{
// Note in general if DontDestroyOnLoad is called on a child object
// the call basically bubbles up until the root object in the Scene
// and makes this entire root tree DontDestroyOnLoad
// so you might consider if you call this on a child object to first do
//gameObject.transform.SetParent(null);
UnityEngine.Object.DontDestroyOnLoad(gameObject);
}
else
{
// add a new temporal GameObject to the active scene
// therefore we needed to make sure before to set the
// SceneManager.activeScene correctly
var newGO = new GameObject();
// This moves the gameObject out of the DontdestroyOnLoad Scene
// back into the currently active scene
gameObject.transform.SetParent(newGO.transform, true);
// remove its parent and set it back to the root in the
// scene hierachy
gameObject.transform.SetParent(null, true);
// remove the temporal newGO GameObject
UnityEngine.Object.Destroy(newGO);
}
}
}
This is an Extension Method which allows you to simply call
someGameObject.SetDontDestroyOnLoad(boolvalue);
on any GameObject reference.
Then I changed your script to
public class MoveBall : MonoBehaviour
{
public static Vector2 mousePos = new Vector2();
// On mouse down enable DontDestroyOnLoad
private void OnMouseDown()
{
gameObject.SetDontDestroyOnLoad(true);
}
// Do your dragging part here
private void OnMouseDrag()
{
// NOTE: Your script didn't work for me
// in ScreenToWorldPoint you have to pass in a Vector3
// where the Z value equals the distance to the
// camera/display plane
mousePos = Camera.main.ScreenToWorldPoint(new Vector3(
Input.mousePosition.x,
Input.mousePosition.y,
transform.position.z)));
transform.position = mousePos;
}
// On mouse up disable DontDestroyOnLoad
private void OnMouseUp()
{
gameObject.SetDontDestroyOnLoad(false);
}
}
And in your StarCollision script you only have to exchange
SceneManager.LoadScene(1);
with
MySceneManager.LoadScene(2, this);
Demo
For a little demonstration I "faked" it using two simple scripts
This one in the Main scene
public class LoadFirstscene : MonoBehaviour
{
// Start is called before the first frame update
private void Start()
{
MySceneManager.LoadScene(1, this);
}
}
And this one in the other scenes
public class LoadNextScene : MonoBehaviour
{
[SerializeField] private int nexSceneIndex;
private void Update()
{
if (!Input.GetKeyDown(KeyCode.Space)) return;
MySceneManager.LoadScene(nexSceneIndex, this);
}
}
And have 3 Scenes:
Main: As mentioned contains
the MainCamera
a DirectionalLight
the LoadFirstScene
test: contains
a MoveBall "Sphere"
the LoadNextScene
test2: contains
a MoveBall "Cube"
the LoadNextScene
With the indexes matching the build settings (make sure Main is always at 0 ;) )
I can now switch between test and test2 using the Space key.
If I drag one of the objects meanwhile I can carry it on into the next scene (but only 1 at a time). I can even take it on again back to the first scene in order to have e.g. two sphere objects I can play with ;)

Categories