Set Children in Prefab as individual GameObjects - c#

When spawning a Prefab, I'm trying to have each individual GameObject that makes up the Prefab run its own respective scripts. Basically,
I want the Prefab to break after spawning; leaving individual game Objects.
I have the GameObjects in the Prefab as children of an Empty. Any suggestions.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CreateFab : MonoBehaviour
{
public Transform Spawnpoint;
public Rigidbody Prefab;
public void OnClick()
{
Rigidbody RigidPrefab;
RigidPrefab = Instantiate(Prefab, Spawnpoint.position, Spawnpoint.rotation) as Rigidbody;
}
public void DetachFromParent()
{
// Detaches the transform from its parent.
transform.parent = null;
}
}
Cube Script
using UnityEngine;
using UnityEditor;
using System.IO;
public class WallClick : MonoBehaviour
{
string path;
public MeshRenderer mRenderer;
public void OpenExplorer()
{
path = EditorUtility.OpenFilePanel("Overwrite with png", "", "png");
GetImage();
}
void GetImage()
{
if (path != null)
{
UpdateImage();
}
}
void UpdateImage()
{
byte[] imgByte = File.ReadAllBytes(path);
Texture2D texture = new Texture2D(2, 2);
texture.LoadImage(imgByte);
mRenderer.material.mainTexture = texture;
}
}

If you spawn a prefab, give the spawned item a reference.
var obj = Instantiate(prefabGameobject);
You can then do whatever you like with the spawned object
var script = obj.AddComponent<YourScript>();
And you can then modify the variables of your script and so on.
Your prefab will not be touched.

The transform.parent you're using here refers to the transform of the gameobject your CreateFab.cs script is attached to, not the prefab's childrens (smaller cubes).
The correct way would be:
// Instantiate the prefab
RigidBody rigidPrefab = Instantiate(Prefab, Spawnpoint.position, Spawnpoint.rotation) as Rigidbody;
// Detach all children from the parent. Each child refers to the transform of a single cube.
foreach (Transform child in rigidPrefab.transform)
{
child.parent = null;
}

Related

Instantiate not showing object

I am trying to get a object to spawn at mouse position in unity 2d
whenever I click, but none of the objects are showing. It adds new clones in the hierarchy, but just not showing.
Here Is the script for the game controller object
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class gamecon : MonoBehaviour
{
public GameObject square;
public void Start()
{
}
public void Update()
{
if (Input.GetMouseButtonDown(0))
{
Vector3 spawnpos = Camera.main.WorldToScreenPoint(Input.mousePosition);
GameObject g =Instantiate(square, (Vector2)spawnpos, Quaternion.identity);
}
}
}
I cant find any answers on the web that apply to my situation.
Thanks for help.
The solution is to use the ScreenToWorldPoint method and not the WorldToScreenPoint because what you need is the world position to spawn your object.
Use the following code:
public void Update()
{
if (Input.GetMouseButtonDown(0))
{
Vector3 spawnpos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
GameObject g =Instantiate(square, (Vector2)spawnpos, Quaternion.identity);
}
}

How to open a child of GameObject in inspector from code (Unity)

E.g. I have Prefab MyPrefab, it has two childs (child_1, child_2).
AssetDatabase.OpenAsset(child_1); opens MyPrefab in inspector instead of child_1.
I'm gonna assume that you have a prefab and inside that prefab you have an another prefab that you wish to open using C#.
You can do that by using the PrefabUtility.GetCorrespondingObjectFromSource method on the child prefab that you wish to open and then calling AssetDatabase.OpenAsset method on the result that is returned.
Example
using UnityEditor;
using UnityEngine;
public class TestScript : MonoBehaviour
{
[SerializeField]
private GameObject prefab = null;
[SerializeField]
private string childPrefabName = string.Empty;
private void Run()
{
var child = prefab.transform.Find(childPrefabName);
var childPrefab = PrefabUtility.GetCorrespondingObjectFromSource(child);
AssetDatabase.OpenAsset(childPrefab);
}
[CustomEditor(typeof(TestScript))]
public class TestScriptEditor: Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if (GUILayout.Button("Run"))
{
var script = target as TestScript;
script.Run();
}
}
}
}

Is there any way to assign player to Camera after the game started in unity?

Okay, so I create an endless runner game that has character selection. The goal is, when the scene starts (with no player in the hierarchy), the game manager will instantiate the player based on the selected player. But, the CameraController, have the error "Object reference not set to an instance of an object", it cannot find the instantiate player object
Here is how I instantiate my player:
void Start()
{
int selectedCharacter = PlayerPrefs.GetInt("selectedChar");
GameObject prefab = characterPrefabs[selectedCharacter];
GameObject clone = Instantiate(prefab, playerStartPoint, Quaternion.identity);
player = FindObjectOfType<PlayerScript>();
platformStartPoint = platformGenerator.position;
scoreManager = FindObjectOfType<ScoreManager>();
Reset();
}
And this is my camera script:
public class CameraController : MonoBehaviour{
public PlayerScript player;
private Vector3 lastPlayerPosition;
private float distToMove;
// Start is called before the first frame update
void Start()
{
player = FindObjectOfType<PlayerScript>();
lastPlayerPosition = player.transform.position;
}
// Update is called once per frame
void Update()
{
distToMove = player.transform.position.x - lastPlayerPosition.x;
transform.position = new Vector3(transform.position.x + distToMove, transform.position.y, transform.position.z);
lastPlayerPosition = player.transform.position;
}
}
The camera should be move along with the character, do you have any idea how to fix this? Thank you
I'm assuming your Camera tries to search for the player before they get instantiated based on what you mentioned. There are several different approaches to fixing this.
Method 1
Have the script that instantiates the player grab the camera and assign the player instance.
Example
// Call this after instantiating a player instance
FindObjectOfType<CameraController>().AssignTarget(player);
// Add this to CameraController.cs
public void AssignTarget(PlayerScript player)
{
this.player = player;
lastPlayerPosition = player.transform.position;
}
Method 2
Add an event field somewhere that informs when player has been instantiated and subscribe to it using your camera.
Example
using System;
using UnityEngine;
public class PlayerScript : MonoBehaviour
{
public static event Action<PlayerScript> InstanceStarted;
private void Start()
{
InstanceStarted?.Invoke(this);
}
}
using UnityEngine;
public class CameraController : MonoBehaviour
{
private Player player;
private void Start()
{
player = FindObjectOfType<PlayerScript>();
if (player == null)
PlayerScript.InstanceStarted += OnPlayerInstanceStarted;
}
private void OnPlayerInstanceStarted(PlayerScript instance)
{
PlayerScript.InstanceStarted -= OnPlayerInstanceStarted;
player = instance;
}
}
Method 3
Add DefaultExecutionOrder attribute to your scripts and change the execution order to ensure that the player gets instantiated before the Camera starts to look for one.
Example
[DefaultExecutionOrder(100)]
public class TestScript : MonoBehaviour {}
Method 4
Have a Coroutine that periodically checks whether an instance of a player is available before allowing the Camera to do anything.
Example
using System.Collections;
using UnityEngine;
public class CameraController : MonoBehaviour
{
private PlayerScript player;
private void Start()
{
StartCoroutine(StartOncePlayerIsFound());
}
private IEnumerator StartOncePlayerIsFound()
{
player = FindObjectOfType<PlayerScript>();
while (player == null)
{
// Feel free to yield "WaitForEndOfFrame" or "null"
// if you wish to search for player every frame
yield return new WaitForSeconds(0.1f);
player = FindObjectOfType<PlayerScript>();
}
// ...
}
}
Try this one
transform.position = new Vector3(player.transform.position.x, transform.position.y, transform.position.z);

Next Scene Not Loading

My problem is that when all the enemies are killed the scene that should be loaded is not loading. I did add the scene to the Build setting (it has an index of 3) but it is still not loading. The script I created is attached to an empty object and not directly to the sprite (is that okay?). Can someone tell me why the scene isn't loading? Thank you.
This image is for to show you the EnemySpawner empty object inspector
EnemySpawner Script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class EnemySpawner : MonoBehaviour {
[SerializeField] GameObject EnemyPreFab;
[SerializeField] int MaxEnemies = 30;
[SerializeField] float EnemySpawnTime = 1.00001f;
[SerializeField] GameObject FirstWaypoint;
int CurrentNumOfEnemies = 0;
public LevelManager myLevelManager;
public int maximumnumberofhits = 0;
int timesEnemyHit;
IEnumerator SpawningEnemies()
{
while(CurrentNumOfEnemies <= MaxEnemies)
{
GameObject Enemy = Instantiate(EnemyPreFab, this.transform.position, Quaternion.identity);
CurrentNumOfEnemies++;
yield return new WaitForSeconds(EnemySpawnTime);
}
}
void Start()
{
StartCoroutine(SpawningEnemies());
timesEnemyHit = 0;
if (this.gameObject.tag == "EnemyHit")
{
CurrentNumOfEnemies++;
}
}
void OnCollisionEnter2D()
{
timesEnemyHit++;
if (timesEnemyHit == maximumnumberofhits)
{
CurrentNumOfEnemies--;
Destroy(this.gameObject);
}
if (CurrentNumOfEnemies == 0)
{
myLevelManager.LoadLevel("NextLevelMenu");
Debug.Log("LevelLoaded");
}
}
}
LevelManger script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class LevelManager : MonoBehaviour {
public void LoadLevel(string name)
{
print("Level loading requested for" + name);
SceneManager.LoadScene(name);
}
}
EnemyShooting Script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyShooting : MonoBehaviour {
[SerializeField] float EnemyLaserSpeed = 10f;
[SerializeField] float EnemyLaserFireTime;
[SerializeField] GameObject LaserBulletEnemyPreFab;
[SerializeField] int MaxNumberOfHits = 1;
int CurrentNumberOfHits = 0;
Coroutine FireCoroutine;
void OnTriggerEnter2D(Collider2D collider)
{
if(collider.gameObject.tag == "PlayerLaser")
{
if(CurrentNumberOfHits < MaxNumberOfHits)
{
CurrentNumberOfHits++;
Destroy(collider.gameObject);
Score.ScoreValue += 2;//The user will be rewarded 1 point
}
}
}
void DestroyEnemy()
{
if(CurrentNumberOfHits >= MaxNumberOfHits)
{
Destroy(gameObject);
}
}
private void Fire()
{
FireCoroutine = StartCoroutine(ShootContinuously());
}
void BecomeVisible()
{
Fire();
}
IEnumerator ShootContinuously()
{
while (true)
{
GameObject LaserBulletEnemy = Instantiate(LaserBulletEnemyPreFab, this.transform.position, Quaternion.identity) as GameObject;
LaserBulletEnemy.GetComponent<Rigidbody2D>().velocity = new Vector2(0, EnemyLaserSpeed);
EnemyLaserFireTime = Random.Range(0.5f, 0.9f);
yield return new WaitForSeconds(EnemyLaserFireTime);
}
}
// Use this for initialization
void Start () {
BecomeVisible();
}
// Update is called once per frame
void Update () {
DestroyEnemy();
}
}
EnemyPathing script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyPathing : MonoBehaviour {
[SerializeField] List<Transform> WayPoints;
[SerializeField] float EnemyMovingSpeed = 5f;
int WayPointIndex = 0;
void EnemyMoving()
{
if (WayPointIndex <= WayPoints.Count - 1)
{
var TargetedPosition = WayPoints[WayPointIndex].transform.position; //The position of where the enemy needs to go
TargetedPosition.z = 0f;
var MoveThisFrame = EnemyMovingSpeed * Time.deltaTime;
transform.position = Vector2.MoveTowards(this.transform.position, TargetedPosition, MoveThisFrame);
if(transform.position == TargetedPosition)
{
WayPointIndex++;
}
}
else
{
Destroy(gameObject);
}
}
// Use this for initialization
void Start () {
transform.position = WayPoints[WayPointIndex].transform.position;
}
// Update is called once per frame
void Update () {
EnemyMoving();
}
}
Problem
You're checking for collisions on the SPAWNER; when someone hits the Spawner it counts down enemies. But the Spawner doesn't have a collision box in the screenshot so it can never be hit. The Scene changing code can never be called.
So the game, based on the code, looks like this:
Spawn X enemies,
Hit the Spawner X times,
(Removed: Destroy the Spawner,)
Change scene.
I'm guessing this is conceptually incorrect and you actually want to check collisions on the spawned enemies, which will then count up the amount of destroyed enemies, and change the scene when they are all dead.
Solution
Conceptually, what you want is:
Spawn X enemies
Count up variable for every enemy
On Enemy death, count it down
When 0, change scene
So how do we code this?
Well, every enemy needs a reference to the object that holds the count. You can do this in several ways, when I personally do it I usually have just one spawner that is responsible for everyone so I make that one a Singleton, that can be references from anywhere:
EnemySpawner
public class EnemySpawner : MonoBehaviour
{
public static Spawner Instance = null;
int CurrentNumOfEnemies = 0;
// ... etc
void Start()
{
if (Instance == null)
Instance = this;
// Spawn enemies like you do already, CurrentNumOfEnemies++ for every spawned
}
public OnEnemyDeath() {
CurrentNumOfEnemies--;
if (CurrentNumOfEnemies < 1)
{
// You killed everyone, change scene:
LevelManager.LoadLevel("Your Level");
}
}
}
Enemy script (I don't know how your current code looks, but here's a minimal solution based on how I THINK your code looks):
void OnDestroy()
{
// This will run automatically when you run Destroy() on this gameObject
EnemySpawner.Instance.OnEnemyDeath(); // Tell the EnemySpawner that someone died
}
This will only work if you have exactly only ONE spawner. If you have multiple ones you will have to send a reference to the instance of its spawner to every spawned enemy. I can show you how to do ths too, if you wish.
Bonus content
LevelManager doesn't need to be on a GameObject, it can be static instead:
Remove the LevelManager script from any GameObject
Change your LevelManager code to this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public static class LevelManager
{
public static void LoadLevel(string name)
{
Debug.Log("Level loading requested for" + name);
SceneManager.LoadScene(name);
}
}
Now you can use it from ANYWHERE, without needing to initialize a reference to any script or GameObject:
LevelManager.LoadLevel("My Level");
myLevelManager.LoadLevel("NextLevelMenu"); is never executed, because you destroy the object in the if-test above.

Parent Transform changes each time I instantiate a new child

I have a Parent GameObject ZombieArmy with an attached script Zombie; its Transform changes each time a new zombie is instantiated as a child. How do I prevent the zombieArmy transform from changing and keep its transformed fixed at Vector3(0,0,0) while having the zombie have its own unique transform from each reSpawn()?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Zombie : MonoBehaviour {
public GameObject zombiePrefab;
public Transform zombieArmy;
public Transform zombieSpawnPoint;
private Transform[] spawnPositions;
public bool reSpawn = false;
private bool lastToggle = false;
private GameObject spawn;
// Use this for initialization
void Start () {
spawnPositions = zombieSpawnPoint.GetComponentsInChildren<Transform>();
}
private void NewSpawn() //spawn location of newZombie
{
if (reSpawn)
{
int i = Random.Range(1, spawnPositions.Length);
transform.position = spawnPositions[i].transform.position;
spawn = Instantiate(zombiePrefab, this.transform.position, this.transform.rotation, zombieArmy);
// zombieArmy.transform.position = new Vector3(0, 0, 0);
}
}
void Update () { //T-toggle
if (reSpawn != lastToggle)
{
NewSpawn();
reSpawn = false;
}
else
lastToggle = reSpawn;
}
}
If I'm understanding you correctly, that Zombie script is attached to the parent gameobject, right?
Then your NewSpawn() method is a bit incorrect.. This line
transform.position = spawnPositions[i].transform.position;
is actually changing the transform of the parent gameobject, as you say, because that's exactly what you're telling it to do.
If what you want is place each newly spawned object in the location of the randomly selected spawn point, try this instead:
int i = Random.Range(0, spawnPositions.Length); // Any reason the 0th index shouldn't be used?
spawn = Instantiate(zombiePrefab, this.transform);
spawn.transform.position = spawnPositions[i].transform.position;
spawn.transform.rotation = spawnPositions[i].transform.rotation;

Categories

Resources