Instantiating GameObject in edit mode - c#

I have some graph of Node objects, which help represent some internal game state. I want to be able to spawn GameObjects from a prefab, initializing associated Node objects, using the GUI - i.e. someone could extend nodes in the example below in edit mode.
I am not familiar with how to do this in edit mode, instead of instantiating at runtime.
Is this possible, and if so, how would I get further than the below? I am familiar-ish with ScriptableObject but am not sure if it is relevant/necessary here.
[ExecuteInEditMode]
public class UGraph : MonoBehaviour
{
[SerializeField] GameObject nprefab;
[SerializeField] List<Node> nodes;
public void CreateNode(Transform transform)
{
GameObject go = Instantiate(nprefab, transform.position,
transform.rotation) as GameObject;
AddToGraph(go.GetComponent<Node>());
}
public void AddToGraph(Node node)
{
node.Graph = this;
nodes.Add(node);
}
...
Some thoughts around this taken from: https://gist.github.com/Problematic/a14aeb0638a09f378ad3

Lece pointed me towards the following page one extending the editor: https://docs.unity3d.com/Manual/editor-CustomEditors.html
This helped me discover how to use EditorWindow, and activate Instantiate via button press.
public class NodeCreator : EditorWindow
{
....
[MenuItem ("Window/Node Creator")]
static void ShowWindow()
{
EditorWindow.GetWindow(typeof(NodeCreator));
}
....
void OnGUI()
{
....
if (GUILayout.Button("Create"))
{
tempobj = Instantiate(obj) as GameObject;
...
}
....
}

Related

How to find GameObjects, and then use their position?

When my player dies and respawns, I'm trying to have my camera look at the newly-instantiated player. Here's the code
public class CameraController : MonoBehaviour
{
void Update()
{
var target = GameObject.FindGameObjectWithTag("Player");
Transform target;
transform.LookAt(target);
}
}
I'm trying to do it without a public "drag-and-drop" Transform variable.
I didn't have any problems with just my base player, but when I tried to respawn it with Instantiate(), the camera cannot detect the copies.
well remove that Transform target; and rather use
transform.LookAt(target.transform);
... in general for performance sake you should avoid using any of the Find variations in Update but do it Once in e.g. Start and reuse the reference
public class CameraController : MonoBehaviour
{
private Transform target;
private void Start()
{
target = GameObject.FindGameObjectWithTag("Player").transform;
}
void Update()
{
transform.LookAt(target);
}
}
The initial problem you're having is you are finding the GameObject of the player which is the type 'var' will be set to. After that, it's being overwritten when you set target to be of type Transform with a null value. Getting the Transform component of the Player and setting target to equal that will solve your issue.
Performance-wise however:
You want to avoid searching for gameobjects in an Update(), instead find what you're searching for one time and cache it. Finding a gameobject is an expensive action and having it inside of Update() will perform that action every cycle.
A more CPU-friendly way to handle it would look like:
Transform target;
public void findTarget(string targetToFind)
{
// potentially do some error handling here in case the gameobject hasn't loaded in yet
target = GameObject.FindGameObjectWithTag(targetToFind).transform;
}
void Update()
{
transform.LookAt(target);
}
How I would have this work is upon character respawn, call the findTarget method on the script passing it the player tag. It also makes the method more generalized so you can do additional camera shift tricks if you desire to have the camera move to a different target for a cinematic event or something similar.
I've found a solution. To any other people looking for something similar:
public class CameraController : MonoBehaviour
{
Transform target;
void Update()
{
GameObject target1 = GameObject.FindGameObjectWithTag("Player");
target = target1.transform;
transform.LookAt(target);
}
}

Finding a way for script to detect a child in a playerContainer game object

I'm currently making a 3D endless runner game with the choice to select different skins for the player. Everything's going smoothly until I come across a problem, in which I try to assign two collision game objects to each character prefab in the container that can detect the player's collision box when it collides with a powerup.
It only detects the first playerContainer's (Milo) 'Coin Detect' game object (eventhough it's been deactivated) and does not recognize the 'Coin Detect' collision game object in the Baddie playerContainer (which the player has chosen to play)
So my question is, how am I able to get the script to recognize the child that's active in the third playerContainer game object instead of it automatically detecting the first playerContainer game object's child?
From the Magnet powerup script I've made, the script detects the first playerContainer's 'Coin Detect' game object only. As shown in the attached picture below:
Here's my current script where the player is able to select their preferred characters.
using System.Collections.Generic;
using UnityEngine;
public class EnablePlayerSelector : MonoBehaviour
{
public GameObject[] players;
public int currPlayerCount;
void Start()
{
currPlayerCount = PlayerPrefs.GetInt("SelectedPlayer", 0);
foreach (GameObject player in players)
player.SetActive(false);
players[currPlayerCount].SetActive(true);
}
}
and here's the script where I deactivate the 'Coin Detect' collision game Object and activate it when the player collides with the powerup.
using System.Collections.Generic;
using UnityEngine;
public class Magnet : MonoBehaviour
{
public GameObject coinDetect;
void Start()
{
coinDetect = GameObject.FindGameObjectWithTag("CoinDetect");
coinDetect.SetActive(false);
Debug.Log("False");
}
private void OnTriggerEnter(Collider other)
{
if(other.gameObject.tag == "Player")
{
StartCoroutine(coinActivate());
Destroy(transform.GetChild(0).gameObject);
}
}
IEnumerator coinActivate()
{
Debug.Log("Hi");
coinDetect.SetActive(true);
yield return new WaitForSeconds(5f);
coinDetect.SetActive(false);
}
}
Well your issue is rather that you are doing GameObject.FindGameObjectWithTag("CoinDetect") which will return whatever is the first encountered CoinDetect in your scene in this moment. So unless EnablePlayerSelector.Start is executed before the Magnet.Start it will always be the first one since they are still all active by then.
You could probably already solve your issue by following the general thumb-rule:
Do things where you do not depend on other components already in Awake
Do things where you o depend on other components in Start
This way you already cover most of the use cases and are sure that components are self-initialized (Awake) before anything is accessing them in Start.
So simply change the EnablePlayerSelector.Start into Awake and you should be fine.
In very specific cases you might still have to adjust the Script Execution Order or use events.
Instead of doing this at all though I would rather in the moment of the collision request the current one from the PlayerContainer directly:
// put this on each player root object
public class Player : MonoBehaviour
{
// reference these via the Inspector
[SerializeField] private GameObject coinDetector;
[SerializeField] private GameObject playerrange;
// ... and in general any information bout this Player that is relevant for other scripts
// Readonly accessor property
public GameObject CoinDetector => coinDetector;
}
and then rather have
public class EnablePlayerSelector : MonoBehaviour
{
[SerializeField] private Player[] players;
[SerializeField] private int currPlayerCount;
public Player CurrentPlayer => players[currPlayerCount];
// In general do things where you don't depend on other scripts already in Awake
// This way you could actually already solve your issue by keeping things where you do
// depend on others in Start
private void Awake()
{
currPlayerCount = PlayerPrefs.GetInt("SelectedPlayer", 0);
for(var i = 0; i < players.Length; i++)
{
players[i].gameObject.SetActive(i == currPlayerCount);
}
}
}
and then finally do e.g.
public class Magnet : MonoBehaviour
{
public float effectDuration = 5f;
private IEnumerator OnTriggerEnter(Collider other)
{
var playerSelector = other.GetComponentInParent<EnablePlayerSelector>();
if(!playerSelector) yield break;
Debug.Log("Hi");
Destroy(transform.GetChild(0).gameObject);
var coinDetect = playerSelector.CurrentPlayer.CoinDetector;
coinDetect.SetActive(true);
yield return new WaitForSeconds(effectDuration);
coinDetect.SetActive(false);
}
}
or alternatively if the other in this case refers to the object with the Player component anyway you could also directly do
public class Magnet : MonoBehaviour
{
public float effectDuration = 5f;
private IEnumerator OnTriggerEnter(Collider other)
{
if(!other.TryGetComponent<Player>(out var player)) yield break;
Debug.Log("Hi");
Destroy(transform.GetChild(0).gameObject);
var coinDetect = player.CoinDetector;
coinDetect.SetActive(true);
yield return new WaitForSeconds(effectDuration);
coinDetect.SetActive(false);
}
}

Check and create lost GameObjects from code (not during gameplay)

Ok. When I open my project if someone GameObject (or all of them) is not found then it should be created automatically.
For example, I have SceneBuilder as GameObject and it contains all necessary scripts: ObjectsGenerator.cs, PlayerData.cs etc. And if somehow SceneBuilder disappears it must be recover from code. How can I do that?
p.s.: I found only two ways:
creating it from menu [MenuItem("MyTools/CreateGameObjects")], but it not obviously for support.
creating it through [CustomEditor(typeof(SomeOjectScript))] , but it must be already exists in scene (I guess a situation where the scene is completely empty)
p.s.: sorry if the question has already been and I have not found it
updated.
thanks to all. solved something like that
[InitializeOnLoad]
public class Checker : MonoBehaviour
{
static Checker()
{
CheckAndCreateObj();
EditorApplication.hierarchyChanged += hierarchyChanged;
}
private static void hierarchyChanged()
{
CheckAndCreateObj();
}
private static void CheckAndCreateObj()
{
string objName = "OBJECTNAME";
GameObject go = GameObject.Find(objName);
if (go == null)
{
Instantiate(new GameObject(objName));
}
}
}
https://docs.unity3d.com/Manual/RunningEditorCodeOnLaunch.html
You can check it dynamicly in one of your other scripts. You can call it from OnEnable or Start functions. Your MonoBehaviour must contain tag [ExecuteInEditMode] It could be something like this:
[ExecuteInEditMode]
public class YourMonoBehaviour: MonoBehaviour
{
void OnEnable()
{
var myObject = GameObject.Find("GAMEOBJECT_NAME");
if (myObject == null)
{
//Create new GameObject here and add your Component
}
}
}
If you need save some asset links or data in your gameobject with serialization, you can create prefab, cache it and create your object from prefab:
MyObjectType prefabTemplate;
void OnEnable()
{
if (GameObject.Find("GAMEOBJECT_NAME") == null)
{
GameObject.Instantiate(prefabTemplate);
}
}

Assign gameObject of a script via another script?

How do I assign a gameObject of one script through another script's gameObject? For example;
Script_1
public class Script_1 : MonoBehaviour
{
public OVRScript OVR;
}
Script_2
public class Script_2 : MonoBehaviour
{
private Script_1 src_1;
public GameObject Front;
void Start()
{
src_1 = (Script_1) GameObject.FindObjectOfType(typeof(Script_1));
src_1.GetComponent<OVRScript >().OVR = Front //I am facing problem here
}
}
Both GameObjects "OVR" and "Front" contain the OVRScript
src_1.GetComponent<OVRScript>().OVR = Front.GetComponent<OVRScript>().OVR;
I don't know or see the OVRScript class but isn't OVR rather a member of Script_1?
And then you would want to use GetComponent on the Front in order to get a component attached to it.
// If possible rather drag your Script_1 in here directly via the Inspector
[SeializeField] private Script_1 src_1;
void Start()
{
// Now I would use find only as fallback
if(!scr_1) src_1 = GameObject.FindObjectOfType<Script_1>();
// then you want to assign the OVR field of the 'src_1' of type 'Script_1'
// and not use 'src_1.GetComponent<OVRScript>()' which would return
// the reference of an 'OVRScript' component attached to the same GameObject as the Script_1
//
// And you want to fill it with the reference of an 'OVRScript' attached to 'Front'
src_1.OVR = Front.GetComponent<OVRScript>();
}
(see [SerializeField])
It would be even better if you directly define
public OVRScript Front;
now if you drag in a GameObject it a) is checked if this GameObject actually has a OVRScript attached, otherwise you can't drop it and b) instead of the GameObject reference already the OVRScript reference is serialized and stored so there is no need for GetComponent anymore:
[SeializeField] private Script_1 src_1;
public OVRScript Front;
void Start()
{
// Now I would use find only as fallback
if(!scr_1) src_1 = GameObject.FindObjectOfType<Script_1>();
src_1.OVR = Front;
}

Unity3d Can't use equip prefab.

Trying to play around with unity so I made a simple player controller, and a weapon controller.
I drag my Weapon Controller onto my player, and I try to assign a Prefab as the starting Gun, but for some reason it doesn't let me do it. I can't drag the Prefab to the startingGun in my script.
My weapon controller looks like this:
namespace Assets.Scripts.Weapon
{
public class WeaponController : MonoBehaviour
{
public Transform WeaponHold;
public Gun startingGun;
private Gun EquippedGun;
void Start()
{
if (startingGun != null)
{
EquipWeapon(startingGun);
}
}
public void EquipWeapon(Gun gunToEquip)
{
if (gunToEquip != null)
Destroy(EquippedGun.gameObject);
EquippedGun = Instantiate(gunToEquip, WeaponHold.position, WeaponHold.rotation) as Gun;
if (EquippedGun != null)
EquippedGun.transform.parent = WeaponHold;
}
}
}
The problem is you're trying to assign a prefab to a variable with the type GameObject - instead you need to use the following code then you should be able to add the prefab in the inspector:
public Object startingGun;

Categories