This has been asked before, but none of the answers solved my problem because the problem was always slightly different.
I have a character that jumps on spacebar down. I want it to make higher jumps when the spacebar is pressed longer, with a maximum of twice the normal jump height.
Here's what I came up with so far:
void FixedUpdate () {
if (Input.GetKeyDown(KeyCode.Space) && myRB.velocity.y == 0)
{
timeHeld = 1;
}
if (Input.GetKey(KeyCode.Space) && myRB.velocity.y == 0)
{
myRB.AddForce(Vector3.up * 20,ForceMode2D.Impulse);
timeHeld += Time.deltaTime;
myRB.mass = myRB.mass - timeHeld/10;
}
if (Input.GetKeyUp(KeyCode.Space))
{
myRB.mass = 1;
}
}
Since the jump has to happen on keydown (not keyup), I can't increase the force added on jump, because the force is already applied on keydown. Therefore, I was thinking of lowering the mass of my rigidbody.
The above code "works" if I keep holding spacebar: the character jumps (minimum height), lands, jumps again but now a bit higher, etc. But that's not what I'm looking for, obviously.
This can easily be done by making a Time frame in which Jumping occurs;
Within this time frame u allow upward forces to be applied by holding space bar down. As soon as you reach either the end of the time frame or the end of the button press; you restrict upward velocity until the character has landed.
Let gravity take its own course after you end the jump.
As for the coding part i suggest you start a co routine when the jump happens.
here is some code to u help you get started;
IEnumerator Jumper()
{
if (maxjumpduration!=jumpduration)
{
jumpduration = jumpduration + 0.1f;
yield return new WaitForSeconds(0.1f);
}
else
{
jumpduration = 0;
StopCoroutine(Jumper());
}
}
As for the fact you want have a regular jump as well; make another time frame in which is decided if the press of space bar is considered either a regular jump or a controlled jump; During that initial time frame in which you are still deciding what kind of jump it is; you execute a short part of the jump until you reach a conclusion and then you send it to the correct method.
I added Some ground work for u but try to finish the code yourself;
{
private bool shortjump = false;
private bool checkphase = false;
private bool hitground = false;
private Rigidbody player = new Rigidbody();
private bool jumping = false;
Vector3 sharedjumpforce = new Vector3();
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Initialjump();
}
if (Input.GetKeyUp(KeyCode.Space))
{
if (jumping)
{
shortjump = true;
}
}
if (jumping)
{
if (hitground)
{
jumping = false;
}
}
}
void Initialjump()
{
if (jumping = false)
{
checkphase = true;
jumping = true;
player.AddForce(sharedjumpforce);
Invoke("Standardorcontrolledjump", 0.2f);
}
}
void Standardorcontrolledjump()
{
checkphase = false;
if (shortjump)
{
}
else
{
}
}
}
I don't understand why are you putting
&& myRB.velocity.y == 0
You want it to reduce its mass while in air don't you. Assuming that reducing the mass after the initial force has been applied does allow the Rigidbody to go higher, (force of gravity weakens, im not sure if that happens in Unity)
Why don't you try this
void FixedUpdate () {
// apply force the instant button is pressed. That is only once
if (Input.GetKeyDown(KeyCode.Space) && myRB.velocity.y == 0)
{
myRB.AddForce(Vector3.up * 20,ForceMode2D.Impulse);
timeHeld = 1;
}
// keep subtracting from mass while its held
if (Input.GetKey(KeyCode.Space))
{
timeHeld += Time.deltaTime;
myRB.mass = myRB.mass - timeHeld/10;
}
if (Input.GetKeyUp(KeyCode.Space))
{
myRB.mass = 1;
}
}
Related
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.
I have been working on a Unity2d project for a while now and I recently implemented a method of which my "Enemy" housed in the GreyGuardController script would fire a bullet in the direction of which it would be facing, using the animator and
otherAnimator = otherObject.GetComponent<Animator>();
for this.
Every time I now run my code, my game freezes but doesn't crash (I think it is stuck in a loop but there are no real loops in my program). It isn't my instantiation of the bullet before anyone starts accusing that as the loop of it freezing as I have commented this out and changed things around over and over again.
public class HurtPlayer : MonoBehaviour {
public float timeToShoot;
private float timeToShootCounter;
private bool shot;
private Vector3 moveDirection;
public float timeBetweenShot;
public float timeBetweenShotCounter;
public Transform firePoint;
public GameObject Bullet;
// Use this for initialization
void Start()
{
shot = false;
timeToShootCounter = timeToShoot;
timeBetweenShotCounter = timeBetweenShot;
}
IEnumerator ExecuteAfterTime(float time)
{
yield return new WaitForSeconds(time);
}
// Update is called once per frame
void Update () {
if (shot == true)
{
timeBetweenShot -= Time.deltaTime;
timeToShoot -= Time.deltaTime;
if (timeBetweenShot <= 0f)
{
shot = false;
timeToShoot = timeToShootCounter;
timeBetweenShot = timeBetweenShotCounter;
}
}
}
void OnTriggerStay2D(Collider2D other)
{
if (other.gameObject.tag == "player")
{
if(shot == false)
{
if (timeToShoot >= 0f)
{
shot = true;
while(shot == true)
{
Instantiate(Bullet, firePoint.position, firePoint.rotation);
if (timeBetweenShot <= 0f)
{
shot = false;
timeToShoot = timeToShootCounter;
timeBetweenShot = timeBetweenShotCounter;
}
}
}
}
}
}
This here is my code attached to my guard which instantiates a bullet as well as trying to use variables "TimeToShoot" as a counter for how long the enemy has left to shoot for and "TimeBetweenShoot" as a counter for how long the enemy has in between shots.
This doesn't work and neither does the Enumerator delay.
As an amateur I am obviously doing something clearly wrong but I have no idea what I'm doing wrong or where and would greatly appreciate your help.
while(shot == true)
this will freeze the editor if the body is never left
if (timeBetweenShot <= 0f)
{
shot = false;
if this is not executed in the first iteration, it won't be in the second because you never change the value of "timeBetweenShot" in that loop
It is important to understand that your update function needs to terminate for the game frame to continue with the next game object's update etc, until the end of the frame is reached, before the next update is called on this game object in the next game frame, so
void Update () {
if (shot == true)
{
timeBetweenShot -= Time.deltaTime;
is never executed while your while loop goes rogue
EDIT:
try something like:
// Update is called once per frame
void Update()
{
if (timeToShoot >= 0f) // if we are shooting at all
{
timeToShootCounter -= Time.deltaTime; // make sure we'll stop shooting after the given amount of time (decrease countdown until negative
timeBetweenShotCounter -= Time.deltaTime; // decrease countdown until next shot (after some intervall)
if (timeBetweenShotCounter <= 0f) // if intervall since last shot expired
{
Instantiate(Bullet, firePoint.position, firePoint.rotation); // shoot
timeBetweenShotCounter += timeBetweenShot; // add the time to wait for the next shot to the intervall (so that we don't shoot anymore in the following frames, until this time expired again (by becoming negative))
}
}
}
void OnTriggerStay2D(Collider2D other)
{
if (other.gameObject.tag == "player")
{
timeToShootCounter = -1f; // initialise as expired (start shooting immediately)
timeBetweenShotCounter = timeBetweenShot; // shoot in intervalls for this amount of time
}
}
this way, when the collision event is triggered, you reset the counters to their initial values. After that the update function makes sure to fire after intervals of time, and stops once the overall shooting time is over.
Of course you can further improve this code to handle edge cases (if overall time expires and intervals time also expires at the same time, do we shoot a last bullet or not? etc)
hope this helps.
If you appreciate my efforts, please give them a thumbs up =)
Here is your loop: you increase timeBetweenShot on each frame but your while have to be executed in one frame, this means your code executes this while until you enter the if statement but you will never do since timeBetweenShot will change value only in next frame
while(shot == true)// here is your loop budy
{
// shot = false; // try this row here you will problably stop having the trouble
Instantiate(Bullet, firePoint.position, firePoint.rotation);
if (timeBetweenShot <= 0f)//
{
shot = false;
timeToShoot = timeToShootCounter;
timeBetweenShot = timeBetweenShotCounter;
}
}
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 */};
I'm trying to make text boxes pop up during my game and have it pause the game, and it seems to be working for the most part. There's a little bit of oddness with it that I've found and can't figure out what's going on: As long as I keep hitting the movement keys (particularly LEFT) I can keep moving.
My game is tile and turn based, so the movements are simply testing the map array and then jumping the character (as follows):
if (Input.GetButtonDown ("Horizontal"))
{
if (Input.GetAxis ("Horizontal") > 0)
{
if (TileArray.WalkableTile (newPosition.x + 1, newPosition.y) == true)
{
TileArray.ToggleWalkable (newPosition.x, newPosition.y, true);
newPosition.x += 1;
PlayerMoved = true;
}
}
else if (Input.GetAxis ("Horizontal") < 0)
{
if (TileArray.WalkableTile (newPosition.x - 1, newPosition.y) == true)
{
TileArray.ToggleWalkable (newPosition.x, newPosition.y, true);
newPosition.x -= 1;
PlayerMoved = true;
}
}
}
The code I'm using to pause and display the text boxes is:
public void StartDialog(int Counter)
{
Time.timeScale = Time.timeScale = 0;
InDialog = true;
DialogCounter = Counter;
DialogCanvas.enabled = true;
ClickCounter = 0;
DialogText.text = DialogArray [DialogCounter][ClickCounter];
UpdateDialogPic(DialogArray[DialogCounter][ClickCounter]);
}
It does not seem to matter whether the text is only 1 box or a chain of multiple text boxes that you cycle through using mouse clicks. Everything seems to be working fine aside from this, and it doesn't matter whether I wait to press left or not - as long as I don't stop once I START, I can walk the map or until I hit something.
You need to note two main facts about Time.timeScale.
When timeScale is set to zero the game is basically paused if all your
functions are frame rate independent.
and
If you lower timeScale it is recommended to also lower
Time.fixedDeltaTime by the same amount.
You may make the following changes to your code,
// introduce a new variable
bool isPaused
// for the pausing/resuming action
if(isPaused)
{
Time.timeScale = 1;
isPaused = !isPaused;
}
else
{
Time.timeScale = 0;
isPaused = !isPaused;
}
// On your 'if (Input.GetButtonDown ("Horizontal"))' line
if (Input.GetButtonDown ("Horizontal") && !isPaused)
Right now I'm not getting much Out of this. Depending on what I do different i either end up with an infinite loop, or poor jumping ability. I used a timer to tick my jumped bool but I was getting a double like jump ability and my ground detection wasn't good enough. Can you See why I can't jump, or jump well?
using UnityEngine;
using System.Collections;
public class player : MonoBehaviour
{
public float speed = 0.05f;
public float jumpVelocity = 0.05f;
public bool onGround = true;
public bool jumped = false;
// Use this for initialization
void Start () { }
// Update is called once per frame
void Update ()
{
//test if player is on the ground
if (onGround) { jumped = false; }
// USER CONTROLS HERE.
if (Input.GetKeyDown(KeyCode.Space) && onGround == true)
{
jumped = true;
while(jumped)
{
this.rigidbody2D.AddForce(new Vector2(0, jumpVelocity));
if (onGround) { jumped = false; }
}
}
else if (Input.GetKey(KeyCode.RightArrow))
{
this.transform.position += Vector3.right * speed * Time.deltaTime;
}
else if (Input.GetKey(KeyCode.LeftArrow))
{
this.transform.position += Vector3.left * speed * Time.deltaTime;
}
}
void OnCollisionEnter2D(Collision2D col)
{
if(col.gameObject.tag == "floor") { onGround = true; }
}
void OnCollisionStay2D(Collision2D col)
{
if(col.gameObject.tag == "floor") { onGround = true; }
}
}
Your problem stems from a misunderstanding how the Update method and physics work. If you do this in the update method, it will create an endless loop:
while(jumped)
{
this.rigidbody2D.AddForce(new Vector2(0, jumpVelocity));
if(onGround)
{
jumped = false;
}
}
The thing is that you tell the physics body to add a force. But you keep doing so over and over again. The physics simulation only takes place after the Update method returns, so whatever "onGround" is it will never become true because the forces aren't being applied until after the Update method.
Instead you have to make this check over and over again every time the Update method runs until onGround is true.