My goal is to make a single collision detection that will decrease the movement speed of the object it collided with for a specific duration.
What I tried so far:
//Class that detects the collision
if (other.gameObject.tag == "enemy")
{
EnemyMovement enemyMove = other.GetComponent <EnemyMovement> ();
if (enemyMove.slowMove != 1) {
return;
}
enemyMove.Slow (2, 0.5f, true);
//....
//Class that handles the Enemy-Movement
//The slowfactor gets multiplied with the Vector3 that handles the movementspeed of the Character
void FixedUpdate ()
{
Movement();
}
void Movement()
{
gegnerRigid.MovePosition (transform.position + (gegnerMove * slowMove));
}
public void Slow (float duration, float slowFactor, bool slowed)
{
if (slowed)
{
if (duration > 0) {
duration -= Time.deltaTime;
slowMove = slowFactor;
Slow (duration, slowFactor, slowed); //this recursive Call leads to huge performance issues, but how to keep the function going?
} else {
slowMove = 1;
slowed = false;
}
}
}
So what I wanted to happen:
Call the Slow-function if collision happens and make it invoke itself until the duration is 0.
Note, the key here is that
1. You have the buff/unbuff on the other object.
You just call to the other object from the 'boss' object. Do not put the actual buff/unbuff code in your 'boss' object. Just "call for a buff".
In other words: always have buff/unbuff code on the thing itself which you are buffing/unbuffing.
2. For timers in Unity just use "Invoke" or "invokeRepeating".
It's really that simple.
A buff/unbuff is this simple:
OnCollision()
{
other.GetComponent<SlowDown>().SlowForFiveSeconds();
}
on the object you want to slow...
SlowDown()
{
void SlowForFiveSeconds()
{
speed = slow speed;
Invoke("NormalSpeed", 5f);
}
void NormalSpeed()
{
speed = normal speed;
}
}
If you want to "slowly slow it" - don't. It's impossible to notice that in a video game.
In theory if you truly want to "slowly slow it"...
SlowDown()
{
void SlowlySlowForFiveSeconds()
{
InvokeRepeating("SlowSteps", 0f, .5f);
Invoke("NormalSpeed", 5f);
}
void SlowSteps()
{
speed = speed * .9f;
}
void NormalSpeed()
{
CancelInvoke("SlowSteps");
speed = normal speed;
}
}
It's that simple.
Related
I know the problem is simple, but I'm new to Unity and don't understand how to do it. I would like for there to be a delay between the ability to cause time dilation and the deceleration itself to last no more than a certain time.
{
void Start ()
{
}
void FixedUpdate ()
{
if (Input.GetKey (KeyCode.Mouse1))
{
Time.timeScale = 0.1f;
Time.fixedDeltaTime = Time.timeScale * 0.01f;
}
else
{
Time.timeScale = 1f;
}
}
}
By looking at your code i would totally suggest you to watch and study more tutorials on unity. I will still write down a simple delay logic with comments.
private float _lastTimeAbilityUsed;
private float _abilityCooldown = 2f; // cooldown for 2 seconds
private void Update()
{
if(_lastTimeAbilityUsed < Time.time - _abilityCooldown) return; // if not enough time has passed, returning
if (Input.GetKey (KeyCode.Mouse1))
{
_lastTimeAbilityUsed = Time.time; // saving the time when ability is used
}
}
First off, I am quite new to scripting so there's probably going to be a few flaws in my script.
So basically, I've made a script for the power up, but once my shot or the player touches the power up coin the fire rate does increase however it won't go back to the normal fire rate after 5 seconds... I have no idea what might be the cause, any advice would be helpful!
using UnityEngine;
using System.Collections;
public class FireRatePowerUp : MonoBehaviour {
private bool isPowerUp = false;
private float powerUpTime = 5.0f;
private PlayerShoot playerShoot;
private void Start()
{
playerShoot = PlayerShoot.FindObjectOfType<PlayerShoot>();
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "Player" || collision.gameObject.tag == "Projectile")
{
StartCoroutine(PowerUpTime());
isPowerUp = true;
Destroy(gameObject);
if (collision.gameObject.tag == "Projectile")
{
Destroy(collision.gameObject);
}
}
}
IEnumerator PowerUpTime()
{
playerShoot.fireRate -= 0.13f;
yield return new WaitForSeconds(powerUpTime);
playerShoot.fireRate += 0.13f;
}
}
I think the issue here is that you're destroying the gameobject this script is attached to (the coin) and by so doing, the script itself is destroyed, therefor its code, coroutine or otherwise won't execute.
StartCoroutine(PowerUpTime());
isPowerUp = true;
Destroy(gameObject); //oops, our script has been destroyed :(
You would have to do this very differently, basically moving the bulk of the code to the PlayerShoot class.
Something like this (this being in PlayerShoot.cs)
public void ActivatePowerupFireRate(float time, float amt) {
StartCoroutine(DoActivatePowerupFireRate(time, amt));
}
public IEnumerator ActivatePowerupFireRate(float time, float amt) {
fireRate -= amt;
yield return WaitForSeconds(time);
fireRate += amt;
}
IEumerator is definately one of the ways you can solve this issue.
However I'm not a fan of them here's my solution if you have a timer in game.
public int timePassed = 0;
public int gotPowerUp = 0;
void Start(){
InvokeRepeating("Timer", 0f, 1.0f);
//Starting at 0 seconds, every second call Timer function.
}
void Timer(){
timePassed++; // +1 second.
}
That way when you obtained the powerup you can set gotPowerUp = timePassed. So you have the exact time when powerup is activated.
then you do something like
if( (gotPowerUp + 5) == timePassed ){
//5 seconds have passed.
//deactivate powerup here
}
How to change a variable in Unity3D just for 5 seconds or until a certain event? For example, changing velocity:
void Start ()
{
GetComponent<Rigidbody2D> ().velocity = Vector3.zero;
}
void Update ()
{
if (Input.GetKey(KeyCode.W))
{
goUp ();
}
}
public void goUp() {
GetComponent<Rigidbody2D> ().velocity = new Vector2 (0, 10);
}
Since A is pressed, object goes up all the time. How to make object go up not all the time, but every frame while A is pressed? And when A is not pressed, object's velocity goes back to zero. I used to make it this way:
void Start () {
GetComponent<Rigidbody2D> ().velocity = Vector3.zero;
}
void Update () {
GetComponent<Rigidbody2D> ().velocity = Vector3.zero;
if (Input.GetKey(KeyCode.A)) {
goUp ();
}
}
public void goUp() {
GetComponent<Rigidbody2D> ().velocity = new Vector2 (0, 10);
}
But this method is not rational and overloads CPU. Also, I can't use Input.GetKeyUp(KeyCode.A) or "else" statement as a trigger.
Take a look at CoRoutines in Unity/C#:
http://docs.unity3d.com/Manual/Coroutines.html
This may do exactly what you need. I have put some pseudo code below if I understand your issue correctly.
if (Input.GetKeyDown("a")) {
goUp();
StartCoroutine("ReduceSpeedAfter5Seconds");
}
IEnumerator ReduceSpeedAfter5Seconds() {
GetComponent<Rigidbody2D> ().velocity = new Vector2 (0, 10);
yield return new WaitForSeconds(5.0f);
}
I would use x as a property like if it was speed. I'll just psudeo code it..
On your Update() for each frame you only care about Speed in this case so you just call it. You don't have to constantly check for other values because those should be handled by other events.
Property Speed get;set;
Void APressed()
{
Speed = 5;
}
Void AReleased()
{
Speed = 0;
}
Sorry I forgot the second part after 5 seconds..
You could add in something like this, If Unity supports the async keyword.. You would then update your A pressed to look like this
Void APressed()
{
Speed = 5;
ReduceSpeedAfter5Seconds();
}
public async Task ReduceSpeedAfter5Seconds()
{
await Task.Delay(5000);
Speed = 0;
}
have you tried this?
if (Input.GetKey(KeyCode.A)) {
goUp ();
}
else
{
GetComponent<Rigidbody2D> ().velocity = Vector3.zero;
}
My goal: To get my snail to seem like it's moving on its own.
Example: Going right for a few seconds, going left for a few seconds, staying in one place for a few seconds.
Current Status: Snail sits still in one place when I attempt to use WaitForSeconds
Without WaitForSeconds, my snail changes directions back and forth successfully(Except doing this really fast)
I've only started learning Unity and C# yesterday. Any tips/suggestions would be of much help, even if it's not on my original question.
If there's anything else I can do to help you help me, let me know! :)
using UnityEngine;
using System.Collections;
public class SnailMove : MonoBehaviour {
void Start()
{
}
// Use this for initialization
void Update ()
{
Waiting();
}
// Update is called once per frame
void Movement ()
{
int direct = Random.Range(-1, 2);
if (direct == 1)
{
transform.Translate(Vector3.left * 1f * Time.deltaTime);
transform.eulerAngles = new Vector2(0,180);
}
if (direct == -1)
{
transform.Translate(Vector3.left * 1f * Time.deltaTime);
transform.eulerAngles = new Vector2(0,0);
}
}
IEnumerator Waiting()
{
Movement ();
yield return new WaitForSeconds (5);
}
}
Coroutines need to be started with StartCoroutine. From your description it sounds like you want something like this:
using UnityEngine;
using System.Collections;
public class SnailMove : MonoBehaviour
{
public float speed = 1f;
int direction = 0;
void Start()
{
StartCoroutine(ChangeDirection());
}
void Update ()
{
if (direction == 1)
{
transform.Translate(Vector3.left * speed * Time.deltaTime);
transform.eulerAngles = new Vector2(0,180);
}
else if (direction == -1)
{
transform.Translate(Vector3.left * speed * Time.deltaTime);
transform.eulerAngles = new Vector2(0,0);
}
}
IEnumerator ChangeDirection()
{
while(true)
{
yield return new WaitForSeconds (5);
direction = Random.Range(-1, 2); // returns "-1", "0" or "1"
}
}
}
Good day I believe that your problem is that you are calling waiting in your update method that gets called on every frame regardless of previous executions, this implies that only the thread that called waiting before waits and as such the method can be called again as the other thread only waits.
try this:
using UnityEngine;
using System.Collections;
private bool waiting = false;
public class SnailMove : MonoBehaviour {
void Start()
{
}
// Use this for initialization
void Update ()
{
if(waiting == false)
{
Waiting();
}
}
// Update is called once per frame
void Movement ()
{
int direct = Random.Range(-1, 2);
if (direct == 1)
{
transform.Translate(Vector3.left * 1f * Time.deltaTime);
transform.eulerAngles = new Vector2(0,180);
}
if (direct == -1)
{
transform.Translate(Vector3.left * 1f * Time.deltaTime);
transform.eulerAngles = new Vector2(0,0);
}
}
IEnumerator Waiting()
{
Movement ();
waiting = true;
yield return new WaitForSeconds (5);
waiting = false;
}
}
This will make the update function wait for the boolean waiting to be set to false before it will call the function again.
Your logical flow is a little confused. You start the Waiting() coroutine every frame, when you probably want to call it once (in Start() for example) and loop your coroutine. The result is you have a whole bunch of Waiting() coroutines running and all competing with each other.
You have two options here:
void Start() {
StartCoroutine(Waiting());
}
IEnumrator Waiting() {
while(true) {
Movement();
yield return new WaitForSeconds(5f);
}
}
I got nauseated typing that though, because while(true) is a horrible idea. Better would be to track time in Update() and call Movement() every 5 seconds, like so:
private float timer = 0f;
void Update() {
timer += Time.deltaTime;
if (timer > 5f) {
Movement();
timer -= 5f;
}
}
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.