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;
Related
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);
}
}
I want to create a reference to an instance of a game object based on if a raycast hits a specific game object. In this case, its called "resource". Once I have click the gameobject I want to create an object of type ResourceSource that holds all variables and methods of that specific gameObject.
E.g i click the gameobject. A varaible is created
ResourceSource resource = hit.collider.gameObject.GetComponent<ResourceSource>();
Obviously, I cant just create a new object as that will just be a copy so if I make any changes to the gameobject the original won't be affected.
ResourceSource class
public class ResourceSource : MonoBehaviour
{
public ResourceType type;
public int quantity;
public UnityEvent onQuantityChange;
Cooldown cooldown;
public Vector3 GetPositon()
{
return gameObject.transform.position;
}
}
The method i want to create the reference
void CheckIfResource(RaycastHit hit)
{
if (hit.collider.gameObject.tag == "Resource")
{
//Want to create something like this
ResourceSource resourcePosition = hit.collider.gameObject.GetComponent<ResourceSource>();
}
}
Still somewhat new to unity so any help is appreciated :)
ResourceSource resource;
resource = hit.collider.gameObject.GetComponent<ResourceSource>();
I have recently been making an inventory system, but I can't manage to get the UI to show up with Input.GetKeyDown like I've wanted to. Here is my code:
using UnityEngine;
public class Inventory : MonoBehaviour
{
public GameObject invUI;
public GameObject soulFragmentPanel;
private void Update()
{
if(Input.GetKeyDown(KeyCode.E))
{
invUI.SetActive(true);
soulFragmentPanel.SetActive(false);
}
if (Input.GetKeyUp(KeyCode.E))
{
invUI.SetActive(false);
soulFragmentPanel.SetActive(true);
}
if (invUI.activeSelf)
{
soulFragmentPanel.SetActive(false);
}
}
}
Can you check in unity? you turn off that object which contains the Inventory script.
you have to set the invUI and soulFragmentPanel in single canvas and take Inventory script in that canvas.
so your code is working properly.
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;
...
}
....
}
When i pick a item in my scene, it calls the AddItem() method, and this method would add the item to an List and destroy the gameObject from the scene, but when i try to acess the gameobject in the list this error appears.
"MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it."
public class Inventory : MonoBehaviour
{
public List<GameObject> itemsGOInInventory = new List<GameObject>();
public void AddItem(GameObject itemToBeAdded)
{
itemsGOInInventory.Add(itemToBeAdded);
Destroy(itemToBeAdded);
}
public void FindItem(string name)
{
foreach (GameObject item in itemsGOInInventory)
{
if (item.GetComponent<Item>().itemName == "pistol")
{
Instantiate(item.GetComponent<Item>().itemObject, this.transform);
}
}
}
}
Once you destroy the gameobject, its gone.
What you can do is hold a reference to that object and then store it somewhere and make it invisible. Its called object pooling.
Unity has a tutorial on this here: https://unity3d.com/learn/tutorials/topics/scripting/object-pooling
You need to set the gameobject SetActive to false or use other means to make it invisible.
https://docs.unity3d.com/ScriptReference/GameObject.SetActive.html
You already have the list of objects, so instead of Destroy(), use SetActive(false);
itemToBeAdded.SetActive(false);
Hope this helps.
You say that you destroy GameObject once its picked up. Once destroyed, it doesn't exist and all references to the GameObject are null. It is a bad idea to keep list of gameobjects in your case. Instead, you should have a model class for inventory items.
public class InventoryItem
{
string name;
string description;
string spritePath;
// other variables
}
You should create a separate class to represent the InventoryItem in the scene. You could have something like this on your gameobjects:
public class InventoryItemView : MonoBehaviour
{
public InventoryItem item;
// ...
}
Now put the item script on 'items' in your scene. Whenever you pick an item, get the InventoryItem script from the gameobject and add it to the list of InventoryItems in your Inventory. Then destroy the gameobject. Now even when your gameobject is destroyed, you have your InventoryItem object.
public class Inventory : MonoBehaviour
{
public List<InventoryItem> itemsGOInInventory = new List<InventoryItem>();
public void AddItem(GameObject itemToBeAdded)
{
itemsGOInInventory.Add(itemToBeAdded.GetComponent<InventoryItemView>().item);
Destroy(itemToBeAdded);
}
//...
}
You might want to check for whether the GameObject has the InventoryItemView script on it.
Ideally your inventory class should not be MonoBehaviour, a serializable class would have sufficed, but that is off topic.