I am creating a virtual reality game where when you double click on an object it deletes it. However multiple objects are duplicates of eachother so when i attach my double click script to them it will delete all the objects upon double click. I want it to just delete the one the mouse is on. I will attach my script below:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class doubleClick : MonoBehaviour
{
private float firstClickTime, timebetweenClicks;
private bool coroutineAllowed;
private int clickCounter;
public GameObject toDelete;
// Start is called before the first frame update
void Start()
{
firstClickTime = 0f;
timebetweenClicks = 0.2f;
clickCounter = 0;
coroutineAllowed = true;
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonUp(0))
clickCounter += 1;
if (clickCounter == 1 && coroutineAllowed)
{
firstClickTime = Time.time;
StartCoroutine(DoubleClickDetection());
}
}
private IEnumerator DoubleClickDetection()
{
coroutineAllowed = false;
while (Time.time < firstClickTime + timebetweenClicks)
{
if (clickCounter == 2)
{
//Destroy(toDelete);
break;
}
yield return new WaitForEndOfFrame();
}
clickCounter = 0;
firstClickTime = 0f;
coroutineAllowed = true;
}
}
I can't see anything that jumps out in your code. Padia's answer is on the right track, though. Mouse input handling when you want to interact with objects is best done from within that object/prefab. That way, you're guaranteed that only that object is interacted with. If not, then there's likely something else going on with your code.
Unfortunately, it's a little late for me to work out a complete solution. But here's a possible solution.
Within the prefab's code, insert an OnMouseDown function. In that function, check if the double-click timer's been started. If not, set a flag to tell the prefab that the double-click timer needs to start (call it WaitingForTimerStart). You could alternatively use an enum to hold the timer state. In any event, don't start the timer yet.
Within the prefab's code, insert an OnMouseUp function. That should check if the timer start flag's (WaitingForTimerStart) been set. If it has, set another timer flag to tell it to run the timer (RunTimer).
In the prefab's Update function, run the timer. I'm sure you know what to do here.
In the prefab's OnMouseDown function (the one you added earlier), check if the timer's running. If it is, then you know a double-click's been performed. Destroy the object.
This way, there's no need to check names, use raycasting or coroutines. Each object will have its own timer and be fully independent of each other.
One main issue with your code is: You are starting the Coroutine everyframe!
You have to wrap your code block in { } after the if, otherwise the condition only applies for the one line after it!
Instead of
if (Input.GetMouseButtonUp(0))
clickCounter += 1;
if (clickCounter == 1 && coroutineAllowed)
{
firstClickTime = Time.time;
StartCoroutine(DoubleClickDetection());
}
it should probably rather be
if (Input.GetMouseButtonUp(0))
{
clickCounter += 1;
if (clickCounter == 1 && coroutineAllowed)
{
firstClickTime = Time.time;
StartCoroutine(DoubleClickDetection());
}
}
Then
I want it to just delete the one the mouse is on.
Well currently you are using Input.GetMouseButtonUp which is true global in the entire app, not only the object the mouse is currently over.
You can rather use e.g. OnMouseDown which is called when the mouse goes down over a collider or UI element.
private void OnMouseDown ()
{
clickCounter += 1;
if (clickCounter == 1 && coroutineAllowed)
{
firstClickTime = Time.time;
StartCoroutine(DoubleClickDetection());
}
}
Try this code:
private float firstClickTime, timebetweenClicks;
private bool coroutineAllowed;
private int clickCounter;
RaycastHit hit;
// Start is called before the first frame update
void Start()
{
firstClickTime = 0f;
timebetweenClicks = 0.2f;
clickCounter = 0;
coroutineAllowed = true;
}
private void OnMouseUp()
{
clickCounter += 1;
}
// Update is called once per frame
void Update()
{
if (clickCounter == 1 && coroutineAllowed)
{
firstClickTime = Time.time;
StartCoroutine(DoubleClickDetection());
}
}
private IEnumerator DoubleClickDetection()
{
coroutineAllowed = false;
while (Time.time < firstClickTime + timebetweenClicks)
{
if (clickCounter == 2)
{
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit))
{
if (hit.collider.transform == this.transform)
{
Destroy(this.gameObject);
}
}
break;
}
yield return new WaitForEndOfFrame();
}
clickCounter = 0;
firstClickTime = 0f;
coroutineAllowed = true;
}
This code use OnMouseDown() Unity function to detect when the game object is clicked by the mouse, and add 1 to the clickCounter variable.
Also you can use OnMouseUp() function if you prefer to detect the click when the mouse is up.
Related
The script is attached to two gameobjects.
One it's colliding area the collider is big enough so when the game start the player is already inside the collider area and then when he exit the area everything is working fine.
The problem is when I attached the object to another gameobject with collider but this time the collider s smaller and he is inside the bigger collider so now the player is entering the smaller collider and not first time exiting. but I want in both case to make the same effect.
If the player exit the collider he slow down wait then turn around and move inside back.
The same I want to make when the player getting closer to the fire flames slow down wait turn around and move back in it's just with the flames the player entering the collider area and then exit while in the bigger collider he first exit then enter.
Screenshot of the two colliders areas. The small one on the left is the one the player entering first and not exiting. The game start when the player is in the bigger collider area.
And the script :
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityStandardAssets.Characters.ThirdPerson;
public class DistanceCheck : MonoBehaviour
{
public Transform targetToRotateTowards;
public Transform colliderArea;
public float lerpDuration;
public float rotationSpeed;
[TextArea(1, 2)]
public string textToShow;
public GameObject descriptionTextImage;
public TextMeshProUGUI text;
public ThirdPersonUserControl thirdPersonUserControl;
private Animator anim;
private float timeElapsed = 0;
private float startValue = 1;
private float endValue = 0;
private float valueToLerp = 0;
private bool startRotating = false;
private bool slowOnBack = true;
private bool exited = false;
private Vector3 exitPosition;
private float distance;
void Start()
{
anim = transform.GetComponent<Animator>();
}
private void FixedUpdate()
{
if (startRotating)
{
transform.rotation = Quaternion.RotateTowards(transform.rotation,
Quaternion.LookRotation(targetToRotateTowards.position - transform.position),
rotationSpeed * Time.deltaTime);
}
if (exitPosition != new Vector3(0, 0, 0) && slowOnBack)
{
distance = Vector3.Distance(transform.position, exitPosition);
}
if (distance > 5 && slowOnBack)
{
slowOnBack = false;
StartCoroutine(SlowDown());
}
}
private void OnTriggerExit(Collider other)
{
if (other.name == colliderArea.name)
{
exited = true;
slowOnBack = true;
exitPosition = transform.position;
thirdPersonUserControl.enabled = false;
descriptionTextImage.SetActive(true);
text.text = textToShow;
StartCoroutine(SlowDown());
}
}
private void OnTriggerEnter(Collider other)
{
if (other.name == colliderArea.name)
{
exited = false;
startRotating = false;
text.text = "";
descriptionTextImage.SetActive(false);
}
}
IEnumerator SlowDown()
{
timeElapsed = 0;
while (timeElapsed < lerpDuration)
{
valueToLerp = Mathf.Lerp(startValue, endValue, timeElapsed / lerpDuration);
anim.SetFloat("Forward", valueToLerp);
timeElapsed += Time.deltaTime;
yield return null;
}
if (exited)
{
yield return new WaitForSeconds(3f);
startRotating = true;
StartCoroutine(SpeedUp());
}
if (slowOnBack == false)
{
thirdPersonUserControl.enabled = true;
}
}
IEnumerator SpeedUp()
{
timeElapsed = 0;
while (timeElapsed < lerpDuration)
{
valueToLerp = Mathf.Lerp(endValue, startValue, timeElapsed / lerpDuration);
anim.SetFloat("Forward", valueToLerp);
timeElapsed += Time.deltaTime;
yield return null;
}
}
}
Two problems I'm facing right now :
The smaller collider the player enter it then exit and the bigger collider the player exit it then enter so I need to change somehow the OnTriggerExit/Enter behavior logic in the script. but how to make the logic ?
Maybe it's better to make the script to be on the player object only and make it some how generic so I can drag to it many colliders and to make the effect in each one of the colliders the problem is how to make a text field for each collider area ? Now because I attach the script to each collider object I have one colliderArea variable but if I want to make the script only attached to the player I need to change the colliderArea variable to a List<Transform> collidersAreas and then how to create a text field/area to each collider area in the List ?
I think I would do this by creating two tags, NoExit and NoEntry. Once the tags are created you can set the tag on the GameObjects that hold your colliders. Then you can check for tags in the OnTriggerEnter and OnTriggerExit and act accordingly.
I can't tell for sure which functionality you've got written is for which case, or if it's both - everything looks muddled. Ultimately what you should be going for is a function like RepositionPlayer, that moves them back to before they're violating the no entry or no exit rules, and then some kind of OnPlayerRepositioned event that restores their control.
I'll leave it up to you to split out your functionality, but in general I'd look to do something like the following:
private void OnTriggerExit(Collider other)
{
if (other.tag == "NoExit")
{
RepositionPlayer();
}
else if(other.tag == "NoEntry")
{
OnPlayerRepositioned();
}
}
private void OnTriggerEnter(Collider other)
{
if (other.tag == "NoExit")
{
OnPlayerRepositioned();
}
else if(other.tag == "NoEntry")
{
RepositionPlayer();
}
}
And again here it's not clear to me what you're trying to do to reposition the player, but it looks like it's something like:
private void RepositionPlayer()
{
// Stuff that needs to happen to reposition the player
exited = true;
slowOnBack = true;
exitPosition = transform.position;
thirdPersonUserControl.enabled = false;
descriptionTextImage.SetActive(true);
text.text = textToShow;
StartCoroutine(SlowDown());
}
private void OnPlayerRepositioned()
{
// stuff you need to do to clear the "repositioning" status
exited = false;
startRotating = false;
text.text = "";
descriptionTextImage.SetActive(false);
}
Splitting up the logic like this makes it easier to both read and maintain.
I just got into Game Dev and I need my script to enable when my player steps a cube trigger, but I don't know how.
This is the Script that I typed and I can't figure out if I should erase the Void Start or not?
public class CountdownTimer : MonoBehaviour {
public bool timerIsRunning;
public Text timeText;
public float timeRemaining = 10;
private void Start()
{
timerIsRunning = true;
}
void Update(){
if (timerIsRunning)
{
if (timeRemaining > 0)
{
timeRemaining -= Time.deltaTime;
DisplayTime(timeRemaining);
}
else
{
Debug.Log("Time has run out!");
timeRemaining = 0;
timerIsRunning = false;
}
}
}
void DisplayTime(float timeToDisplay){
timeToDisplay += 1;
float minutes = Mathf.FloorToInt(timeToDisplay / 60);
float seconds = Mathf.FloorToInt(timeToDisplay % 60);
timeText.text = string.Format("{0:00}:{1:00}", minutes, seconds);
}
}
I think you can make good use of a trigger collider. To do that, you need to add a collider of your choice to your zone and set it as a trigger. Be sure to have a collider and a rigidbody on your player.
Then in your script you can use the function OnTriggerEnter who will be called when an object enter in your zone defined by your trigger collider.
For your countdown method, you can make good use of a couroutine. It will look like this :
IEnumerator DisplayTime() {
timeIsRunning = true;
for (int i = 0; i < timeRemaining; i++) {
// your code to change the text
yield return new WaitForSeconds(1);
}
timeIsRunning = false;
}
private void OnColliderEnter(Collider other) {
if(!timeIsRunning) {
StartCouroutine(DisplayTime());
}
}
What this code does is when an object enter in the trigger, it starts a routine who will update the time displayed each second until the countdown is finished.
With that you won't need your Start and your Update methods anymore.
If you want more informations on trigger collider : https://docs.unity3d.com/ScriptReference/Collider-isTrigger.html
If you want more informations on couroutines : https://docs.unity3d.com/Manual/Coroutines.html
If you want more informations on the method OnTriggerEnter : https://docs.unity3d.com/ScriptReference/Collider.OnTriggerEnter.html
As the title says I want to cancel an animation after I attacked once and the monster lost 10 lives , and run it again if I attack again and the monster loses another 10 lives . I have tried to find the answer already but I couldn't find anything that works . So far I managed to get it to work the first time I shoot , after he loses 10 lives and the animation goes to idle instead of the animation defend. But the second time nothing happens , he just stays in idle . The code should work unless I am missing something?(I still have to add some code (if it was working)).
```
public GameObject monster;
private Animator anim;
public GameObject player;
private Transform player_pos;
private Target target;
private float i = 0;
private float y = 0;
// Start is called before the first frame update
void Start()
{
anim = monster.GetComponent<Animator>();
player_pos = player.GetComponent<Transform>();
target = monster.GetComponent<Target>();
}
// Update is called once per frame
void Update()
{
if(target.health <= 0)
{
anim.SetBool("isDead", true);
}
if (target.health < 100 && i<1)
{
anim.SetBool("isDamaged", true);
i++;
}
else
{
anim.SetBool("isDamaged", false);
anim.SetBool("isIdle", true);
}
if (target.health == 90 && y < 1)
{
y++;
anim.SetBool("isDamaged", true);
}
else
{
anim.SetBool("isDamaged", false);
anim.SetBool("isIdle", true);
}
}
For anyone having this issue, I found out how, just replace the parameter type from bool to trigger.
This question already has answers here:
How to make the script wait/sleep in a simple way in unity
(7 answers)
Closed 4 years ago.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GateControl : MonoBehaviour
{
public Transform door;
public float doorSpeed = 1.0f;
public bool randomDoorSpeed = false;
[Range(0.3f, 10)]
public float randomSpeedRange;
private Vector3 originalDoorPosition;
// Use this for initialization
void Start()
{
originalDoorPosition = door.position;
}
// Update is called once per frame
void Update()
{
if (randomDoorSpeed == true && randomSpeedRange > 0.3f)
{
StartCoroutine(DoorSpeedWaitForSeconds());
}
door.position = Vector3.Lerp(originalDoorPosition,
new Vector3(originalDoorPosition.x, originalDoorPosition.y, 64f),
Mathf.PingPong(Time.time * doorSpeed, 1.0f));
}
IEnumerator DoorSpeedWaitForSeconds()
{
doorSpeed = Random.Range(0.3f, randomSpeedRange);
yield return new WaitForSeconds(3);
}
}
Making StartCoroutine inside the Update is a bad idea. But I want that it will take one random speed when running the game then will wait 3 seconds and change to a new random speed then wait another 3 seconds and change for another new random speed and so on.
And while it's waiting 3 seconds to keep the current speed constant until the next change.
Update:
This is what I tried:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GateControl : MonoBehaviour
{
public Transform door;
public float doorSpeed = 1.0f;
public bool randomDoorSpeed = false;
public bool IsGameRunning = false;
[Range(0.3f, 10)]
public float randomSpeedRange;
private Vector3 originalDoorPosition;
// Use this for initialization
void Start()
{
IsGameRunning = true;
originalDoorPosition = door.position;
StartCoroutine(DoorSpeedWaitForSeconds());
}
// Update is called once per frame
void Update()
{
door.position = Vector3.Lerp(originalDoorPosition,
new Vector3(originalDoorPosition.x, originalDoorPosition.y, 64f),
Mathf.PingPong(Time.time * doorSpeed, 1.0f));
}
IEnumerator DoorSpeedWaitForSeconds()
{
var delay = new WaitForSeconds(3);//define ONCE to avoid memory leak
while (IsGameRunning)
{
if (randomDoorSpeed == true && randomSpeedRange > 0.3f)
doorSpeed = Random.Range(0.3f, randomSpeedRange);
yield return delay;
}
}
}
But there are two problems.
The first problem is that every 3 seconds, when it's changing the speed of the door, it's also changing the door position from its current position. So it looks like the door position is jumping to another position and then continue from there. How can I make that it will change the speed meanwhile the door keep moving from its current position ?
Second problem is how can I change the randomDorrSpeed flag so it will take effect while the game is running? I Want that if randomDorrSpeed is false use the original speed of the door (1.0) and if it`s true use the random speed.
You already know that the coroutine should start from Start:
void Start()
{
//initialization
StartCoroutine(DoorSpeedWaitForSeconds());
}
So make the coroutine a loop with the proper terminate condition:
IEnumerator DoorSpeedWaitForSeconds()
{
var delay = new WaitForSeconds(3);//define ONCE to avoid memory leak
while(IsGameRunning)
{
if(randomDoorSpeed == true && randomSpeedRange > 0.3f)
doorSpeed = Random.Range(0.3f, randomSpeedRange);
if(!randomDoorSpeed)
doorSpeed = 1;//reset back to original value
yield return delay;//wait
}
}
For the other question you asked, if you think about it you can't possibly use ping pong with dynamic speed based on Time.time. You need to change it like this:
bool isRising = true;
float fraq = 0;
void Update()
{
if (isRising)
fraq += Time.deltaTime * doorSpeed;
else
fraq -= Time.deltaTime * doorSpeed;
if (fraq >= 1)
isRising = false;
if (fraq <= 0)
isRising = true;
fraq = Mathf.Clamp(fraq, 0, 1);
door.position = Vector3.Lerp(originalDoorPosition,
new Vector3(originalDoorPosition.x, originalDoorPosition.y, 64f),
fraq);
}
You can solve the original problem without coroutines:
public float timeBetweenChangeSpeed = 3f;
public float timer = 0;
void Update ()
{
// Add the time since Update was last called to the timer.
timer += Time.deltaTime;
// If 3 seconds passed, time to change speed
if(timer >= timeBetweenChangeSpeed)
{
timer = 0f;
//Here you call the function to change the random Speed (or you can place the logic directly)
ChangeRandomSpeed();
}
}
And about opening and closing doors. Here is a script I used to control doors in a maze game I worked in:
You need to set empty gameObjects with to set the boundaries, that is until what point you want to move the door one opening or when closing. You place this empty gameobjects in your scene and link them to the scrip in the correct field. The script will take the transform.position component on it's own. There is also a trigger enter to activate the door when a character approaches. If you dont need that part, I can edit the code tomorrow.
You can use this script also to move platforms, enemies... in general anything which can move in a straight line.
using UnityEngine;
using System.Collections;
public class OpenDoor : MonoBehaviour {
// define the possible states through an enumeration
public enum motionDirections {Left,Right};
// store the state
public motionDirections motionState = motionDirections.Left;
//Variables for State Machine
bool mOpening = false;
bool mClosing = false;
//bool mOpened = false;
//OpenRanges to open/close the door
public int OpenRange = 5;
public GameObject StopIn;
public GameObject StartIn;
//Variables for Movement
float SpeedDoor = 8f;
float MoveTime = 0f;
int CounterDetections = 0;
void Update () {
// if beyond MoveTime, and triggered, perform movement
if (mOpening || mClosing) {/*Time.time >= MoveTime && */
Movement();
}
}
void Movement()
{
if(mOpening)
{
transform.position = Vector3.MoveTowards(transform.position, StopIn.transform.position, SpeedDoor * Time.deltaTime);
if(Vector3.Distance(transform.position, StopIn.transform.position) <= 0)
mOpening = false;
}else{ //This means it is closing
transform.position = Vector3.MoveTowards(transform.position, StartIn.transform.position, SpeedDoor * Time.deltaTime);
if(Vector3.Distance(transform.position, StartIn.transform.position) <= 0)
mClosing = false;
}
}
// To decide if door should be opened or be closed
void OnTriggerEnter(Collider Other)
{
print("Tag: "+Other.gameObject.tag);
if(Other.gameObject.tag == "Enemy" || Other.gameObject.tag == "Player" || Other.gameObject.tag == "Elevator")
{
CounterDetections++;
if(!mOpening)
Opening();
}
}
void OnTriggerStay(Collider Other)
{
if(Other.gameObject.tag == "Elevator")
{
if(!mOpening)
Opening();
}
}
void OnTriggerExit(Collider Other)
{
if(Other.gameObject.tag == "Enemy" || Other.gameObject.tag == "Player")
{
CounterDetections--;
if(CounterDetections<1)
Closing();
}
}
void Opening()
{
mOpening = true;
mClosing = false;
}
void Closing()
{
mClosing = true;
mOpening = false;
}
}
Using timer and setting an interval. The delegate event will fire every time the interval is reached.
var t = new Timer {Interval = 3000};
t.Elapsed += (sender, args) => { /* code here */};
So i am designing a game where once the object collides with the powerup it should wait for 10 seconds and go back to normal. I am using update function to keep count but once OnTriggerEnter function is used the update function stops running every frame. How do i counter it. Please help
public class Powerup_1act : MonoBehaviour {
//This powerup temporarily passes through all lasers
public GameObject EffectiveTo;
private float TimeRemaining;
int flag=0;
float timer=0.0f;
public float alphalevel=.20f;
public GameObject laserfield;
float flag2=0f;
float countdown=0;
void OnTriggerEnter2D(Collider2D other)
{
//Check the provided Collider2D parameter other to see if it is tagged "PickUp", if it is...
if (other.gameObject.CompareTag("Player"))
{
gameObject.SetActive(false);
laserfield.gameObject.tag = "Temp";
flag2 = 1;
changealpha ();
Debug.Log ("Active?"+gameObject.activeInHierarchy);
StartCoroutine(Powerup());
}
}
private IEnumerator Powerup()
{
countdown = 10f;
while (countdown >= 0) {
Debug.Log (timer);
timer += Time.deltaTime;
if (flag2 == 1) {
//changes the alpha level between 0.2f and 0
if (alphalevel > 0 && flag == 0) {
alphalevel -= .05f;
changealpha ();
} else if ((alphalevel == 0 || flag == 1) && alphalevel < .20) {
alphalevel += .05f;
changealpha ();
Debug.Log (alphalevel);
flag = 1;
} else if (alphalevel == .20) {
flag = 0;
}
countdown-=Time.smoothDeltaTime;
yield return null;
}
}
alphalevel = 1f;
changealpha ();
laserfield.gameObject.tag = "Laser";
flag2 = 0;
}
//heres a function to change transparency of player when a powerup is in effect
void changealpha()
{
EffectiveTo.GetComponent<SpriteRenderer> ().color = new Color (1f, 1f, 1f, alphalevel);
}
}
Use Coroutine's for your advantage:
void OnTriggerEnter2D(Collider2D other)
{
StartCoroutine(PowerUp());
}
private IEnumerator PowerUp()
{
countDown = 10f;
while (countDown >= 0)
{
// Logic during the 10 seconds
countDown -= Time.smoothDeltaTime;
yield return null;
}
}
What is happening is that when you disable the gameobject, the script that this gameobject has stop working too.
In your case when the OnTriggerEnter2D is fired you're setting you gameobject to false gameObject.SetActive(false); which means that this script will stop working because you've also placed this script on the gameobject you've just disabled.
You should move the OnTriggerEnter2D event on the other object that dosen't get disabled and you can disable this gameobject from there. What you do in Update would also be moved to that other gameobject.
From Unity documentation:
Making a GameObject inactive will disable every component, turning off any attached renderers, colliders, rigidbodies, scripts, etc... Any scripts that you have attached to the GameObject will no longer have Update() called, for example.