I am trying to display a message saying 'Press E to talk to NPC' when the player is collided with the NPC collider and when the player is not collided with the NPC the message is disabled. The message does appear upon collision but it does not disabled when there are no collisions I have tried so many things but nothing seem to work. Can anyone help? HERE IS MY CODE AND SOME THINGS I HAVE TRIED:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Task_7 : MonoBehaviour
{
public GameObject PressEmsg;
//public bool isNearNPC = true;
// Start is called before the first frame update
void Start()
{
PressEmsg.gameObject.SetActive(false);
}
// Update is called once per frame
void Update()
{
Collider[] nearbyColliders = Physics.OverlapSphere(transform.position, 5f);
//bool isNearNPC = false;
//we are looping in the array hitColliders
foreach(Collider collider in nearbyColliders)
{
if(collider.gameObject.tag == "NPC")
{
PressEmsg.gameObject.SetActive(true);
print("NPC DETECTED");
//isNearNPC = true;
}
else
{
PressEmsg.gameObject.SetActive(false);
print("NPC NOT DETECTED");
}
/*
else if(collider.gameObject.tag != "NPC")
{
PressEmsg.gameObject.SetActive(false);
print("NPC NOT DETECTED");
}
*/
}
/*foreach(Collider collider1 in notnearbyColliders)
{
if(collider1.gameObject.tag != "NPC")
{
PressEmsg.gameObject.SetActive(false);
print("NPC NOT DETECTED");
}
}
*/
}
}
If you have no collisions at all you will not have any iteration of your loop. So the deactivate will not happen, except you have a nearby object without the tag NPC.
Further you are iterating through all the nearby objects and check for each of them if it has the tag NPC. So the loop fully depends on the order in which the colliders are iterated. It might e.g. happen that you first have a hit that has the tag, then you have a second hit that doesn't => you falsely deactivate the object again.
You should rather use Linq Any like e.g.
using System.Linq;
...
void Update()
{
var nearbyColliders = Physics.OverlapSphere(transform.position, 5f);
// This will be true if any of the nearbyColliders has the tag "NPC"
// If there are no colliders this will automatically be false accordingly
var detected = nearbyColliders.Any(collider => collider.CompareTag("NPC"));
// Basically this equals somewhat doing
//var detected = false;
//foreach(var collider in nearbyColliders)
//{
// if(collider.CompareTag("NPC"))
// {
// detected = true;
// break;
// }
//}
PressEmsg.gameObject.SetActive(detected);
print(detected ? "NPC detected" : "NPC not detected");
}
In general for performance reasons avoid logging in Update! Even though your users don't see the log it is still done and is quite expensive.
Note: Typed on smartphone but I hope the idea gets clear
It seems like if you don't have any collisions, you won't get into your for loop.
Before looping over the found colliders I would default the message to be not active, but I'd use a variable so I only actually call the message state method once:
bool isNearNpc = false;
Collider[] nearbyColliders = Physics.OverlapSphere(transform.position, 5f);
foreach(Collider collider in nearbyColliders)
{
if(collider.gameObject.tag == "NPC")
{
print("NPC DETECTED");
isNearNpc = true;
}
}
PressEmsg.gameObject.SetActive(isMessageActive);
print($"NPC DETECTED: { isNearNpc }");
Related
So i am making a level of unity: In this level you have to sort the garbage.
The player can move with the arrow keys, and can pick up trash with the E key. Then take it to the right trash can. I made a script that should make the character pickup the item and then he can go to the right trashbin and if he hits the trashbin the item will be destroyed, but it does not work and I have no idea what is wrong.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class PickupItem : MonoBehaviour
{
public float pickupRange = 2f;
public LayerMask pickupLayer;
public AudioClip pickupSound;
public string[] pickupTags;
public AudioClip wrongBinSound;
public string[] trashBinTags;
public TextMeshProUGUI itemNameText;
private AudioSource audioSource;
private GameObject currentObject;
private bool holdingItem = false;
private void Start()
{
audioSource = GetComponent<AudioSource>();
}
private void Update()
{
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.forward, out hit, pickupRange, pickupLayer))
{
foreach (string tag in pickupTags)
{
if (hit.collider.tag == tag && !holdingItem)
{
currentObject = hit.collider.gameObject;
if (Input.GetKeyDown(KeyCode.E))
{
StartCoroutine(Pickup());
}
break;
}
}
}
else
{
currentObject = null;
}
}
private void OnCollisionEnter(Collision collision)
{
foreach (string trashBinTag in trashBinTags)
{
if (collision.gameObject.tag == trashBinTag)
{
switch (currentObject.tag)
{
case "paper":
if (trashBinTag == "TrashbinPa")
{
Debug.Log("paper in vuilbak");
audioSource.PlayOneShot(pickupSound);
itemNameText.text = "";
holdingItem = false;
}
break;
case "glass":
if (trashBinTag == "TrashbinG")
{
Debug.Log("glass in vuilbak");
audioSource.PlayOneShot(pickupSound);
Destroy(currentObject);
itemNameText.text = "";
holdingItem = false;
}
break;
case "metal":
if (trashBinTag == "TrashbinM")
{
Debug.Log("metal in vuilbak");
audioSource.PlayOneShot(pickupSound);
Destroy(currentObject);
itemNameText.text = "";
holdingItem = false;
}
break;
case "plastic":
if (trashBinTag == "TrashbinP")
{
Debug.Log("plastic in vuilbak");
audioSource.PlayOneShot(pickupSound);
Destroy(currentObject);
itemNameText.text = "";
holdingItem = false;
}
break;
default:
audioSource.PlayOneShot(wrongBinSound);
break;
}
break;
}
}
}
IEnumerator Pickup()
{
if (currentObject != null)
{
Debug.Log("Object picked up");
yield return new WaitForSeconds(1);
audioSource.PlayOneShot(pickupSound);
currentObject.SetActive(false);
itemNameText.text = "Inventory: " + currentObject.name;
holdingItem = true;
}
}
}
My character settings:
enter image description here
One of mine Trashbin(Glass):
enter image description here
One of mine Trash items(Wine bottle):
enter image description here
I don't know what i'm doing wrong can someone help me?
I tried to debug but no outcome. I can pickup the item but i cannot hit the trashbin so it can delete the item. I use tags for the right trashbin which also use tags.
Alright, so let's take it one step at a the time. First of all, that code needs a lot of optimization as it's a FPS-drop nightmare.
private void Update()
{
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.forward, out hit, pickupRange, pickupLayer))
{
foreach (string tag in pickupTags)
{
if (hit.collider.tag == tag && !holdingItem)
{
currentObject = hit.collider.gameObject;
if (Input.GetKeyDown(KeyCode.E))
{
StartCoroutine(Pickup());
}
break;
}
}
}
else
{
currentObject = null;
}
}
Here you are raycasting every frame regardless of the player wanting to pick up the trash or not (i.e. hitting the E key). This doesn't make much sense. As I said in my comment, this check if (Input.GetKeyDown(KeyCode.E)) should be a lot earlier.
Also, if the player has holdingItem == true, raycasting to search for additional trash doesn't really make sense if the player can only carry one trash at a time. So you can optimize that code out this way. I would even argue that holdingItem will cause conflicts since the same effect can be achieved by using currentObject != null, therefore, I'll recycle holdingItem.
Next, iterating through lists each frame, also adds overhead. If you see the break, you actually call it only if this condition if (hit.collider.tag == tag && !holdingItem) is fulfilled. That means that regardless of holding an item or pressing E, as long as the player stares at some trash, you will Raycast each frame, iterate through the list each frame and compare the tag.
I mentioned tag compare. Comparing tags as string also adds some overhead. Please see GameObject.CompareTag() as that is the proper way to efficiently compare tags. Also learn how conditions work, both for or as well as for and. In your case you chose the most inefficient way to compare:
if (hit.collider.tag == tag && !holdingItem)
Even if you would choose NOT to use CompareTag() (although you should use it), this will always be more efficient:
if (!holdingItem && hit.collider.tag == tag)
This is because if the player holds an item, the next condition is skipped. When using or and and clauses in conditions always use the least expensive and most common one first.
The else is also dodgy there. That means that if the player holds an object, that object will be set to null if a Raycast doesn't hit anything in range. This might be the reason why your trash destruction fails.
This should be better, I think:
private void Update()
{
if (currentObject == null && Input.GetKeyDown(KeyCode.E))
{
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.forward, out hit, pickupRange, pickupLayer))
{
holdingItem = false; // you have a 1 second delay in Pickup()
// no delay here
foreach (string tag in pickupTags)
{
if (!holdingItem && hit.collider.gameObject.CompareTag(tag))
{
holdingItem = true; // stops overlaps
StartCoroutine(Pickup(hit.collider.gameObject));
break;
}
}
}
}
}
IEnumerator Pickup(GameObject trash)
{
if (holdingItem && trash != null && currentObject == null)
{
#if UNITY_EDITOR
// don't include this outside tests
Debug.Log(trash.tag + " picked up");
#endif
yield return new WaitForSeconds(1);
audioSource.PlayOneShot(pickupSound);
currentObject = trash;
currentObject.SetActive(false);
itemNameText.text = "Inventory: " + currentObject.name;
}
}
Next, let's look at the trash bin.
There is a ton of repeated code that can just be simplified away. Easier for maintenance too, not to mention bug fixes.
This is how I'd write OnCollisionEnter():
private void ThrowTrashAway()
{
#if UNITY_EDITOR
// don't include this outside tests
Debug.Log(currentObject.tag + " in vuilbak");
#endif
audioSource.PlayOneShot(pickupSound);
Destroy(currentObject);
currentObject = null;
itemNameText.text = "";
holdingItem = false;
}
private void OnCollisionEnter(Collision collision)
{
if (currentObject == null)
return; // don't execute anything
if ((collision.gameObject.CompareTag("TrashbinPa") && currentObject.CompareTag("paper"))
|| (collision.gameObject.CompareTag("TrashbinG") && currentObject.CompareTag("glass"))
|| (collision.gameObject.CompareTag("TrashbinM") && currentObject.CompareTag("metal"))
|| (collision.gameObject.CompareTag("TrashbinP") && currentObject.CompareTag("plastic")))
ThrowTrashAway();
else
audioSource.PlayOneShot(wrongBinSound);
}
Also keep in mind you don't destroy paper trash in your original code, like you do with other types of trash. So if you tested with paper, it wouldn't have worked anyway.
At first glance I'd say it's because of how you originally wrote the code in Update(). Of course this is also blind guessing since you haven't provided any debugging info regarding what steps the code executed in each branch on the stack nor did you tell us how each log call was or wasn't called. Regardless of whether you apply the suggestions, at least keep the concept in mind for further projects. Just because checking something in Update() is easier doesn't mean you have to do it there.
Still, as I said in my comment, the best way to really know why your code did not execute is a debugging session (not Debug.Log() calls, although they can help as well but they should never replace real debugging).
Let us know how it turned out.
I'm not entirely sure what your switch statement is doing, since currentObject gets disabled when it gets picked up (currentObject.SetActive(false);). By disabling it, the raycast in your Update() method will not find anything, and currentObject will be set to null every frame.
This will prevent any of your destruction code from running inside the OnCollisionEnter() method.
I would suggest either not disabling the currentObject, or pausing the raycast in Update() when an object is being held. This will allow the switch statement to run inside the collision method, and should destroy your object.
I'm developing a search game, where players must look for certain objects. Whenever the targeted object is found and has been picked up, the player wins and go to the next level. I tagged the targeted objects as "TargetObj". I successfully implemented this when there is only one object to look for. I want to modify my code to include cases where there is more than one object to look for. Here is my code :
public void someFunction() {
//if we press the button of choice
if (Input.GetKeyDown(KeyCode.Space)) {
//and we're not holding anything
if (currentlyPickedUpObject == null) {
//and we are looking an interactable object
if (lookObject != null) {
PickUpObject();
}
} else { //if we press the pickup button and have something, we drop it
BreakConnection();
}
}
}
/* ommitted lines */
public void PickUpObject() {
if (GameObject.FindGameObjectsWithTag("TargetObj").Length == 1 & lookObject.tag == "TargetObj") {
physicsObject = lookObject.GetComponentInChildren<PhysicsObjects>();
currentlyPickedUpObject = lookObject;
pickupRB = currentlyPickedUpObject.GetComponent<Rigidbody>();
pickupRB.constraints = RigidbodyConstraints.FreezeRotation;
physicsObject.playerInteractions = this;
winUI.SetActive(true);
Time.timeScale = 0f;
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
Time.timeScale = 1f;
} else if (GameObject.FindGameObjectsWithTag("TargetObj").Length > 1) {
} else {
physicsObject = lookObject.GetComponentInChildren<PhysicsObjects>();
currentlyPickedUpObject = lookObject;
pickupRB = currentlyPickedUpObject.GetComponent<Rigidbody>();
pickupRB.constraints = RigidbodyConstraints.FreezeRotation;
physicsObject.playerInteractions = this;
}
}
I added this line, to check if there is more than one object to look for.
else if (GameObject.FindGameObjectsWithTag("TargetObj").Length > 1)
How to implement this (if the player picked up all objects of tag "TargetObj", go to next level.)?
A fast way of doing this is to keep a counter of picked objects. Then, if the counter is equal with the number of objects with the "TargetObj" tag, then the player wins. As snippet you can have something like this:
Gameobject[] targetObjects; // an array where you will keep your objects with "TargetObj" tag
List<GameObject> targetObjectsList;
void Start()
{
targetObjects = GameObject.FindGameObjectsWithTag("TargetObj");
targetObjectsList = new List<GameObject>();
}
.
.
.
// In your method (You didn't put all your code so I will use your snippet)
if (Input.GetKeyDown(KeyCode.Space))
{
//and we're not holding anything
if (currentlyPickedUpObject == null)
{
//and we are looking an interactable object
if (lookObject != null )
{
PickUpObject();
// I suppose that "lookObject" is the gameobject that you want to pickup. If not, replase this variable with the right gameobject.
if(!targetObjectsList.Contains(lookObject.gameObject))
{
targetObjectsList.Add(lookObject.gameObject);
if (targetObjectsList.Count == targetObjects.Length)
{
//Finish the game
winUI.SetActive(true);
Time.timeScale = 0f;
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
Time.timeScale = 1f;
}
}
}
}
}
//if we press the pickup button and have something, we drop it
else
{
BreakConnection();
}
}
Then you modify your PickUpObject method, just to pick and drop objects.
I'm sorry if I miss something. I wrote this without an editor and I didn't test the code, so please tell me if is something that I miss.
These past month+ I learned many things by making a game in Unity.I have a lot of fun doing so. But some thing are still confusing me. I'm trying to setup a skill to the character and it goes almost well. When the character is casting the skill, the skill goes behind the character and not in front. So i thought to play with positions and rotations to make it work but still nothing. Worth to mention that the prefab has it's own motion. So my code so far is this. So help would be great and some teaching about the logic behind the skills system would be much appreciated. So take a look:
using UnityEngine;
public class MagicSkill : MonoBehaviour
{
public GameObject hand; // Players right hand
public GameObject fireballPrefab; // Fireball particle
Vector3 fireballPos; // Adding the transform.position from the players hand
Quaternion fireballRot; // Adding the rotation of the players hand
private bool isPressed = false; //Skill button (mobile)
public Animator animator; // Casting spell animation
void Update()
{
fireballPos = hand.transform.position; // Getting the position of the hand for Instatiating
fireballRot.x = hand.transform.rotation.x; // Getting the rotation of the hand for x Axis
fireballRot.y = hand.transform.rotation.y; // Getting the rotation of the hand for y Axis (Here i try to modify the values but still nothing)
fireballRot.z = hand.transform.rotation.z; // Getting the rotation of the hand for z Axis
if (isPressed == true)
{
animator.SetBool("Magic", true);
if (hand.transform.position.y >= 20) // Here I got the exact position of the hand for when to
Instatiate the skill
{
for (int i = 0; i < 1; i++) // For some reason it instatiates too many prefabs (I think it's because it's in Update method)
{
Instantiate(fireballPrefab, fireballPos, Quaternion.Euler(fireballRot.x, fireballRot.y, fireballRot.z));
Invoke("Update", 2.0f); // I'm trying to slow down the pressed button so that it want spawn every frame
}
}
}
else
{
animator.SetBool("Magic", false);
}
}
public void MagicSkills()
{
if (isPressed == false)
{
isPressed = true;
}
else
{
isPressed = false;
}
}
}
After some playing around with the code I managed to find a solution.Here I post the working code for me at least.Maybe it will help someone else too.For this to work properly you must remove the Event Trigger OnPointerUp from your button.Many thanks for the help Guilherme Schaidhauer Castro
using UnityEngine;
public class MagicSkill : MonoBehaviour
{
public GameObject hand; // Players right hand
public GameObject fireballPrefab; // Fireball particle
public Animator animator; // Casting spell animation
Vector3 fireballPos; // Adding the transform.position from the players hand
private bool isPressed = false; //Skill button (mobile)
void Update()
{
fireballPos = hand.transform.position; // Getting the position of the hand for Instatiating
if (isPressed == true)
{
animator.SetBool("Magic", true);
if (hand.transform.position.y >= 20) // Here I got the exact position of the hand for when to Instatiate the skill
{
Instantiate(fireballPrefab, fireballPos, Quaternion.identity);
isPressed = false;
}
}
else
{
animator.SetBool("Magic", false);
}
}
public void MagicSkills()
{
if (isPressed == false)
{
isPressed = true;
}
else
{
isPressed = false;
}
}
}
Keep a reference to the character's transform and use that transform to instantiate the fireball instead of using the rotation from the hand. The rotation of the hand is not changing in relation to the body, so that's why the ball is always going in the same direction.
I have this script, but doesn't work, when i kill one enemy the door opens, i need to know how active the door when i kill all enemies. I need to destroy all enemies to open the door, but doesn't work good, if i kill one enemy the door opens for no reason.
public class DoorScript : MonoBehaviour
{
private GameObject[] enemyToKill;
public Transform pos1, pos2;
public float speed;
public Transform startPos;
Vector3 nextPos;
public bool EnemyDead = false;
// Start is called before the first frame update
void Start()
{
enemyToKill = GameObject.FindGameObjectsWithTag("EnemyR1");
nextPos = startPos.position;
}
// Update is called once per frame
void Update()
{
foreach (GameObject enemy in enemyToKill)
{
if (enemy == null)
{
EnemyDead = true;
if (transform.position == pos1.position && EnemyDead)
{
nextPos = pos2.position;
}
transform.position = Vector3.MoveTowards(transform.position, nextPos, speed * Time.deltaTime);
}
}
}
private void OnDrawGizmos()
{
Gizmos.DrawLine(pos1.position, pos2.position);
}
}
Well you are setting your bool to true for the first enemy that is killed.
You should rather check if all enemies are non existent. In general do never compare anything derived from UnityEngine.Object for null! Rather use the implicit bool operator.
Then you could use Linq Any (or All) like e.g.
using System.Linq;
...
void Update()
{
// In general use a bool check!
// A destroyed object reference is not necessarily null
// This returns true if there is still an existing enemy
if(!EnemyDead && enemyToKill.Anyy(enemy => enemy)) return;
// Still keep the flag so the check can be skipped
// As soon as the flag is already set
EnemyDead = true;
if (transform.position == pos1.position)
{
nextPos = pos2.position;
}
transform.position = Vector3.MoveTowards(transform.position, nextPos, speed * Time.deltaTime);
}
If I assume correctly (it is not clear from above code), You have set to all enemies some tag "EnemyR1" and now You want to check, if those enemies exists or not. If there are no enemies, you want to open the doors (like exit point for player).
It also depends, if you kill the enemy immediately (and destroy the GameObject), or you only mark enemy as something (set some property/set is non-active/set different tag).
In the code you want to:
void Start()
{
// There you will get ALL objects that have the tag (that was ok before)
// This function always returns Array. Sometimes empty.
enemyToKill = GameObject.FindGameObjectsWithTag("EnemyR1");
nextPos = startPos.position;
}
void Update()
{
// Now it depends how your logic is implemented:
// If you destroy automatically all objects, array will be empty, check length:
if (enemyToKill.Length == 0)
{
//There are no enemies to be killed, You can open the door
}
// Else if objects still exist, you must check if all have the condition.
// They can be disabled, have some property, etc.. In that case You need to:
bool allKilled = true;
foreach (GameObject enemy in enemyToKill)
{
if (enemy.active) //update this condition as you need
{
allKilled = false; // Set to false, when enemy is not killed
break; // and exit the loop
}
}
if (allKilled)
{
//There are no enemies to be killed, You can open the door
}
}
I am trying to change character animations using a script, based on key inputs, but Unity seems to only play the default "standing idle" animation and occasionally switching to the "crouched idle", is there a different way to handle animations or am I just doing the script wrong? Here is my script as it currently stands
using UnityEngine;
using System.Collections;
public class CharacterControl : MonoBehaviour {
private Animator animator;
public bool crouched;
private string sc;
// Use this for initialization
void Start () {
animator = GetComponent<Animator> ();
}
// Update is called once per frame
void Update () {
if (crouched == true) {
sc = "crouch";
} else {
sc = "standing";
}
animator.Play (sc + "_idle");
if (Input.GetButton ("Fire3")) {
if (crouched == false) {
crouched = true;
} else {
crouched = false;
}
}
}
}
Try replacing
if (Input.GetButton ("Fire3")) {
if (crouched == false) {
crouched = true;
} else {
crouched = false;
}
}
with
if (Input.GetButton ("Fire3")) {
crouched = true;
} else {
crouched = false;
}
Now, when you hold down the "Fire3" button, your character should crouch, and when you release it he/she should stand again
Also a suggestion: Put the other code in the function (if (crouched == true) ... animator.Play (sc + "idle"); after this code (the Input.GetButton check). That way, your character should instantly start crouching the same frame that the button is pressed; otherwise, he/she will the frame after
Explanation
Input.GetButton will return true while you're pressing down (in the middle of clicking or touching) on the button each frame. Each time Update is called (around 1/60th of a second) your code will check if you're pressing down, and toggle crouched. When you click/tap the button, you'll likely be pressing down for a few frames, so crouched will switch from true to false, back and forth, a few times. In some cases (when you press down for an odd number of frames) crouched will be switched, but in other cases (when you press down for an even number of frames) crouched will stay as it was before you clicked on the button, preventing your character from crouching, or standing if he was crouching before.
Source: From the official API: Input.GetButton
I would strongly suggest using animation states and transit from one to another when you get an input. Checkout my answer at: Unity 2D animation running partially
Yes you can do the toggle action like this
void Update()
{
if(Input.MouseButtonDown(2))
{
crouched = true;
}
if(Input.MouseButtonUp(2))
{
crouched = false;
}
}
You can try this code:
crouched = Input.GetButtonDown("Fire3") ? true : false;