GetKeyDown sends multiple outputs - c#

specifically it sends as many outputs as there are objects with the code in them that allows them to be interacted with, even if I press E when not looking at anything. I wanted to make an inventory system, but this causes all objects that have this code to be interacted with. I included all the scripts that im using for this system if that can help. I genuinely don't know what I did wrong
the interactor code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using System;
public class Interactable : MonoBehaviour
{
public LayerMask interactableLayerMask;
//ITEM VARIABLE
public Item item;
//PICK UP RADIUS
public float radius = 4f;
public void Interact()
{
Debug.Log("Interacted with " + transform.name);
PickUp();
}
//PICK UP INTERACTION
void PickUp()
{
Debug.Log("Picking up " + item.name);
bool wasPickedUp = Inventory.instance.Add(item);
if (wasPickedUp)
Destroy(gameObject);
}
//INTERACTION
void Update()
{
if (Input.GetKeyDown(KeyCode.E))
{
Debug.Log("Added item -----------------------------------------");
RaycastHit hit;
if (Physics.Raycast(Camera.main.transform.position, Camera.main.transform.forward, out hit, radius))
{
Interactable interactable = hit.collider.GetComponent<Interactable>();
if (interactable != null)
{
Interact();
}
} else
{
Debug.Log("Nothing");
}
}
}
}
the Inventory code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Inventory : MonoBehaviour
{
#region Singleton
public static Inventory instance;
void Awake()
{
if (instance != null)
{
Debug.LogWarning("More than one instance of Inventory found!");
return;
}
instance = this;
}
#endregion
public delegate void OnItemChanged();
public OnItemChanged onItemChangedCallback;
public int space = 20;
public List<Item> items = new List<Item>();
public bool Add (Item item)
{
if (!item.isDefaultItem)
{
if (items.Count >= space)
{
Debug.Log("Note enough space in inventory");
return false;
}
else
{
items.Add(item);
if (onItemChangedCallback != null)
onItemChangedCallback.Invoke();
}
}
return true;
}
public void Remove(Item item)
{
items.Remove(item);
if (onItemChangedCallback != null)
onItemChangedCallback.Invoke();
}
}
Item code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "New Item", menuName = "Inventory/Item")]
public class Item : ScriptableObject
{
new public string name = "New Item";
public Sprite icon = null;
public bool isDefaultItem = false;
}

To my understanding, your code goes into the Input.GetKeyDown(KeyCode.E) for every object in the scene, but it does not add it to the inventory.
That is because you use the Update function in the Interactable class, which are your objects. Instead, try moving the exact same block of code into your Inventory class. Make sure you make your Interact method public, so you can call it.

Related

UI element keeps upscaling itself upon game start

Pretty new to developing so please excuse any obvious errors.
I'm currently trying to build an inventory system for my game but the item slots keep upscaling themselves upon game start.
The prefab starts with a scaling of: x=1, y=1, z=1;
On game start this inflates to: x=53.1, y=53.1, z=53.1;
Here is the scale of the prefab:HERE
And here is the scale of the prefab upon game start: HERE
Here is the script to update the items:
using UnityEngine;
using UnityEngine.UI;
public class UIItem : MonoBehaviour
{
public Item item;
private Image spriteImage;
private void Awake()
{
spriteImage = GetComponent<Image>();
UpdateItem(null);
}
public void UpdateItem(Item item)
{
this.item = item;
if (this.item != null)
{
spriteImage.color = Color.white;
spriteImage.sprite = this.item.icon;
}
else
{
spriteImage.color = Color.clear;
}
}
}
And here is the script used to update the slots and to add the items to the inventory:
using System.Collections.Generic;
using UnityEngine;
public class UIInventory : MonoBehaviour
{
public List<UIItem> uIItems = new List<UIItem>();
public GameObject slotPrefab;
public Transform slotPanel;
public int numberOfSlots = 16;
private void Awake()
{
for (int i = 0; i < numberOfSlots; i++)
{
GameObject instance = Instantiate(slotPrefab);
instance.transform.SetParent(slotPanel);
uIItems.Add(instance.GetComponentInChildren<UIItem>());
}
}
public void UpdateSlot(int slot, Item item)
{
uIItems[slot].UpdateItem(item);
}
public void AddNewItem(Item item)
{
UpdateSlot(uIItems.FindIndex(i => i.item == null), item);
}
public void RemoveItem(Item item)
{
UpdateSlot(uIItems.FindIndex(i => i.item == item), null);
}
}
Here is the inventory script itself:
using System.Collections.Generic;
using UnityEngine;
public class Inventory : MonoBehaviour
{
public List<Item> characterItems = new List<Item>();
public ItemDatabase itemDatabase;
public UIInventory inventoryUI;
private void Start()
{
GiveItem(1);
GiveItem("Clue 5");
RemoveItem(1);
}
public void GiveItem(int id)
{
Item itemToAdd = itemDatabase.GetItem(id);
characterItems.Add(itemToAdd);
inventoryUI.AddNewItem(itemToAdd);
Debug.Log("Added item:" + itemToAdd.name);
}
public void GiveItem(string itemName)
{
Item itemToAdd = itemDatabase.GetItem(itemName);
characterItems.Add(itemToAdd);
inventoryUI.AddNewItem(itemToAdd);
Debug.Log("Added item:" + itemToAdd.name);
}
public Item CheckForItem(int id)
{
return characterItems.Find(item => item.id == id);
}
public void RemoveItem(int id)
{
Item item = CheckForItem(id);
if (item != null)
{
characterItems.Remove(item);
inventoryUI.RemoveItem(item);
Debug.Log("Item removed:" + item.name);
}
}
}
I've tinkered on this for the past four hours and have nothing that seems to work. I've tried the Aspect Ratio Fitter and it only made it more. I can try to explain further if needed.
If anyone could help, it would be greatly appreciated.

Implement stacking to my inventory system in Unity

I'm currently working on a game, and now I've just finished the inventory system, which is highly inspired by the one Brackeys made a while back. Right now the player can pick up items, which will go into the inventory's list and be displayed on some UI. After having finished this, I tried to make items stack together, however I was never able to find a working solution. I don't want anything fancy, I just want every item to be able to stack infinitely. If anyone could help me out I'd greatly appreciate it!
Sorry for putting so much code in here, I'm not sure what I should and shouldn't add.
My "Items" script:
using UnityEngine;
[CreateAssetMenu(fileName = "New item", menuName = "Inventory/Items")]
public class Item : ScriptableObject
{
new public string name = "New item";
public Sprite icon = null;
public bool itemDefaut = false;
public virtual void Use()
{
Debug.Log("Using " + name);
}
}
My "Inventory" script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Inventory : MonoBehaviour
{
#region Singleton
public static Inventory instance;
void Awake()
{
if (instance != null)
{
Debug.LogWarning("More than one inventory instance found!");
return;
}
instance = this;
}
#endregion
public delegate void OnItemChanged();
public OnItemChanged onItemChangedCallback;
public List<Item> items = new List<Item>();
public void Add(Item item)
{
items.Add(item);
if (onItemChangedCallback != null)
{
onItemChangedCallback.Invoke();
}
}
public void Remove(Item item)
{
items.Remove(item);
if (onItemChangedCallback != null)
{
onItemChangedCallback.Invoke();
}
}
}
My "InventorySlot" script:
using UnityEngine;
using UnityEngine.UI;
public class InventorySlot : MonoBehaviour
{
Item item;
public Image icon;
public void AddItem(Item newItem)
{
item = newItem;
icon.sprite = item.icon;
icon.enabled = true;
}
public void RemoveItem()
{
item = null;
icon.sprite = null;
icon.enabled = false;
}
public void UseItem()
{
if (item != null)
{
item.Use();
}
}
}
My "InventoryUI" script:
using UnityEngine;
public class InventoryUI : MonoBehaviour
{
public Transform itemsParent;
public GameObject inventoryUI;
Inventory inventory;
InventorySlot[] slots;
// Start is called before the first frame update
void Start()
{
inventory = Inventory.instance;
inventory.onItemChangedCallback += UpdateUI;
slots = itemsParent.GetComponentsInChildren<InventorySlot>();
}
// Update is called once per frame
void Update()
{
if (Input.GetButtonDown("Inventory"))
{
inventoryUI.SetActive(!inventoryUI.activeSelf);
}
if (inventoryUI.activeSelf)
{
Time.timeScale = 0;
}
else
{
Time.timeScale = 1;
}
}
void UpdateUI()
{
for (int i = 0; i < slots.Length; i++)
{
if (i < inventory.items.Count)
{
slots[i].AddItem(inventory.items[i]);
}
else
{
slots[i].RemoveItem();
}
}
}
}
My "ItemPickup" script:
using UnityEngine;
public class ItemPickup : Interactable
{
public Item item;
public override void Interact()
{
base.Interact();
PickUp();
}
void PickUp()
{
Debug.Log("Picking up " + item.name);
Inventory.instance.Add(item);
Destroy(gameObject);
}
}
And my "Interactable" script:
using UnityEngine;
public class Interactable : MonoBehaviour
{
public float radius = 3f;
[SerializeField] Player player;
bool interacted = false;
void Start()
{
interacted = false;
}
public virtual void Interact()
{
//This method is meant to be overwritten
Debug.Log("Interacting with " + transform.name);
}
void Update()
{
if (!interacted)
{
float distance = Vector3.Distance(player.transform.position, transform.position);
if (distance <= radius)
{
Interact();
interacted = true;
}
}
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, radius);
}
}
Finally, here's what my hierarchy looks like for my inventory (just replace "inventaire" by "inventory" and don't mind the item names):
If I understand you correctly it sounds like you basically want a counter for the slot so you can add multiple of the Item instance.
You could probably do something like
public class InventorySlot : MonoBehaviour
{
// Have a counter for the amount of items added
int _amount;
Item item;
public Image icon;
public Text amountText;
// optionally add an amount parameter
// if not passed 1 is used but allows to add multiple at once
public void AddItem(Item newItem, int amount = 1)
{
// If there is no item yet
// then simply add the first one
if(!item)
{
item = newItem;
icon.sprite = item.icon;
icon.enabled = true;
_amount = amount;
}
else
{
// Otherwise check first if it is the same item
if(item != newItem)
{
Debug.LogWarning($"Type mismatch between current item {item} and added item {newItem}!", this);
// TODO handle this?
return;
}
// simply increase the amount
_amount += amount;
}
// update text
amountText.text = _amount.ToString();
}
// Again maybe add optional parameter
// if nothing is passed 1 is used
// but allows to remove multiple at once
public void RemoveItem(int amount = 1)
{
// check if enough
if(!item)
{
Debug.LogWarning("This slot is empty! Can't remove", this);
// TODO handle this?
return;
}
if(_amount - amount < 0)
{
Debug.LogWarning($"Not enough items in this slot to remove {amount}!", this);
}
// simply reduce the amount
_amount -= amount;
// update the text
_amountText.text = amount.ToString();
// Only if you reached 0 -> removed the last item
// reset this slot
if(amount == 0)
{
item = null;
icon.sprite = null;
icon.enabled = false;
}
}
public void UseItem()
{
if (item)
{
item.Use();
// TODO also remove one?
}
}
}

Why when adding a script to a selected gameobject through another script it's adding the script to each object in the list twice?

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class GenerateAutomaticGuid : Editor
{
[MenuItem("GameObject/Generate Guid", false, 11)]
private static void GenerateGuid()
{
foreach (GameObject o in Selection.objects)
{
o.AddComponent<GenerateGuid>();
o.GetComponent<GenerateGuid>().GenerateGuidNum();
o.tag = "My Unique ID";
}
}
}
For example I'm selecting two gameobjects in the hierarchy right click and GenerateGuid I use a break point and it's making a loop on each object in the list Selection.objects twice so each object in the list have the script GenerateGuid twice. And I want it to add the script to each object once only.
This is the GenerateGuid script :
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenerateGuid : MonoBehaviour, IStateQuery
{
public string uniqueGuidID;
public SaveLoad saveLoad;
public GameObject naviParent;
private Guid guidID;
private bool isNaviChildOfKid = false;
public void GenerateGuidNum()
{
guidID = Guid.NewGuid();
uniqueGuidID = guidID.ToString();
}
private State m_state = new State();
public Guid UniqueId => Guid.Parse("E0B03C9C-9680-4E02-B06B-E227831CB33F");
private class State
{
public bool naviInHand;
}
public string GetState()
{
return JsonUtility.ToJson(m_state);
}
public void SetState(string jsonString)
{
m_state = JsonUtility.FromJson<State>(jsonString);
if (m_state.naviInHand == true)
{
transform.GetComponent<InteractableItem>().distance = 0;
transform.parent = GameObject.Find("Navi Parent").transform;//rig_f_middle;
transform.localPosition = GameObject.Find("Navi Parent").transform.localPosition;
transform.localRotation = Quaternion.identity;
transform.localScale = new Vector3(0.001f, 0.001f, 0.001f);
}
}
private void Update()
{
if(transform.IsChildOf(naviParent.transform) == false && isNaviChildOfKid == false)
{
m_state.naviInHand = true;
saveLoad.Save();
isNaviChildOfKid = true;
}
}
}
A screenshot of all the places I'm calling the GenerateGuid :
Maybe you executing GenerateGuid twice or by amount of gameobjects?
Can you show all places where you executing GenerateGuid
You can add a null check to your foreach loop to see if your object already has a GenerateGuid component and skip the process if it does:
foreach (GameObject o in Selection.objects)
{
if(!o.GetComponent<GenerateGuid>()){
o.AddComponent<GenerateGuid>();
o.GetComponent<GenerateGuid>().GenerateGuidNum();
o.tag = "My Unique ID";
}
}
It won't prevent your function from being called multiple times but it can prevent duplicate GenerateGuid components.

Unity2D: Fixing colliding for inventory

My goal was to make my character pickup item on collider (2D) didn't work.
So here is what I've tried:
Player Controller Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float moveSpeed = 2f;
public Inventory inventory;
void Start()
{
}
public bool isGrounded;
public LayerMask groundLayers;
void Update()
{
// isgrounded?
isGrounded = Physics2D.OverlapArea(new Vector2(transform.position.x -
0.2f, transform.position.y - 0.2f),
new Vector2(transform.position.x + 0.2f, transform.position.y -
0.21f), groundLayers);
Jump();
Vector3 movement = new Vector3(Input.GetAxis("Horizontal"), 0f, 0f);
transform.position += movement * Time.deltaTime * moveSpeed;
}
void Jump()
{
if(Input.GetButtonDown("Jump") && isGrounded)
{
gameObject.GetComponent<Rigidbody2D>().AddForce(new Vector2(0f,
2.5f), ForceMode2D.Impulse);
}
}
private void OnCollisionEnter2D(ControllerColliderHit hit)
{
IInventoryItem item = hit.collider.GetComponent<IInventoryItem>();
if (item != null)
{
inventory.AddItem(item);
}
}
}
HUD SCRIPT:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HUD : MonoBehaviour
{
public Inventory Inventory;
void Start ()
{
Inventory.ItemAdded += InventoryScript_ItemAdded;
}
private void InventoryScript_ItemAdded(object sender, InventoryEventArgs
e)
{
Transform inventoryPanel = transform.Find("InventoryPanel");
foreach(Transform slot in inventoryPanel)
{
// Border... Image
Image image = slot.GetChild(0).GetChild(0).GetComponent<Image>();
// We found empty slot!
if (!image.enabled)
{
image.enabled = true;
image.sprite = e.Item.Image;
// Todo store a reference;
break;
}
}
}
}
Inventory Script:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Inventory : MonoBehaviour
{
private const int SLOTS = 7;
private List<IInventoryItem> mItems = new List<IInventoryItem>();
public event EventHandler<InventoryEventArgs> ItemAdded;
public void AddItem(IInventoryItem item)
{
if(mItems.Count < SLOTS)
{
Collider collider = (item as MonoBehaviour).GetComponent<Collider>
();
if (collider.enabled)
{
collider.enabled = false;
mItems.Add(item);
item.OnPickup();
if (ItemAdded != null)
{
ItemAdded(this, new InventoryEventArgs(item));
}
}
}
}
}
Inventory Item Script:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface IInventoryItem
{
string Name { get; }
Sprite Image { get; }
void OnPickup();
}
public class InventoryEventArgs : EventArgs
{
public InventoryEventArgs(IInventoryItem item)
{
Item = item;
}
public IInventoryItem Item;
}
Rock Script (The object):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rock : MonoBehaviour, IInventoryItem
{
public string Name
{
get
{
return "Rock";
}
}
public Sprite _Image = null;
public Sprite Image
{
get
{
return _Image;
}
}
public void OnPickup()
{
// TODO: ADD LOGIC THAT WILL MAKE THE ROCK A 'WEAPON' TO CUT DOWN THE
TREE
gameObject.SetActive(false);
}
}
All of those scripts work, but whenever I join my game and Collide the player with the object (all 2d, 2D Box colliders, etc.) the character wont pick the item up and put it in it's inventory?
The scripts are referenced to each other.
What did I do wrong?
Physics 2D Screenshot:
Player inspector screenshot:
Rock (Object that needs to join his inventory)
One thing I noticed is that you are mixing the syntax of two events to create one that doesn't exist. void OnCollisionEnter2D(ControllerColliderHit hit) is not a built-in event in Unity. You probably mean to use void OnCollisionEnter2D(Collision2D hit):
private void OnCollisionEnter2D(Collision2D hit)
{
IInventoryItem item = hit.collider.GetComponent<IInventoryItem>();
if (item != null)
{
inventory.AddItem(item);
}
}
Another thing is that BoxCollider2D does not inherit from Collider. So, in AddItem, you should look for a Collider2D component instead:
public void AddItem(IInventoryItem item)
{
if(mItems.Count < SLOTS)
{
Collider2D collider = (item as MonoBehaviour).GetComponent<Collider2D>();
if (collider.enabled)
{
collider.enabled = false;
mItems.Add(item);
item.OnPickup();
if (ItemAdded != null)
{
ItemAdded(this, new InventoryEventArgs(item));
}
}
}
}
Consider this to be a partial solution because this may not capture all the changes needed... Let me know if this alone doesn't fix the problem in the comments below.

How can I init a list according to the game state if the game is running or not?

I have this monobehaviour script :
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
public class CompareObjects : MonoBehaviour
{
public GameObject mainGame;
public string comparisonObjects;
public float waitTime;
public List<GameObject> allobjects = new List<GameObject>();
public bool startComparingAtStart = false;
private Coroutine comparer;
private void Start()
{
if (Application.isPlaying == false)
{
allobjects = new List<GameObject>();
}
else
{
allobjects = FindObjectsOfType<GameObject>().ToList();
}
if (startComparingAtStart == true)
{
StartComparing();
}
}
public void StartComparing()
{
mainGame.SetActive(false);
if (comparer == null)
{
comparer = StartCoroutine(Compare());
}
}
public void StopComparing()
{
if (comparer != null)
{
comparisonObjects = "";
allobjects = new List<GameObject>();
StopCoroutine(comparer);
mainGame.SetActive(true);
comparer = null;
}
}
IEnumerator Compare()
{
while (true)
{
foreach (GameObject go in allobjects)
{
if (go.name != "Game Manager")
{
comparisonObjects = go.name + " >>>>> " + go.scene.name + " >>>>> is active object";
}
yield return new WaitForSeconds(waitTime);
}
}
}
}
And editor script for buttons in inspector :
using UnityEngine;
using System.Collections;
using UnityEditor;
[CustomEditor(typeof(CompareObjects))]
public class CompareObjectsButton : Editor
{
private CompareObjects compareObjects;
private void OnEnable()
{
compareObjects = (CompareObjects)target;
}
public override void OnInspectorGUI()
{
DrawDefaultInspector();
CompareObjects myTarget = (CompareObjects)target;
if (GUILayout.Button("Compare Objects"))
{
myTarget.StartComparing();
}
if (GUILayout.Button("Stop"))
{
myTarget.StopComparing();
}
}
}
This part is not working :
if (Application.isPlaying == false)
{
allobjects = new List<GameObject>();
}
else
{
allobjects = FindObjectsOfType<GameObject>().ToList();
}
When running the game and clicking the start Compare Objects button if I will click the Stop button it will reset the List allObjects to length 0.
But if instead clicking the Stop button I just strop the game the allObjects list will be empty but the length will keep be over 5000 items. It's the items will be empty but I want that when the game is not running the list to be length 0.
Not sure why it keep the list length over 5000 items and how to reset it to length 0.
Tried to use the :
Application.isPlaying
But it's not working.
This is what I tried so far :
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
[ExecuteAlways]
public class CompareObjects : MonoBehaviour
{
public GameObject mainGame;
public string comparisonObjects;
public float waitTime;
public List<GameObject> allobjects = new List<GameObject>();
public bool startComparingAtStart = false;
private Coroutine comparer;
private void Start()
{
if (Application.isPlaying == false)
{
allobjects = new List<GameObject>();
}
else
{
allobjects = FindObjectsOfType<GameObject>().ToList();
}
if (startComparingAtStart == true)
{
StartComparing();
}
}
public void StartComparing()
{
mainGame.SetActive(false);
if (comparer == null)
{
comparer = StartCoroutine(Compare());
}
}
public void StopComparing()
{
if (comparer != null)
{
comparisonObjects = "";
allobjects = new List<GameObject>();
StopCoroutine(comparer);
mainGame.SetActive(true);
comparer = null;
}
}
IEnumerator Compare()
{
while (true)
{
foreach (GameObject go in allobjects)
{
if (go.name != "Game Manager")
{
comparisonObjects = go.name + " >>>>> " + go.scene.name + " >>>>> is active object";
}
yield return new WaitForSeconds(waitTime);
}
}
}
}
But if the comparing is working the Coroutine is in the middle and I quit the game pressing the play button to quit the game the whole editor is freezing and I need to force close it in the Task Manager.
Code if (Application.isPlaying == false) not working in your case because it places in MonoBehaviour's Start method which called only in playmode by default.
To make code workable you can move allobjects initialization to StartComparing/StopComparing methods or play with ExecuteInEditMode or ExecuteAlways attributes to run code in editor mode.

Categories