Strange If/Else Behavior - unity3d

Context
I am making a mobile game in which the player is required to touch objects in a specified order. The correct order is determined in a List called clickOrder. To determine the current object the player is supposed to click, currClickIndex is used.
Problem
When touching a correct object, the debug text will display "Correct" for a split second, and will then immediately change to "Wrong." What I am unsure about is why both the if and else blocks are executed when only touching a single object.
Code
void Update()
{
if (Input.touchCount == 1)
{
if (this.enabled)
{
Vector2 worldPoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);
RaycastHit2D hit = Physics2D.Raycast(worldPoint, Vector2.zero);
if (hit != null && hit.collider != null)
{
// check if the touched object is the correct one
if (hit.collider.gameObject == clickOrder[MyData.currClickIndex])
{
debug.text = "Correct";
MyData.currClickIndex++;
}
else
{
debug.text = "Wrong";
}
}
}
}
}

As soon as the correct object is being touched, you do this:
MyData.currClickIndex++;
which moves you forward in the ordered sequence, and from then on, the previously correct object is not correct anymore. But you're still touching it.
If you want to avoid this, you need to move forward in the sequence after you've touched the correct object.
if (there are touches and the correct object is being touched)
{
set a flag;
}
else if (a flag has been set)
{
MyData.currClickIndex++;
reset the flag;
}

Related

How to make raycasting very accurate?

I'm using raycast to detect the object the player is looking at so it can be picked up. However, it is not very accurate. in my game all small objects are pickable. I'm facing a problem when trying to pick up an item next to many other pickable items because when I'm looking directly at one item and click the pick-up button, it picks another near item instead of the one I'm currently looking at.
raycastPos = mainCamera.ScreenToWorldPoint(new Vector3(Screen.width / 2, Screen.height / 2, 0));
RaycastHit hit;
if (Physics.SphereCast(raycastPos, sphereCastRadius, mainCamera.transform.forward, out hit, maxDistance, 1 << interactableLayerIndex))
{
lookObject = hit.collider.transform.gameObject;
}
else
{
lookObject = null;
}
if (PickingUp)
{
if (currentlyPickedUpObject == null)
{
if (lookObject != null)
{
PickupObject();
if (lookObject.CompareTag("TargetObj") && !targetObjectsList.Contains(lookObject.gameObject))
{
if (aSource)
{
aSource.Play();
}
targetObjectsList.Add(lookObject.gameObject);
if (targetObjectsList.Count == targetObjects.Length)
{
//
}
}
CenterIcon.SetActive(false);
}
}
else
{
// pickupRB.transform.position = PickupParent.position;
BreakConnection();
}
How can I make the raycasting very accurate so only the object I'm pointing to gets picked?
Spherecaat/boxcast cover an area which can be great for small objects as the user does not have to be pixel perfect to click on them especially bid they have a mesh collider such as a key. However. If you have many objects near each other a wider cast can mean other objects are picked up first. Decreasing the radius so only 1 object is covered is one way or using a normal raycast will be more accurate at the expense of needing to be more accurately over said item

SteamVR: Correct way to get the input device triggered by an action and then get it's corresponding Hand class?

I have an action that is mapped to both my left amd right hand triggers on my VR controllers. I would like to access these instances...
Player.instance.rightHand
Player.instance.leftHand
...depending on which trigger is used but I can't fathom the proper way to do it from the SteamVR API. So far the closest I have gotten is this...
public SteamVR_Action_Boolean CubeNavigation_Position;
private void Update()
{
if (CubeNavigation_Position[SteamVR_Input_Sources.Any].state) {
// this returns an enum which can be converted to string for LeftHand or RightHand
SteamVR_Input_Sources inputSource = CubeNavigation_Position[SteamVR_Input_Sources.Any].activeDevice;
}
}
...am I supposed to do multiple if statements for SteamVR_Input_Sources.LeftHand and SteamVR_Input_Sources.RightHand? That doesn't seem correct.
I just want to get the input device that triggered the action and then access it using Player.instance.
I was also looking for an answer to this. I've for now done what I think is what you mean with the if-statements. It works, but definitely not ideal. You want to directly refer to the hand which triggered the action, right?
With the 'inputHand' variable here I get the transform.position of the hand from which I will raycast and show a visible line. I could have put a separate instance of a raycastScript like this on each hand, of course, but I wanted to make a 'global' script, if that makes sense.
private SteamVR_Input_Sources inputSource = SteamVR_Input_Sources.Any; //which controller
public SteamVR_Action_Boolean raycastTrigger; // action-button
private Hand inputHand;
private void Update()
{
if (raycastTrigger.stateDown && !isRaycasting) // If holding down trigger
{
isRaycasting = true;
inputHand = inputChecker();
}
if (raycastTrigger.stateUp && isRaycasting)
{
isRaycasting = false;
}
}
private Hand inputChecker()
{
if (raycastTrigger.activeDevice == SteamVR_Input_Sources.RightHand)
{
inputHand = Player.instance.rightHand;
}
else if (raycastTrigger.activeDevice == SteamVR_Input_Sources.LeftHand)
{
inputHand = Player.instance.leftHand;
}
return inputHand;
}

Keeping variable from changing on another scene load

I'm new to unity and i'm trying to load scenes based on items collected.
Problem is that the counter is not counting my acquired items.
I'm using OnTriggerEnter2D() to trigger the event; Below is the snippet:
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.CompareTag("Player"))
{
collectionNumber += 1;
Destroy(gameObject);
if (collectionNumber == 1)
{
collision.gameObject.transform.Find("Acquired_Items").GetChild(0).gameObject.SetActive(true);
qiCollector.gameObject.transform.GetChild(0).gameObject.SetActive(true);
}
else if (collectionNumber == 2)
{
collision.gameObject.transform.Find("Acquired_Items").GetChild(1).gameObject.SetActive(true);
qiCollector.gameObject.transform.GetChild(1).gameObject.SetActive(true);
}
else if (collectionNumber == 3)
{
collision.gameObject.transform.Find("Acquired_Items").GetChild(2).gameObject.SetActive(true);
qiCollector.gameObject.transform.GetChild(2).gameObject.SetActive(true);
}
else
{
Debug.LogWarning("All Items Collected !!");
}
cN.text = "Collection Number " + collectionNumber.ToString();
}
}
Whenever a new scene is loaded this script is loaded because it is on my quest item. And for every scene there is a quest item. So what I want to do is basically keep track of my collectionNumber, but it resets to 0.
Any help is much appreciated :)
First method:
Don't allow your object to be destroyed on scene load
https://docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html
public static void DontDestroyOnLoad(Object target);
Above code will prevent destroying your GameObject and its components from getting destroyed when loading a new scene, thus your script values
Second Method:
Write out your only value into a player pref
https://docs.unity3d.com/ScriptReference/PlayerPrefs.html
// How to save the value
PlayerPrefs.SetInt("CollectionNumber", collectionNumber);
// How to get that value
collectionNumber = PlayerPrefs.GetInt("CollectionNumber", 0);
Third method:
Implement a saving mechanism:
In your case i would not suggest this

Colliding objects bouncing off when isTrigger is true instead of passing through

I have looped through some game objects to set the isTrigger property before a certain collision occurs but though the property is true collision with the object still occurs. Please see the relevant code below:
void OnCollisionEnter2D(Collision2D col) {
if (col.gameObject.tag == "object1") {
for (int i = 0; i < badGuys.Count; i++) {
badGuys[i].getBadGuyGameObject().GetComponent<Collider2D>().isTrigger = true;
}
}
else if (col.gameObject.tag == "object2") {
// collision with object1 always occurs before object2, though isTrigger is true the colliding object doesn't pass through the badGuys game objects, it bounces off on collision
}
else if (col.gameObject.tag == "object3") {
for (int i = 0; i < badGuys.Count; i++) {
badGuys[i].getBadGuyGameObject().GetComponent<Collider2D>().isTrigger = false;
}
}
else if (col.gameObject.tag == "badGuy") {
}
}
collision with object1 always occurs before object2, though isTrigger is true the colliding object doesn't pass through the badGuys game objects, it bounces off on collision. How can I solve this?
UPDATE 1
I just realized that the isTrigger value is true or false in the inspector depending on the time I stop the game. For example if I stop the game after Object1 collision the isTrigger is true and when I stop the game after collision with object3 it false. This inconsistency makes debugging very cumbersome. Is this a bug or something?
UPDATE 2
Based on recommendation from Joe Blow and Agustin0987 I used the enabled property of Collider2D instead of isTrigger. I also removed virtually all the code in OnCollisionEnter2D to get a simple test scenario. Please see code below:
void OnCollisionEnter2D(Collision2D col) {
if (col.gameObject.tag == "object1") {
if (badGuys[4].getBadGuyGameObject().GetComponent<Collider2D>().enabled = false) {
badGuys[4].getBadGuyGameObject().GetComponent<Collider2D>().enabled = true;
Debug.Log ("CHANGED TO TRUE");
Debug.Log ("bad guy collider enabled is " + badGuys[4].getBadGuyGameObject().GetComponent<Collider2D>().enabled);
}
else if (badGuys[4].getBadGuyGameObject().GetComponent<Collider2D>().enabled = true) {
badGuys[4].getBadGuyGameObject().GetComponent<Collider2D>().enabled = false;
Debug.Log ("CHANGED TO FALSE");
Debug.Log ("bad guy collider enabled is " + badGuys[4].getBadGuyGameObject().GetComponent<Collider2D>().enabled);
}
}
else if (col.gameObject.tag == "badGuy") {
Debug.Log("collided with bad guy"); // DOESN'T OCCUR
}
}
Instead of looping through this time, I decided to test for one. The else if is where enabled is true always satisfied though the Log prints false and the if where enabled is false is never satisfied. In the inspector, the Box Collider2D for bad guys is always unchecked. Collision with the badGuy never occurs. Based on my simple scenario code I thought it should be working.
Never, ever ever
I mean absolutely never
use "else if".
Simply, never, ever - ever - type "else if" for the rest of your life, until you die.
In the first instance, delete all your code and replace it with this:
void OnCollisionEnter2D(Collision2D col)
{
Debug.Log("We will deal with this ......... " +col.gameObject.name);
if (col.gameObject.tag == "object1")
{
Debug.Log("\t handling 'object' type issue");
HandleObjectIssue(col)
return; //NOTE HERE
}
if (col.gameObject.tag == "object1")
{
Debug.Log("\t handling 'bad guy' type issue");
HandleBadGuyIssue(col)
return; //NOTE HERE
}
}
private void HandleObjectIssue(Collision2D col)
{
Debug.Log("'\t\t object', I'm dealing with this .. " +col.gameObject.name);
}
private void HandleBadGuyIssue(Collision2D col)
{
Debug.Log("\t\t 'badguy', I'm dealing with this .. " +col.gameObject.name);
}
Run that extensively and "unit test" it. If you like, convert back (USING THAT CODE) to a "loop" method to look at all items. In any event, test that extensively.
--
Secondly, add this routine ..
private void ToggleColliderOnGameObject( GameObject gg )
{
Debug.Log("I'm supposed to toggle the collider on: " +gg.name);
Collider2D { or whatever } col = gg.GetComponent<Collider2D>();
bool currentState = GetComponent<Collider2D>().enabled;
Debug.Log("\t it is currently: " +currentState);
bool newState = ! currentState;
col.enabled = newState;
Debug.Log("\t\t but now it is: " +newState);
}
... and start unit testing with it. So you will have code like:
ToggleColliderOnGameObject( badGuys[4].getBadGuyGameObject() );
For example, try some things like this:
void Start()
{
InvokeRepeating("teste", 1f, 1f);
}
private void Teste()
{
ToggleColliderOnGameObject( badGuys[4].getBadGuyGameObject() );
}
After experimenting with that for some time and unit testing, you will be able to move on.
Note that apart from other problems, it is likely that (for example) your routine "getBadGuyGameObject" is returning the wrong thing - or some similar problem.
EDIT:
Remove badGuy = badGuyPrefab; from your public void createBadGuy(GameObject badGuyPrefab, BadGuyType badGuyType, Vector3 badGuyPosition) function. This was a mistake in Programmer's code from the link in your comment.
It seems like you are trying to enable/detect the collision between badGuys and object2 only if object1 collides with whatever object has this script attached (seems to be some kind of BadGuyManager). So I would recommend that instead of setting isTrigger to true or false, you should try to enable or disable the whole `Collider2D.
P.S: I agree with Joe Blow in that you should explain what are you trying to achieve.

Overlapping Collider2D Triggers

Ok lets get into it.
I've got 2 colliders whose purpose it is to determine the hit type of the ball. If it is GoodHit or NiceHit (Nice is like Perfect).
The GoodHit collider2d has a much larger area than NiceHit.
The NiceHit on the other hand overlaps to a portion of GoodHit in its center.
Using this code I can determine whether the ball enters the area of GoodHit and NiceHit
void OnTriggerEnter2D(Collider2D other) {
if (other.name == "HitNice") {
hitType = 2;
} else if(other.name == "HitGood") {
hitType = 1;
}
}
void OnTriggerStay2D(Collider2D other) {
if (other.name == "HitNice") {
hitType = 2;
} else if(other.name == "HitGood") {
hitType = 1;
}
}
void OnTriggerExit2D(Collider2D other) {
hitType = 0;
}
The problem is it doesn't triggers the NiceHit and the hitType is still valued 1 regardless if the ball is in NiceHit area.
I first created the Goodhit object, that in a sense gives it a priority than NiceHit. I could swap them by names and values that way I can achieve the thing that I wanted but that doesn't really solved it.
Any workaround or tweaks I can do? Thanks!
You have two overlapping triggers, one of which is in the middle of a larger trigger.
Here's something to remember: if an object is inside the inner trigger, it is also inside the larger trigger!
So in your code, you use OnTriggerStay to determine if the object is in one of those triggers. Let's take a look at that:
void OnTriggerStay2D(Collider2D other) {
if (other.name == "HitNice") {
hitType = 2;
} else if(other.name == "HitGood") {
hitType = 1;
}
}
This function is executed once per trigger that the object is inside of. So this function is executed twice if it is inside of the HitNice trigger. So let's say Unity calls this function for each trigger. What is probably happening is that it's calling the handlers in a bad order (pseudocode):
OnTriggerEnter(HitNice) //hitType is 2
OnTriggerEnter(HitGood) //hitType is now 1!
Unity is calling the collider handlers from inside out, causing the result from a HitGood trigger to always override the HitNice value. I don't know for sure if that's how it works, but looking at your code it's totally a possibility of why it's not working correctly.
Now, I can't say this for sure, but I think just using your OnTriggerEnter handler will suffice. It will only trigger the hitType detection when it enters a collider rather than when it stays. Since the object must enter the outer trigger before entering the inner trigger, this means that it should always evaluate in the correct order. In that case, your code is probably working as is, but I would say that your logic should be like this:
void OnTriggerEnter2D(Collider2D other) {
if(other.name == "HitNice") {
hitType = 2;
} else if(other.name == "HitGood" && hitType != 2) {
hitType = 1;
}
}
The only caveat of this approach is that you would need to reset the value of hitType manually. Not sure exactly how your game works, but this should give you an idea of how to approach this. Your HitNice should always override a HitGood. So when OnTriggerEnter is called for HitGood, check that you haven't yet hit HitNice yet. Or even better, record both hit types in separate variables, and then resolve which type of hit it was later on when doing scoring or whatever.