I have just started to learn Unity 2d and I have taken a task at hand where I want to Instantiate a Prefab from Assets folder and then disable some of the child elements in it once the prefab is initiated. Following is my code :
void createPuzzleGame()
{
Puz = Resources.Load("Dog") as GameObject;
Instantiate(Puz, PuzArea.transform);
for (int i = 0; i < Puz.transform.childCount; ++i)
{
Transform currentItem = Puz.transform.GetChild(i);
if (currentItem.name.StartsWith("a") || currentItem.name.StartsWith("og"))
{
currentItem.gameObject.SetActive(false); //this line doesn't work
}
else
{
Debug.Log(currentItem.name);
}
}
}
I want to disable all child images of the prefab Puz that start with letter 'a' or 'og'.
The prefab Dog(clone) gets created on running the code. However the child elements don't seem to disable. Where am I going wrong? Please help.
You are trying to iterate through the children of the original prefab Puz.
You rather need to iterate through the newly created instance
var instance = Instantiate(Puz, PuzArea.transform);
foreach(Transform child in instance.transform)
{
if (child.name.StartsWith("a") || child.name.StartsWith("og"))
{
child.gameObject.SetActive(false);
}
else
{
Debug.Log(child.name);
}
}
Puz is the prefab's reference. Instantiate returns the actual instance you create from Puz. You need to change the instance:
Transform dog = Instantiate(Puz, PuzArea.transform).transform;
for (int i = 0; i < dog.childCount; ++i) { /* ... */ }
or, you can add a script to your prefab, and move this logic there, something like this (in this case, you have to add your references to _images on the prefab, but you can use your search by name logic also):
public class Puzzle : MonoBehaviour
{
[SerializeField] private GameObject[] _images;
public void SetImagesVisibility(bool visible)
{
for (int i = 0; i < _images.Length; i++) _images[i].SetActive(visible);
}
}
and you can use this at instantiating:
Puzzle puz = Instantiate(Puz, PuzArea.transform).GetComponent<Puzzle>();
puz.SetImagesVisibility(false);
Related
I have an object, which contains a list of GameObjects. I wish to destroy all of these GameObjects in its destructor.
However, when I attempt to call GameObject.Destroy() from inside the destructor, it seems to halt execution (the line after GameObject.Destroy() never executes, but the line before it does)
If i copy and paste exactly the same code into a function called not_a_destructor() and call that instead, it works perfectly. What gives? I've got it working, but I would really like to understand what's going on.
Destructor and not_a_destructor() code:
// Destructor DOES NOT work
~MoveAction(){
for(int i = 0; i < arrows.Count; i++){
Debug.Log("wasd");
GameObject.Destroy(arrows[i]);
Debug.Log("asdf");
}
}
// Identical code, calling not_a_destructor() works perfectly
public void not_a_destructor(){
for(int i = 0; i < arrows.Count; i++){
Debug.Log("PRETEND DESTRUCTOR!");
GameObject.Destroy(arrows[i]);
Debug.Log("GameObject destroyed successfully");
}
}
As requested in comments, a full copy of the class:
public class Action
{
public int type;
public string debug_string;
public GameObject ui_pill; // Only present for Actions created on the client
}
public class MoveAction : Action
{
public int type = ActionType.MOVE;
public MapHex origin;
public List<MapHex> route; // Intermediate hexes travelled through during the move (includes target_hex)
public Fleet fleet;
private List<GameObject> arrows = new List<GameObject>(); // Arrows for the graphical representation of the pending move on the tactical map
public MapHex target_hex {
get {
return route[route.Count - 1];
}
}
public string debug_string {
get {
return "MOVE ACTION WITH FLEET: " + fleet.name;
}
}
public MoveAction(Fleet _fleet, List<MapHex> _route){
fleet = _fleet;
route = _route;
origin = fleet.planned_position;
update_arrows_from_route();
}
public void update_arrows_from_route(){
Material default_material = new Material(Shader.Find("Sprites/Default"));
// Create one arrow for every hex we will pass through.
MapHex last = fleet.planned_position;
foreach (MapHex hex in route){
// Create arrow from last to hex
GameObject arrow_gameobj = new GameObject();
arrow_gameobj.name = "move_order_arrow";
LineRenderer line_renderer = arrow_gameobj.AddComponent<LineRenderer>();
line_renderer.material = default_material;
line_renderer.SetColors(fleet.owner.color, fleet.owner.color);
line_renderer.positionCount = 2;
arrow_gameobj.layer = layers.tactical_map;
Vector3[] line_points = new Vector3[]{last.position, hex.position};
line_renderer.SetPositions(line_points);
line_renderer.startWidth = 0.1f;
line_renderer.endWidth = 0.1f;
arrows.Add(arrow_gameobj);
last = hex;
}
}
public void not_a_destructor(){
for(int i = 0; i < arrows.Count; i++){
Debug.Log("PRETEND DESTRUCTOR!");
GameObject.Destroy(arrows[i]);
Debug.Log("GameObject destroyed successfully");
}
}
~MoveAction(){
for(int i = 0; i < arrows.Count; i++){
Debug.Log("wasd");
GameObject.Destroy(arrows[i]);
Debug.Log("asdf");
}
}
Its probable best to use more of Unity and less of C#, there is a good callback called OnDestroy() which would be a fine place to destroy all the arrows. If execution of your unity code depends on running a finalizer on something, this is a very strong code smell.
Unless you are using IO in a way that REQUIRES an action to happen in a finalizer (possibly things like releasing an IO resource), its best to leave them empty, and put Unity code inside Unity callbacks
i am creating a video game like Crypt of the necrodancer and i am blocked when i want to generate a map
my idea was simple :
make a grid system and replace some tiles to have specific tiles (obstacle, wall...) but i don't know how i should store the information to replace my tile.
I based my grid generation map from Sebastian Lague tutorial
i would like to replace in the inspector my tiles
as far as i am, i've found a script for replacing some tiles by another but i can't save it...
Here is the script for replacing stuff
using UnityEngine;
using UnityEditor;
public class ReplaceWithPrefab : EditorWindow
{
[SerializeField] private GameObject prefab;
[MenuItem("Tools/Replace With Prefab")]
static void CreateReplaceWithPrefab()
{
EditorWindow.GetWindow<ReplaceWithPrefab>();
}
private void OnGUI()
{
prefab = (GameObject)EditorGUILayout.ObjectField("Prefab", prefab, typeof(GameObject), false);
if (GUILayout.Button("Replace"))
{
var selection = Selection.gameObjects;
for (var i = selection.Length - 1; i >= 0; --i)
{
var selected = selection[i];
var prefabType = PrefabUtility.GetPrefabType(prefab);
GameObject newObject;
if (prefabType == PrefabType.Prefab)
{
newObject = (GameObject)PrefabUtility.InstantiatePrefab(prefab);
}
else
{
newObject = Instantiate(prefab);
newObject.name = prefab.name;
}
if (newObject == null)
{
Debug.LogError("Error instantiating prefab");
break;
}
Undo.RegisterCreatedObjectUndo(newObject, "Replace With Prefabs");
newObject.transform.parent = selected.transform.parent;
newObject.transform.localPosition = selected.transform.localPosition;
newObject.transform.localRotation = selected.transform.localRotation;
newObject.transform.localScale = selected.transform.localScale;
newObject.transform.SetSiblingIndex(selected.transform.GetSiblingIndex());
Undo.DestroyObjectImmediate(selected);
}
}
GUI.enabled = false;
EditorGUILayout.LabelField("Selection count: " + Selection.objects.Length);
}
}
any ideas for how i could save the informations?
Unity recently added a tilemap system a few versions ago. It seems to be what you're looking for and it already handles the grids for you.
You can read more about it here:
https://docs.unity3d.com/Manual/class-Tilemap.html
You can edit the tilemap with the SetTile method and a Tile object, rather than instanciating a bunch of prefabs. The Tiles themselves are created with a tile palette which you can also read more about in the page above.
I have game for multiple players where each user selects their hero before game starts and that loads the selected heroes into the battle arena.
I have small issue with getting the instantiation to spawn in correct numbers of players
The method that I have for Spawning the characters:
private void Placement()
{
for (int i = 0; i < SelectedCards.Count; i++)
{
for (int t = 0; t < AvailableHeroes.Count; t++)
{
if (AvailableHeroes[t].name == SelectedCards[i].name)
{
Debug.Log(AvailableHeroes[t]);
// Instantiate(AvailableHeroes[t], PlayerSpawnLocation[t].transform.position, transform.rotation);
}
{
}
}
}
}
This script checks for amount of selected hero cards and puts it against my list that has all the available heroes to choose from(prefabs).
The debug.log shows that only the correct heroes get called.
Instantiate ends up spawning a loot of heroes instead of the selected amount.
For clarity I attach full class:
{
private int playerSize; //amount of choices for card selection
private GameManager GM;
[Header("Lists for Spawning in Heroes")]
public List<GameObject> SelectedCards;
public List<GameObject> AvailableHeroes;
public List<Transform> PlayerSpawnLocation;
[Header("Canvas used for the game")]
public Transform GameCanvas;
public Transform CharacterCanvas;
//When scene starts it takes how many players will be picking a card.
void Start()
{
//connects this script with gamenmanager to be able to manipulate the cameras
GM = GameObject.Find("GameManager").GetComponent<GameManager>();
//gets playersize information from main menu selection
PlayerPrefs.GetInt("PlayerSize");
playerSize = PlayerPrefs.GetInt("PlayerSize");
SelectedCards = new List<GameObject>();
//enables/disables correct canvas not to cause any problems when we initiate this scene
GameCanvas.gameObject.SetActive(false);
CharacterCanvas.gameObject.SetActive(true);
}
// Update is called once per frame
void Update()
{
if (playerSize <= 0)
{
Placement();
GM.CharacterSelectionCamera.enabled = false;
GameCanvas.gameObject.SetActive(true);
CharacterCanvas.gameObject.SetActive(false);
GM.BattleCamera.enabled = true;
}
}
public void PlayerSelected(int cardPicked)
{
playerSize -= cardPicked;
}
private void Placement()
{
for (int i = 0; i < SelectedCards.Count; i++)
{
for (int t = 0; t < AvailableHeroes.Count; t++)
{
if (AvailableHeroes[t].name == SelectedCards[i].name)
{
Debug.Log(AvailableHeroes[t]);
// Instantiate(AvailableHeroes[t], PlayerSpawnLocation[t].transform.position, transform.rotation);
}
{
}
}
}
}
}
I hope someone can explain where I am going wrong with this.
Thanks,
I got the answer, I guess I was just being tired from working and could not see the obvious.
For those who wonder what solution is
The method gets called each frame thus it continues to endlessly spawn objects
There are 2 ways to fix it
1 Make coroutine and then return after you make your initial batch
2 Use a boolean at update so not only it checks player size but also whenever it can spawn it or not, you set the boolean to false after method get called.
I did not even notice the update function part.
Just a heads up, in your start function, PlayerPrefs.GetInt("PlayerSize"); is not doing anything since the value is not saved anywhere.
I have a gameObject (myObject) and a component that attached somewhere in that gameObject (myComponent).
I duplicate game object:
var duplicate = Instantiate(myObject);
And then I want to have reference to the same component, but on my duplicate gameObject.
Is there a way to get same component on duplicated object?
I tried to get component by index, but is is not work for hierarchy of game objects.
You could make a copy of component but since component can have only 1 parent (gameObject) this means 1 component instance cannot be shared with 2 game objects.
Otherwise if you are OK with having 2 separate instances of component on both game objects, you could create Prefab out of gameObject (with component) and instantiate prefab.
Considering that you might have multiple components of the same type, but with different settings (properties) and you want to find component with the same settings you would have to use GetComponents and loop through results to find a new (duplicated) component with exactly the same settings.
Considering (for simplicity) you look for property called Id:
MyComponent myObjectsComponent = ... // here logic to find it, etc
GameObject duplicate = Instantiate(myObject);
List<MyComponent> myComponents = duplicate.GetComponents<MyComponent>();
// This can be replaced with bellow LINQ
MyComponent foundComponent = null;
foreach(MyComponent c in myComponents) {
if (c.Id=myObjectsComponent.Id) {
foundComponent = c;
break;
}
}
Alternatively you could use LINQ to simplify loop:
MyComponent foundComponent = (from c in myComponents where c.Id=myObjectsComponent.Id select c).FirstDefault<MyComponent>();
Thanks to all of you.
I was needed something like this:
public static T GetSameComponentForDuplicate<T>(T c, GameObject original, GameObject duplicate)
where T : Component
{
// remember hierarchy
Stack<int> path = new Stack<int>();
var g = c.gameObject;
while (!object.ReferenceEquals(g, original))
{
path.Push(g.transform.GetSiblingIndex());
g = g.transform.parent.gameObject;
}
// repeat hierarchy on duplicated object
GameObject sameGO = duplicate;
while (path.Count != 0)
{
sameGO = sameGO.transform.GetChild(path.Pop()).gameObject;
}
// get component index
var cc = c.gameObject.GetComponents<T>();
int componentIndex = -1;
for (int i = 0; i < cc.Length; i++)
{
if (object.ReferenceEquals(c, cc[i]))
{
componentIndex = i;
break;
}
}
// return component with the same index on same gameObject
return sameGO.GetComponents<T>()[componentIndex];
}
Try this:
var duplicate = Instantiate(myObject) as GameObject;
var componentDup = duplicate.GetComponent<__YOUR_COMPONENT__>();
https://docs.unity3d.com/ScriptReference/GameObject.GetComponent.html
Alright, I'm going to have a go at trying to understand what you want as well... My interpretation is that you want a reference to a specific myComponent on your duplicateObject. Very well, this is how you could do it:
public class MyObject : MonoBehaviour {
// Create a reference variable for the duplicate to have
public Component theComponent { get; set; }
void Start()
{
// Save the component you want the duplicate to have
theComponent = GetComponent<Anything>();
// Create the duplicate
var duplicate = Instantiate(gameObject);
// Set the component reference to the saved component
duplicate.GetComponent<MyObject>().theComponent = theComponent;
}
}
This way you should have a reference to the same component in all your instances of the object. You could also just create a script that holds a static reference to the specific component, which every script can reach without instantiating anything.
using UnityEngine;
public class DataHolder {
public static Component theComponent;
}
I am making a game like pong with multiple power-ups. One of them is to have a "Triple Ball" power-up. I tried just creating another reference of the ball, but that doesn't seem to work. I figured this would work because I mostly work with Java where I can just create another instance. Here is how I tried to do it, I tried testing through just clicking a button:
public class TripleBall : MonoBehaviour {
public Ball firstBall;
private int amountOfBalls = 2;
private Ball[] ballArray;
private bool start, avail, located;
void Start () {
ballArray = new Ball[amountOfBalls - 1];
for (int i = 0; i < ballArray.Length; i++)
ballArray[i] = new Ball();
}
// Update is called once per frame
void Update () {
if (Input.GetKeyDown(KeyCode.B))
start = true;
if (start)
{
//Begin locator: Makes it so when the balls spawn they spawn where the first ball
if (!located) {
for (int i = 0; i < ballArray.Length; i++)
{
ballArray[i].transform.position = firstBall.transform.position;
}
located = true;
}
//End locator : if statement and loop
}
}
}
But it seems I can't do this because Ball is of type MonoBehavior.
Question: Is there any way to do this the way I planned or any way at all? Thanks in advanced!
You really don't use new to create an instance. The only place you are right in your code is using new to initialize array. After that, you are supposed to use GameObject.AddComponent to create a new instance instead of the new keyword. This is because Ball class derives from the MonoBehaviour.
You Ball class derives from MonoBehaviour.
public class Ball : MonoBehaviour
{
}
Here is your code fixed:
private Ball[] ballArray;
void Start()
{
ballArray = new Ball[amountOfBalls - 1];
for (int i = 0; i < ballArray.Length; i++)
ballArray[i] = gameObject.AddComponent<Ball>();
}
Assuming that your Ball class looks like the code below and does not inherit from MonoBehaviour:
public class Ball
{
}
Then code in your question would be valid and using new is the right way.
You need to use instantiate. It is derived from Unity's Object class, so assuming "ball" inherits from either Object or GameObject, this is what you want. You can find more info about it here: http://docs.unity3d.com/ScriptReference/Object.Instantiate.html