How to add string to EditorGUILayout.ObjectField on the right side? - c#

using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
public class GetChildsEditorWindow : EditorWindow
{
public Transform transform;
List<Transform> allChildren = new List<Transform>();
List<string> names = new List<string>();
Vector2 scrollPos;
[MenuItem("Get Childs/Get")]
static void Init()
{
GetChildsEditorWindow window = (GetChildsEditorWindow)EditorWindow.GetWindow(typeof(GetChildsEditorWindow), false, "Get Childs");
window.Show();
}
private void OnGUI()
{
GUILayout.Space(20);
transform = EditorGUILayout.ObjectField("Transform to get childs", transform, typeof(Transform), true) as Transform;
if (GUILayout.Button("Start"))
{
allChildren = new List<Transform>();
ObjectLevel(transform);
for (int i = 0; i < GameObject.FindObjectsOfType<Transform>().Length; i++)
{
var t = GameObject.FindObjectsOfType<Transform>()[i];
var level = ObjectLevel(t);
names.Add(" Level " + level.ToString());
allChildren.Add(t);
}
}
if (allChildren != null && allChildren.Count > 0)
{
EditorGUILayout.BeginHorizontal();
scrollPos =
EditorGUILayout.BeginScrollView(scrollPos, GUILayout.Width(400), GUILayout.Height(400));
for (int i = 0; i < allChildren.Count; i++)
{
allChildren[i] = EditorGUILayout.ObjectField(names[i], allChildren[i], typeof(Transform), true) as Transform;
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndHorizontal();
}
}
public static int ObjectLevel(Transform current, int level = 0)
{
if (current.parent) return ObjectLevel(current.parent, ++level);
return level;
}
}
The result :
I want the Level 3 all the levels and numbers to be on the right side of the ObjectField instead on the left side like in the screenshot.

Solution is to add EditorGUILayout.BeginHorizontal();
And EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
allChildren[i] = EditorGUILayout.ObjectField(allChildren[i], typeof(Transform), true) as Transform;
EditorGUILayout.LabelField(names[i]);
EditorGUILayout.EndHorizontal();

Related

how to make line renderer stay flat on the surface?

I am drawing a line renderer between two navmesh agents and assigning a direction arrow texture to it. But the problem is that it is standing vertically on top of my road structure. I need to make a lie down flat.
The code for drawing lines between two agents:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class ParkingLevel : MonoBehaviour
{
[Space(10)]
[Header("For Path Rendering")]
public Transform targetAgent;
public NavMeshAgent agent_ParkingPoint;
public LineRenderer line;
public static ParkingLevel Instance;
void OnEnable()
{
if (Instance == null)
{
Instance = this;
line.startWidth = 3;
line.endWidth = 3;
return;
}
}
void OnDisable()
{
Instance = null;
}
void LateUpdate()
{
GetPath();
}
public void GetPath()
{
targetAgent = PlayerActivitiesManager.Instance.busAgent.transform;
line.SetPosition(0, agent_ParkingPoint.gameObject.transform.position);
agent_ParkingPoint.SetDestination(targetAgent.position);
DrawPath(agent_ParkingPoint.path);
agent_ParkingPoint.isStopped = true;
}
private void DrawPath(NavMeshPath path)
{
if (path.corners.Length < 2)
return;
line.positionCount = path.corners.Length;
for (var i = 1; i < path.corners.Length; i++)
{
line.SetPosition(i, path.corners[i]);
}
}
}
Here are my settings for the line renderer:
You could use a little trick:
Set the LineRenderer to position = Vector3.zero
Set Use World Space = false -> will use local space positions
Rotate the line to x = 90°
Finally now you have to alter the positions slightly and flip Z and Y axis
so something like e.g.
void OnEnable()
{
if (Instance == null)
{
Instance = this;
line.startWidth = 3;
line.endWidth = 3;
line.useWorldSpace = false;
var lineTransform = line.transform;
lineTransform.parent = null;
lineTransform.position = Vector3.zero;
lineTransform.localScale = Vector3.one;
line.transform.rotation = Quaternion.Euler(90, 0, 0);
}
}
private void DrawPath(NavMeshPath path)
{
if (path.corners.Length < 2)
{
line.enabled = false;
return;
}
line.enabled = true;
var flippedPositions = new Vector3[path.corners.Length];
var firstPosition = agent_ParkingPoint.transform.position;
var fistFlippedPosition = new Vector3(firstPosition.x, firstPosition.z, firstPosition.y);
flippedPositions[0] = fistFlippedPosition;
for (var i = 1; i < path.corners.Length; i++)
{
var p = path.corners[i];
flippedPositions[i] = new Vector3(p.x, p.z, p.y);
}
line.positionCount = flippedPositions.Length;
line.SetPositions(flippedPositions);
}

How to detect/know when object have been deleted from the hierarchy in editor or runtime and update a list in the inspector?

It's a bit long but everything is connected.
The goal what i'm trying to archive is to make objects in my hierarchy to be interactable items in the game.
I have editor script when i make right click on object i can chose in the context menu if i want to make this selected object to be interactable item. once i selected it to be interactable item it's changing the object tag to Interactable Item and also adding the object to a list in the editor.
The list is just to know and to be able to see what items are interactable instead searching for them all the time in all the hierarchy.
The problem is what to do if i deleted some object/s by accident or if i wanted to delete them how to update the list ?
Now if i will delete object it will show in the list missing.
The editor script :
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
[CustomEditor(typeof(InteractableItems))]
public class InteractableItemsEditor : Editor
{
private static List<GameObject> interactedobjects = new List<GameObject>();
private static bool interacted = false;
[MenuItem("GameObject/Make Object Interactable", false, 30)]
public static void GeneratePickupItems()
{
if (Selection.gameObjects.Length > 0)
{
interactedobjects.Clear();
for (int i = 0; i < Selection.gameObjects.Length; i++)
{
Selection.gameObjects[i].tag = "Interactable Item";
interactedobjects.Add(Selection.gameObjects[i]);
}
interacted = true;
}
}
private static Renderer TraverseHierarchy(Transform root, int childIndex)
{
Renderer renderer = new Renderer();
if (root.childCount == 0 || root.childCount > 0)
{
var skinnedMesh = root.GetComponent<SkinnedMeshRenderer>();
if (skinnedMesh) return skinnedMesh;
var mesh = root.GetComponent<MeshRenderer>();
if (mesh) return mesh;
}
if (root.childCount > 0)
{
foreach (Transform child in root)
{
// Deal with child
if (child.GetComponent<SkinnedMeshRenderer>() != null)
{
renderer = Selection.gameObjects[childIndex].GetComponentInChildren<SkinnedMeshRenderer>(true);
}
if (child.GetComponent<MeshRenderer>() != null)
{
renderer = Selection.gameObjects[childIndex].GetComponentInChildren<MeshRenderer>(true);
}
}
}
return renderer;
}
private static void AddBoxCollider(Renderer renderer, int index)
{
Selection.gameObjects[index].AddComponent<BoxCollider>();
var bounds = renderer.bounds;
var size = bounds.size;
var center = bounds.center;
var boxCollider = Selection.gameObjects[index].GetComponent<BoxCollider>();
size = boxCollider.transform.InverseTransformVector(size);
center = boxCollider.transform.InverseTransformPoint(center);
boxCollider.size = size;
boxCollider.center = center;
}
InteractableItems _target;
private ReorderableList _myList;
public void OnEnable()
{
_myList = new ReorderableList(serializedObject, serializedObject.FindProperty("interactableItems"))
{
draggable = false,
displayAdd = false,
displayRemove = false,
drawHeaderCallback = rect => EditorGUI.LabelField(rect, ""/*"My Reorderable List"*/, EditorStyles.boldLabel),
drawElementCallback = (rect, index, isActive, isFocused) =>
{
if (index > _myList.serializedProperty.arraySize - 1) return;
var element = _myList.serializedProperty.GetArrayElementAtIndex(index);
var color = GUI.color;
EditorGUI.BeginDisabledGroup(true);
{
GUI.color = Color.green;
EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width - 20, EditorGUIUtility.singleLineHeight), element, GUIContent.none);
}
EditorGUI.EndDisabledGroup();
GUI.color = color;
}
};
}
public override void OnInspectorGUI()
{
var list = serializedObject.FindProperty("interactableItems");
serializedObject.Update();
if (interacted)
{
interacted = false;
foreach (var newEntry in interactedobjects)
{
// check if already contains this item
bool alreadyPresent = false;
for (var i = 0; i < list.arraySize; i++)
{
if ((GameObject)list.GetArrayElementAtIndex(i).objectReferenceValue == newEntry)
{
alreadyPresent = true;
break;
}
}
if (alreadyPresent) continue;
// Otherwise add via the serializedProperty
list.arraySize++;
list.GetArrayElementAtIndex(list.arraySize - 1).objectReferenceValue = newEntry;
}
interactedobjects.Clear();
}
_myList.DoLayoutList();
serializedObject.ApplyModifiedProperties();
}
public object GetParent(SerializedProperty prop)
{
var path = prop.propertyPath.Replace(".Array.data[", "[");
object obj = prop.serializedObject.targetObject;
var elements = path.Split('.');
foreach (var element in elements.Take(elements.Length - 1))
{
if (element.Contains("["))
{
var elementName = element.Substring(0, element.IndexOf("["));
var index = Convert.ToInt32(element.Substring(element.IndexOf("[")).Replace("[", "").Replace("]", ""));
obj = GetValue(obj, elementName, index);
}
else
{
obj = GetValue(obj, element);
}
}
return obj;
}
public object GetValue(object source, string name)
{
if (source == null)
return null;
var type = source.GetType();
var f = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
if (f == null)
{
var p = type.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (p == null)
return null;
return p.GetValue(source, null);
}
return f.GetValue(source);
}
public object GetValue(object source, string name, int index)
{
var enumerable = GetValue(source, name) as IEnumerable;
var enm = enumerable.GetEnumerator();
while (index-- >= 0)
enm.MoveNext();
return enm.Current;
}
}
And the mono script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class InteractableItems : MonoBehaviour
{
public List<GameObject> interactableItems = new List<GameObject>();
}
The interactable items empty gameobject with the mono script the list is empty now :
When i select one object or more in the hierarchy and make right click and select the Make Object Interactable :
Then i make right click on the cube and select to make the cube interactable item :
Now the cube tagged as Interactable Item :
Now i'm deleting the cube from the hierarchy and this is what it's showing in the list :
Missing (Game Object)
This is what i want to handle. a case where i deleted a interactable item for some reason how to update the list by removing this item from the list ?
I can loop nonstop on the list in the mono script but that's not a solution it will make a bad performance if the list a bit bigger. it's more like a work around then a solution.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Whilefun.FPEKit;
[ExecuteInEditMode]
public class InteractableObjects : MonoBehaviour
{
public List<GameObject> interactableObjects = new List<GameObject>();
private void Update()
{
for(int i = 0; i < interactableObjects.Count; i++)
{
if(interactableObjects[i] == null)
{
interactableObjects.RemoveAt(i);
}
}
}
}
In the end i have this IK look at script that have a list of objects and i want to use this objects i make interactable items with this script list too. so i need to be able to update the list in this scripy sync with the list in the InteractableItems script :
using UnityEngine;
using System;
using System.Collections;
[RequireComponent(typeof(Animator))]
public class IKControl : MonoBehaviour
{
public Transform[] lookObj = null;
public float weightDamping = 1.5f;
private Animator animator;
private Transform lastPrimaryTarget;
private float lerpEndDistance = 0.1f;
private float finalLookWeight = 0;
private bool transitionToNextTarget = false;
void Start()
{
animator = GetComponent<Animator>();
}
// Callback for calculating IK
void OnAnimatorIK()
{
if (lookObj != null)
{
Transform primaryTarget = null;
float closestLookWeight = 0;
// Here we find the target which is closest (by angle) to the players view line
foreach (Transform target in lookObj)
{
Vector3 lookAt = target.position - transform.position;
lookAt.y = 0f;
float dotProduct = Vector3.Dot(new Vector3(transform.forward.x, 0f, transform.forward.z).normalized, lookAt.normalized);
float lookWeight = Mathf.Clamp(dotProduct, 0f, 1f);
if (lookWeight > closestLookWeight)
{
closestLookWeight = lookWeight;
primaryTarget = target;
}
}
if (primaryTarget != null)
{
if ((lastPrimaryTarget != null) && (lastPrimaryTarget != primaryTarget) && (finalLookWeight > 0f))
{
// Here we start a new transition because the player looks already to a target but
// we have found another target the player should look at
transitionToNextTarget = true;
}
}
// The player is in a neutral look position but has found a new target
if ((primaryTarget != null) && !transitionToNextTarget)
{
lastPrimaryTarget = primaryTarget;
finalLookWeight = Mathf.Lerp(finalLookWeight, closestLookWeight, Time.deltaTime * weightDamping);
float bodyWeight = finalLookWeight * .75f;
animator.SetLookAtWeight(finalLookWeight, bodyWeight, 1f);
animator.SetLookAtPosition(primaryTarget.position);
}
// Let the player smoothly look away from the last target to the neutral look position
if ((primaryTarget == null && lastPrimaryTarget != null) || transitionToNextTarget)
{
finalLookWeight = Mathf.Lerp(finalLookWeight, 0f, Time.deltaTime * weightDamping);
float bodyWeight = finalLookWeight * .75f;
animator.SetLookAtWeight(finalLookWeight, bodyWeight, 1f);
animator.SetLookAtPosition(lastPrimaryTarget.position);
if (finalLookWeight < lerpEndDistance)
{
transitionToNextTarget = false;
finalLookWeight = 0f;
lastPrimaryTarget = null;
}
}
}
}
}
This way i can build a small interactable system.
For the editor, you can try to handle EditorApplication.hierarchyChanged event.
But actually, in case I need such functionality, I will make a script and add it to every interactable object, that will register in the list in its Awake() and unregister in OnDestroy(). As for me, it is more obvious and will work in the runtime too.

How can I change the numbers/texts positions in the Update?

This script position text/numbers in front/above objects depending on the flags settings to true or false.
It was working fine when I called the method ChangeTextPosition only in the Start
but now I want to make that the changes will be also in the Update if I enable false the above or front it will remove the numbers/text and if both enabled true then it will position the numbers above and the text in front if only numbers is true it will position only the numbers above without the text at all and if only text then without the numbers.
Same it should be in the Start, if when starting the game and only one of the flags is true then only show the one is true and if both then show both.
Now when I called it in the Update it's just keep creating none stop numbers and texts.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Generatenumbers : MonoBehaviour
{
public UnityEngine.GameObject[] objectsToNumber;
public UnityEngine.GameObject text;
public float yPadding;
public bool rotateNumbers = false;
public float rotationSpeed = 10f;
public bool textAbove = false;
public bool textInFront = false;
public bool textOnFaces = false;
private List<GameObject> newTexts = new List<GameObject>();
private MeshRenderer[] renderer;
private Vector3 newPos;
private TextMesh textmesh;
private int numbers;
private void Start()
{
renderer = new MeshRenderer[objectsToNumber.Length];
ChangeTextPosition();
}
private void Update()
{
if (rotateNumbers == true)
{
for (int i = 0; i < newTexts.Count; i++)
{
newTexts[i].transform.Rotate(Vector3.up, 10 * rotationSpeed * Time.deltaTime);
}
}
ChangeTextPosition();
}
private void ChangeTextPosition()
{
for (int i = 0; i < objectsToNumber.Length; i++)
{
GameObject newText = Instantiate(text);
renderer[i] = newText.GetComponent<MeshRenderer>();
if (textAbove == true)
{
newPos = new Vector3
(
objectsToNumber[i].transform.position.x,
((objectsToNumber[i].transform.position.y + renderer[i].bounds.extents.y) + yPadding),
objectsToNumber[i].transform.position.z
);
}
if (textInFront == true)
{
newPos = new Vector3
(
((objectsToNumber[i].transform.position.x + renderer[i].bounds.extents.x) + yPadding),
objectsToNumber[i].transform.position.y,
objectsToNumber[i].transform.position.z
);
}
newText.transform.position = newPos;
newText.transform.parent = transform;
newText.name = i.ToString();
newText.tag = "Number";
newTexts.Add(newText);
textmesh = newText.GetComponent<TextMesh>();
textmesh.transform.localRotation = Quaternion.Euler(0, -90, 0);
if (textAbove == true)
{
textmesh.text = i.ToString();
}
if (textInFront == true)
{
textmesh.text = "Hello World";
}
}
}
}
What I tried so far :
First I tried to use only the Start. The rules should be if one of the flags textAbove or textInfront is true put the numbers/text above or in front. If both are true put both text and numbers the numbers above the text in front. if both false don't add anything if one is false one is true add only the one that true.
Same rules for the Update.
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
public class Generatenumbers : MonoBehaviour
{
public UnityEngine.GameObject[] objectsToNumber;
public UnityEngine.GameObject text;
public float yPadding;
public bool rotateNumbers = false;
public float rotationSpeed = 10f;
public bool textAbove = false;
public bool textInFront = false;
public bool textOnFaces = false;
private List<GameObject> newTexts = new List<GameObject>();
private MeshRenderer[] renderer;
private Vector3 newPosAbove;
private Vector3 newPosFront;
private List<Vector3> newPositions = new List<Vector3>();
private TextMesh textmesh;
private int numbers;
private List<GameObject> textInstances = new List<GameObject>();
private void Start()
{
renderer = new MeshRenderer[objectsToNumber.Length];
for (int i = 0; i < objectsToNumber.Length; i++)
{
textInstances.Add(Instantiate(text));
}
ChangeTextPosition();
}
private void Update()
{
if (rotateNumbers == true)
{
for (int i = 0; i < newTexts.Count; i++)
{
newTexts[i].transform.Rotate(Vector3.up, 10 * rotationSpeed * Time.deltaTime);
}
}
//ChangeTextPosition();
}
private void ChangeTextPosition()
{
for (int i = 0; i < objectsToNumber.Length; i++)
{
renderer[i] = textInstances[i].GetComponent<MeshRenderer>();
if (textAbove == true)
{
newPosAbove = new Vector3
(
objectsToNumber[i].transform.position.x,
((objectsToNumber[i].transform.position.y + renderer[i].bounds.extents.y) + yPadding),
objectsToNumber[i].transform.position.z
);
newPositions.Add(newPosAbove);
}
if (textInFront == true)
{
newPosFront = new Vector3
(
((objectsToNumber[i].transform.position.x + renderer[i].bounds.extents.x) + yPadding),
objectsToNumber[i].transform.position.y,
objectsToNumber[i].transform.position.z
);
newPositions.Add(newPosFront);
}
for(int x = 0; x < newPositions.Count; x++)
{
textInstances[i].transform.position = newPositions[x];
textInstances[i].transform.parent = transform;
textInstances[i].name = i.ToString();
textInstances[i].tag = "Number";
newTexts.Add(textInstances[i]);
textmesh = textInstances[i].GetComponent<TextMesh>();
textmesh.transform.localRotation = Quaternion.Euler(0, -90, 0);
if (textAbove == true)
{
textmesh.text = i.ToString();
}
}
}
}
}
But this is not working good even only for the Start()
It's showing only numbers and only the numbers 1,3,5 and it should show 6 numbers since there are 6 objects 3 numbers above and 3 in front. Each object should have a number above and in front.

Is there a way to destroy/replace a GameObject that is inside or part of a Prefab instance?

Exception
I'm getting exception in the editor:
InvalidOperationException: Destroying a GameObject inside a Prefab instance is not allowed.
Then I have to go manual to open the prefab and remove the gameobject from in there. But is there a way to do it by script automatic ?
This is my script to replace gameobjects with prefab.
The only change I did for the prefab is taking a gmeobject and added to it some colors and texture all the scripts and other stuff are the same.
I'm trying to replace all the doors in the scene with the prefab that is also a door just with some colors and texture.
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class PrefabReplace : EditorWindow
{
[SerializeField] private GameObject prefab;
private bool selectionChanged;
private string objectsToSearch = "";
private List<GameObject> foundObjects = new List<GameObject>();
private List<GameObject> duplicatedObjects = new List<GameObject>();
private bool searched = false;
private int count = 0;
private int countChilds = 0;
private bool countChildren = false;
private GUIStyle guiStyle = new GUIStyle(); //create a new variable
private Texture timage;
[MenuItem("Tools/Prefab Replace")]
static void CreateReplaceWithPrefab()
{
int width = 340;
int height = 300;
int x = (Screen.currentResolution.width - width) / 2;
int y = (Screen.currentResolution.height - height) / 2;
GetWindow<PrefabReplace>().position = new Rect(x, y, width, height);
}
private void OnGUI()
{
Texture oo = null;
Texture texture = (Texture)oo;
//EditorGUI.DrawTextureTransparent(new Rect(10, 10, 20, 20), timage);
guiStyle.fontSize = 20; //change the font size
Searching();
GUILayout.Space(50);
Replacing();
}
private void Searching()
{
GUI.Label(new Rect(10, 20, 150, 20), "Search by name", guiStyle);
objectsToSearch = GUI.TextField(new Rect(90, 60, 150, 20), objectsToSearch, 25);
if (objectsToSearch != "")
{
GUI.enabled = true;
}
else
{
GUI.enabled = false;
}
GUILayout.Space(40);
if (GUILayout.Button("Search"))
{
foundObjects = new List<GameObject>();
duplicatedObjects = new List<GameObject>();
countChildren = true;
countChilds = 0;
count = 0;
foreach (GameObject gameObj in GameObject.FindObjectsOfType<GameObject>())
{
if (gameObj.name == objectsToSearch)
{
count += 1;
foundObjects.Add(gameObj);
Transform[] childs = gameObj.GetComponentsInChildren<Transform>();
foreach (Transform go in childs)
{
foundObjects.Add(go.gameObject);
}
}
}
if (foundObjects.Count > 0)
{
searched = true;
}
else
{
searched = false;
}
}
GUI.enabled = true;
if (count > 0)
GUI.TextField(new Rect(90, 85, 60, 15), count.ToString(), 25);
if (foundObjects.Count > 0 && countChildren == true)
{
for (int i = 0; i < foundObjects.Count; i++)
{
if (foundObjects[i].transform.childCount > 0)
{
countChilds += foundObjects[i].transform.childCount;
}
}
countChildren = false;
}
GUI.enabled = true;
if (countChilds > 0)
GUI.TextField(new Rect(90, 105, 60, 15), countChilds.ToString(), 25);
GUILayout.Space(100);
if (foundObjects.Count > 0)
EditorGUILayout.LabelField("Test");
}
private void Replacing()
{
GUILayout.Space(20);
GUILayout.BeginVertical(GUI.skin.box);
GUILayout.Label("Replacing");
GUILayout.Space(20);
prefab = (GameObject)EditorGUILayout.ObjectField("Prefab", prefab, typeof(GameObject), false);
var selection = Selection.objects.OfType<GameObject>().ToList();
if (selectionChanged)
{
if (selection.Count == 0)
GUI.enabled = false;
for (var i = selection.Count - 1; i >= 0; --i)
{
var selectedObject = selection[i];
if (prefab != null && selection.Count > 0 &&
selectedObject.scene.name != null
&& prefab != PrefabUtility
.GetCorrespondingObjectFromSource(selectedObject))
{
GUI.enabled = true;
}
else
{
GUI.enabled = false;
}
}
}
else
{
GUI.enabled = false;
}
if (GUILayout.Button("Replace"))
{
InstantiatePrefab(selection);
selectionChanged = false;
}
GUILayout.Space(10);
GUI.enabled = true;
EditorGUILayout.LabelField("Selection count: " + Selection.objects.OfType<GameObject>().Count());
GUILayout.EndVertical();
}
private void OnInspectorUpdate()
{
Repaint();
}
private void OnSelectionChange()
{
selectionChanged = true;
}
private void InstantiatePrefab(List<GameObject> selection)
{
if (prefab != null && selection.Count > 0)
{
for (var i = selection.Count - 1; i >= 0; --i)
{
var selected = selection[i];
Component[] components = selected.GetComponents(typeof(MonoBehaviour));
if (components.Length == 0)
{
SceneManager.SetActiveScene(SceneManager.GetSceneByName(selected.scene.name));
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);
}
}
}
}
}
All the doors was replaced fine but the door in the screenshot is the only one that is part of a prefab instance.
PrefabUtility.LoadPrefabContents()
This method does the following:
Loads a Prefab Asset at a given path into an isolated Scene and returns the root GameObject of the Prefab.
You can use this to get the content of the Prefab and modify it directly instead of going through an instance of the Prefab. This is useful for batch operations.
This will let you modify the prefab in the manner you desire, however:
Once you have modified the Prefab you have to write it back using SaveAsPrefabAsset and then call UnloadPrefabContents to release the Prefab and isolated Scene from memory.
You have to save it back to the original prefab again and overwrite your changes.
I can do this, I solve this. Just destroy all prefab transforms. I have root _Canvas, and Prefab _Panel. And I delete like this:
foreach (Transform child in _Canvas.transform)
GameObject.Destroy(child.gameObject);
Childer objects is prefab objects.

How can I add/destroy new objects to existing formation?

In the manager:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FormationsManager : MonoBehaviour
{
public Transform squadMemeberPrefab;
public int numberOfSquadMembers = 20;
public int columns = 4;
public int gaps = 10;
public Formations formations;
private int numofmembers;
// Use this for initialization
void Start()
{
numofmembers = numberOfSquadMembers;
formations.Init(numberOfSquadMembers, columns, gaps);
GenerateSquad();
}
// Update is called once per frame
void Update()
{
if (numofmembers != numberOfSquadMembers)
{
GenerateSquad();
}
}
private void GenerateSquad()
{
Transform go = squadMemeberPrefab;
for (int i = 0; i < formations.newpositions.Count; i++)
{
go = Instantiate(squadMemeberPrefab);
go.position = formations.newpositions[i];
go.tag = "Squad Member";
go.transform.parent = gameObject.transform;
}
}
}
And the Formations script:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Formations : MonoBehaviour
{
public List<Vector3> newpositions;
private int numberOfSquadMembers;
private int columns;
private int gaps;
private List<Quaternion> quaternions;
private Vector3 FormationSquarePositionCalculation(int index)
{
float posX = (index % columns) * gaps;
float posY = (index / columns) * gaps;
return new Vector3(posX, posY);
}
private void FormationSquare()
{
newpositions = new List<Vector3>();
quaternions = new List<Quaternion>();
for (int i = 0; i < numberOfSquadMembers; i++)
{
Vector3 pos = FormationSquarePositionCalculation(i);
Vector3 position = new Vector3(transform.position.x + pos.x, 0, transform.position.y + pos.y);
newpositions.Add(position);
}
}
public void Init(int numberOfSquadMembers, int columns, int gaps)
{
this.numberOfSquadMembers = numberOfSquadMembers;
this.columns = columns;
this.gaps = gaps;
FormationSquare();
}
}
What I want to do is in the FormationsManager in the Update not only just calling GenerateSquad but to add the new once to the last/next position of the existing already formation.
void Update()
{
if (numofmembers != numberOfSquadMembers)
{
GenerateSquad();
}
}
If the value of numberOfSquadMembers is 20 first time and then I changed it to 21 add new object to the end of the formation and same if I change the value of numberOfSquadMembers for example from 20 to 19 or from 21 to 5 destroy the amount of objects from the end and keep the formation shape.
The soldiers the last line is on the right side.
So if I change the value to add more then add it to the right and if I change to less destroy from the right side. The most left line of soldiers is the first.
It is possible if you keep GameObject instances inside FormationsManager class, and then reuse them in GenerateSquad method.
In FormationsManager class, add and modify code as follows.
public GameObject squadMemeberPrefab;
List<GameObject> SquadMembers = new List<GameObject>();
void Update()
{
if (numofmembers != numberOfSquadMembers)
{
numofmembers = numberOfSquadMembers;
formations.Init(numberOfSquadMembers, columns, gaps);
GenerateSquad();
}
}
private void GenerateSquad()
{
Transform go = squadMemeberPrefab;
List<GameObject> newSquadMembers = new List<GameObject>();
int i = 0;
for (; i < formations.newpositions.Count; i++)
{
if (i < SquadMembers.Count)
go = SquadMembers[i];
else
{
go = Instantiate(squadMemeberPrefab);
newSquadMembers.Add(go);
}
go.position = formations.newpositions[i];
go.tag = "Squad Member";
go.transform.parent = gameObject.transform;
}
for (; i < SquadMembers.Count; i++)
Destroy(SquadMembers[i]);
SquadMembers = newSquadMembers;
}
However, I recommend you to consider GameObject Pool (Object Pool), which can thoroughly resolve such object recycle problem. For this purpose, you can use ClientScene.RegisterSpawnHandler. Go to this Unity Documentation page and search text "GameObject pool". You can see an example code there.

Categories

Resources