I am trying to make an [ExecuteInEditMode] script spawn game objects (linked to the same prefab) at specific positions right in the Editor so that I can quickly create different hexagon tile maps by just triggering booleans in the inspector. However, the Resources.Load() method cannot not find the prefab even though the path is correct and so I get the following error:
NullReferenceException: Object reference not set to an instance of an object.
Here is the code:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
[ExecuteInEditMode]
public class PositionChecker : MonoBehaviour
{
[SerializeField] float tileGap = 1.5f;
[SerializeField] GameObject tilePrefab; // alternatively tried dragging the prefab in the field in the inspector - it worked
[SerializeField] bool tileUpLeft;
GameObject tilesParent;
private void Awake()
{
tilesParent = GameObject.Find("All Tiles");
tilePrefab = Resources.Load("Assets/Prefabs/Tile.prefab") as GameObject;
}
// Update is called once per frame
void Update()
{
CheckForCreateTile();
}
private void CheckForCreateTile()
{
if (tileUpLeft)
{
tileUpLeft = false;
InstantiateTilePrefab(new Vector3(transform.position.x - 0.6f * tileGap, transform.position.y, transform.position.z - tileGap));
}
}
private void InstantiateTilePrefab(Vector3 vector3)
{
GameObject newTile = PrefabUtility.InstantiatePrefab(tilePrefab, tilesParent.transform) as GameObject;
Debug.Log(tilePrefab); // null
Debug.Log(tilesParent); // ok
Debug.Log(newTile); // Null
newTile.transform.position = vector3;
}
}
If I drag the prefab onto the serialized field of each created tile manually in the inspector instead of trying to load it, everything works fine.
The asset has to be in a "Resources" folder. So to solve your problem you can put "Tile.prefab" into the folder "Assets/Resources" and use the relative path: Resources.Load("Tile.prefab");
https://docs.unity3d.com/ScriptReference/Resources.Load.html
The file-path string automatically starts with "Resources/". So you shouldn't type that in. Also I don't think unity likes the file type in the string so don't type that in either. This works for me:
GameObject tilePrefab;
tilePrefab = Resources.Load<GameObject>("Prefabs/Tile");
GameObject tile = Instantiate(tilePrefab, Vector3.zero, Quaternion.identity);
Related
I am working on the platforming part of my game Maltrov. I have a script (below) that is supposed to duplicate a platform 5 times with each clone being in a new place. Whenever I attach the script to my platform and assign it as the parent as well as the Gameobject variables the project crashes. Is there a way to make the code work?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Duplicateplatform : MonoBehaviour
{
public GameObject newGameObject;
public Transform parent;
Vector3 newPosition;
public Quaternion newRotation;
public float xValue = 20f;
public float yValue = 20f;
// Start is called before the first frame update
void Start()
{
for (int i = 0; i < 5; i++)
{
xValue += 20f;
yValue += 20f;
duplicateObject(xValue, yValue);
}
}
public void duplicateObject(float xValue,float yValue)
{
// create (duplicate, in a new position, at a new rotation to the parent)
newPosition = new Vector3(xValue, yValue, 0);
Instantiate(newGameObject,newPosition,newRotation,parent);
}
}
I tried attaching the platform to an empty Gameobject, and then the script for the platform to the empty GameObject, but the project still crashed.
I tried your code and it works fine in mine.
Therefore the problem lies elsewhere.
How about checking your inspector?
If you're getting null errors, check if you plugged in your newGameObject and Parent.
If your editor froze, it's possibly because you added the game object as your instantiate object, and it's in an infinite loop.
Hi!
After the discussion with Ruzihm in the comments. I've now created a simple version of my game to better ask the question I'm having.
The question now is, since I'm not able to manually create a connection to the testObject field in the inspector. How do I now tell Unity to use my instantiated objects while the game is running?
And is this a good solution for a RTS game that may have 100s of Units active at a time? The end goal here is to apply this force to a radius around the cursor. Which I was thinking of using Physics.OverlapSphere
Here's the minimal scenario of what I have:
New Unity scene
Attached the InputManager to the main camera.
Created a capsule and a plane.
Added ApplyForce to the Capsule
Created a prefab from the capsule and deleted it from the scene.
In the InputManager I added the ability to press space to Instantiate a capsule with the ApplyForce script attached..
Drag the capsule prefab to the InputManager "objectToGenerate"
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace GL.RTS.Mites
{
public class InputManager : MonoBehaviour
{
public GameObject testObject;
public ApplyForce onSpawnTest;
public GameObject objectToGenerate;
void Start()
{
onSpawnTest = testObject.GetComponent<ApplyForce>();
}
void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
Instantiate(objectToGenerate);
}
if (Input.GetMouseButton(0))
{
onSpawnTest.PushForward();
}
}
}
}
The ApplyForce script that I attach to the Capsule:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace GL.RTS.Mites
{
public class ApplyForce : MonoBehaviour
{
public float moveSpeed;
Rigidbody rb;
void Start()
{
rb = GetComponent<Rigidbody>();
Debug.Log("A Mite has spawned!");
}
public void PushForward()
{
rb.AddRelativeForce(Vector3.up * moveSpeed * Time.deltaTime);
Debug.Log("A force of: " + moveSpeed + " is being added.");
}
}
}
Well, you are creating your new instances of your object, but your input manager immediately forgets about them (note that you do nothing with the return value). The InputManager only knows about the ApplyForce that was created in its Start (and then interacts with it depending on mouse input) and your ApplyForce script knows nothing about any InputManager. So, it should come as no surprise that only the first instance reacts to the mouse input.
So, something has to be done to your InputManager and/or your ApplyForce. Your InputManager could remember the instances it creates (which isn't enough, because what if for example, a map trigger creates new player controllable units) or it could go looking for units each time.
Your ApplyForce could register with the InputManager when they are created, but then you would need to loop through the units and find out which ones are under the mouse, anyway.
Since you only want to select ones based on what is near or under your cursor and only when input occurs and not like every frame, I would go with the simplest approach, just letting your InputManager find the units when it needs them. Something like below, explanation in comments:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace GL.RTS.Mites
{
public class InputManager : MonoBehaviour
{
public GameObject testObject;
public ApplyForce onSpawnTest;
public GameObject objectToGenerate;
private Camera mainCam;
// which layers to consider for cursor detection
[SerializeField] LayerMask cursorLayerMask;
// how big for cursor detection
[SerializeField] float cursorRadius;
void Awake()
{
// cache main camera
mainCam = Camera.main;
}
void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
Instantiate(objectToGenerate);
}
if (Input.GetMouseButton(0))
{
Collider[] colls = FindCollidersUnderCursor();
// check each collider for an applyforce and use it if present
foreach( Collider coll in colls)
{
ApplyForce af = coll.GetComponent<ApplyForce>();
if (af != null)
{
af.PushForward();
}
}
}
}
Collider[] FindCollidersUnderCursor()
{
// find ray represented by cursor position on screen
// and find where it intersects with ground
// This technique is great for if your camera can change
// angle or distance from the playing field.
// It uses mathematical rays and plane, no physics
// calculations needed for this step. Very performant.
Ray cursorRay = mainCam.ScreenPointToRay(Input.mousePosition);
Plane groundPlane = new Plane(Vector3.up, Vector3.zero);
if (groundPlane.Raycast(cursorRay, out float cursorDist))
{
Vector3 worldPos = cursorRay.GetPoint(cursorDist);
// Check for triggers inside sphere that match layer mask
return Physics.OverlapSphere(worldPos, cursorRadius,
cursorLayerMask.value, QueryTriggerInteraction.Collide);
}
// if doesn't intersect with ground, return nothing
return new Collider[0];
}
}
}
Of course, this will require that every unit you're interested in manipulating has a trigger collider.
I have a simple script that should grab a GameObject's transform position and rotation then move the GameObject back to that position when it's hit by a Raycast. (Objects get moved around as the player bumps into them).
Its working fine for the objects that already have the script attached but when I apply the script to a new GameObject, it resets the transform's values to (0,0,0),(0,0,0) in the scene editor.
I'm sure there's a smarter way to do this.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TransformReset : MonoBehaviour
{
private Transform LocalTransform;
private Vector3 InitialPos;
private Quaternion InitialRot;
private Vector3 CurrentPos;
private Quaternion CurrentRot;
void Start()
{
InitialPos = transform.position;
InitialRot = transform.rotation;
}
public void Reset()
{
transform.position = InitialPos;
transform.rotation = InitialRot;
Debug.Log("resetting position of " + gameObject.name);
}
}
that "public void Reset()" part is called by my raycasting script. But I think the problem is that its running in the scene editor before ever getting a useful "InitialPos" value.
Appreciate any help and advice.
Reset is a MonoBehaviour Message that the engine calls under certain conditions. From the documentation, emphasis mine:
Reset is called when the user hits the Reset button in the Inspector's
context menu or when adding the component the first time. This
function is only called in editor mode. Reset is most commonly used to
give good default values in the Inspector.
Basically, your naming of the method is colliding with built in behaviour it shouldn't have anything to do with. If you change the name of your method to something else, it should work fine:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TransformReset : MonoBehaviour
{
private Transform LocalTransform;
private Vector3 InitialPos;
private Quaternion InitialRot;
private Vector3 CurrentPos;
private Quaternion CurrentRot;
void Start()
{
InitialPos = transform.position;
InitialRot = transform.rotation;
}
public void ResetTransform()
{
transform.position = InitialPos;
transform.rotation = InitialRot;
Debug.Log("resetting position of " + gameObject.name);
}
}
And then in the other class:
transformResetComponent.ResetTransform();
I'm having an issue where I can't disable a script from the other script - they are both public and within the same package (I think).
Here is my code for the script I'm trying to disable from:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEditor;
#endif
using RTS;
public class PauseMenu : MonoBehaviour {
Canvas canvas;
private Player player;
public Button Button2;
void Start()
{
Debug.Log ("asdf");
player = transform.root.GetComponent< Player >();
canvas = GetComponent<Canvas>();
canvas.enabled = false;
ResourceManager.MenuOpen = false;
Button2.GetComponent<Button>().onClick.AddListener(() => { Resume();});
if(player) player.GetComponent< UserInput >().enabled = false;
}
And the code for the other script:
//sets up what resources we are using
using UnityEngine;
using System.Collections;
using RTS;
public class UserInput : MonoBehaviour {
//sets up a private variable for only this class - our player
private Player player;
// Use this for initialization
void Start () {
//this goes to the root of the player ie the object player and allows us to
player = transform.root.GetComponent< Player > ();
}//end Start()
So the part that is not working is:
if(player) player.GetComponent< UserInput >().enabled = false;
And the code runs and then causes the runtime error:
NullReferenceException: Object reference not set to an instance of an object
PauseMenu.Pause () (at Assets/Menu/PauseMenu.cs:40)
PauseMenu.Update () (at Assets/Menu/PauseMenu.cs:29)
Here is a picture showing my scene hierarchy and components:
The issue here is that you try to execute transform.root.GetComponent< Player >(); from within PauseMenu, which is on the "Canvas" object.
The problem with that is that the topmost transform in the hierarchy of your "Canvas" object (which is what transform.root returns) is, well, the transform of the "Canvas" object - which is in no way related to the UserInput script you are trying to access. For this script to actually work, you would need the transform of your "Player" object, which is the object that actually has the UserInput script.
My suggestion is to eliminate the need to run GetComponent() at all - create a public UserInput variable in your PauseMenu class, then (while selecting your "Canvas") in the editor, drag the "Player" object into that new field. This will cause the UserInput script of your "Player" object to be accessible within the PauseMenu.
So your PauseMenu script might look like:
public class PauseMenu : MonoBehaviour {
Canvas canvas;
public UserInput playerInput; // Drag the Player object into this field in the editor
public Button Button2;
void Start()
{
Debug.Log ("asdf");
canvas = GetComponent<Canvas>();
canvas.enabled = false;
ResourceManager.MenuOpen = false;
Button2.GetComponent<Button>().onClick.AddListener(() => { Resume();});
playerInput.enabled = false;
}
}
Hope this helps! Let me know if you have any questions.
(An alternative is to use GameObject.Find("Player") to get GameObject of "Player". This needs a bit more code but doesn't use the editor.)
I would say your player = transform.root.GetComponent< Player >(); arrives null.
So you are trying to disable something that doesnt exist.
Enter debug mode and see if your player is null or not.
I'm looking to create a piece of terrain in unity using only a script (c# preferably) to do this rather than the menu options on the editor. So far I only have this code below, but I don't know what to do next to get it to appear on the scene, can anyone help?
Thank you
using UnityEngine;
using System.Collections;
public class terraintest : MonoBehaviour {
// Use this for initialization
void Start () {
GameObject terrain = new GameObject();
TerrainData _terraindata = new TerrainData();
terrain = Terrain.CreateTerrainGameObject(_terraindata);
}
// Update is called once per frame
void Update () {
}
}
Simply adding :
Vector3 position = ... //the ingame position you want your terrain at
GameObject ingameTerrainGameObject = Instantiate(terrain, position, Quaternion.identity);
should make the terrain appear ingame. The Instantiate method returns a reference to the gameobject spawned ingame, so if you later want to access it, you can use that reference.