public class ProjectileFlightTimeUntilDecay : MonoBehaviour
{
public float PlayerProjectile;
public float timeUntilDecay = 5.0f;
void Start()
{
timeUntilDecay = timeUntilDecay * Time.deltaTime;
}
// Update is called once per frame
void Update()
{
if(timeUntilDecay <= 0)
{
// Destroy the projectile
}
}
}
I tried this, and doesn't work.
timeUntilDecay is never decreased from its initial value, so the check if (timeUntilDecay <= 0) will never be true.
Typically this is done by decrementing it in Update(), e.g.
timeUntilDecay -= Time.deltaTime;
Related
I am trying to make a script that moves my GameObject for sometime and then stops it for 2 seconds.
But my code does make it run a while then stop but then it goes in an endless loop of not doing anything at all
Here is my code;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
public class Enemyai : MonoBehaviour
{
public Animator anim;
public int timer = 100000;
public int Abstimer = 100000;
public bool running = true;
public float speed = 5;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (timer==0)
{
timer = Abstimer;
anim.SetBool("isRunning", false);
running = false;
Invoke("attack", 2);
}
if (running == true)
{
anim.SetBool("isRunning", true);
this.transform.position += transform.right * -speed * Time.deltaTime;
timer--;
}
}
void attack()
{
running = true;
}
`
Well .. you know you currently make it wait for 100000 frames
=> with a normal frame rate of about 60 frames per second that makes about 1667 seconds - aka 27 Minutes - of waiting...
What you would rather do is
not use frame rate dependent code but seconds
use a reasonable amount of time ^^
Like e.g.
public class Enemyai : MonoBehaviour
{
public Animator anim;
// This would mean 3 seconds
public float Abstimer = 3f;
public float speed = 5;
void Update()
{
if (timer<=0)
{
timer = Abstimer;
anim.SetBool("isRunning", false);
running = false;
Invoke(nameof(attack), 2);
}
if (running)
{
this.transform.position -= transform.right * speed * Time.deltaTime;
timer-= Time.deltaTime:
}
}
void attack()
{
anim.SetBool("isRunning", true);
running = true;
}
}
Maybe easier to handle might be a Coroutine like e.g.
public class Enemyai : MonoBehaviour
{
public Animator anim;
// This would mean 3 seconds
public float Abstimer = 3f;
public float speed = 5;
private IEnumerator Start()
{
while(true)
{
yield return new WaitForSeconds(2);
anim.SetBool("isRunning", true);
for(var timer = 0f; timer < Abstimer; timer += Time.deltaTime)
{
transform.position -= transform.right * speed * Time.deltaTime;
yield return null;
}
anim.SetBool("isRunning", false);
}
}
}
Either way have in mind you still need to actually adjust the value of Abstimer in the Inspector not only in code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScrollingObject : MonoBehaviour
{
private Rigidbody2D rb2d;
// Start is called before the first frame update
void Start()
{
rb2d = GetComponent<Rigidbody2D>();
//if (GameControl.instance.score > 0)
// rb2d.velocity = new Vector2(GameControl.instance.scrollSpeed * GameControl.instance.score, 0);
//else
rb2d.velocity = new Vector2(GameControl.instance.scrollSpeed, 0);
}
// Update is called once per frame
void Update()
{
if (GameControl.instance.score > 5)
rb2d.velocity = new Vector2(GameControl.instance.scrollSpeed *3, 0);
if (GameControl.instance.score > 10)
rb2d.velocity = new Vector2(GameControl.instance.scrollSpeed*5, 0);
if (GameControl.instance.gameOver == true)
{
rb2d.velocity = Vector2.zero;
}
}
}
You already seem to have a central Singleton controller storing your values.
So instead of poll checking these values every frame I would rather implement it event based using properties and an event
public class GameControl : MonoBehaviour
{
// by default multiplier is 1
private float multiplier = 1;
// Configure here whatever your scrollSpeed is anyway
[SerializeField] private float _scrollSpeed = 20;
// A public read-only property which already has the multiplier applied
public float scrolSpeed => _scrollSpeed * multiplier;
// the actual score backing field
private int _score;
public int score
{
// when accessing the score property just return the internal _score value
get => _score;
// when assigning a new value to score
set
{
// update the backing field
_score = value;
// also update the multiplier
// use a base value of 1
// intentionally use an int division to increase multiplier by 2 for every 5 points
multiplier = 1 + 2 * score / 5;
// inform listeners that the score was changed
onScoreChanged?.Invoke();
}
}
// evnt whih is invoked everytime the score is changed
public event Action onScoreChanged;
...
}
and then you simply want to do
public class ScrollingObject : MonoBehaviour
{
[SerializeField] private Rigidbody2D rb2d;
private void Awake()
{
if(!rb2d) rb2d = GetComponent<Rigidbody2D>();
}
private void FixedUpdate()
{
rb2d.velocity = Vector2.right * GameControl.instance.scrollSpeed;
}
}
if you really need to constantly set the velocity for whatever reason.
Or even better do it all event based and only update the velocity once the score was actually changed:
public class ScrollingObject : MonoBehaviour
{
[SerializeField] private Rigidbody2D rb2d;
private void Awake()
{
if(!rb2d) rb2d = GetComponent<Rigidbody2D>();
// fetch the initial values once immediately
OnScoreChanged();
// attach a callback to the score changed event
GameControl.instance.onScoreChanged += OnScoreChanged;
}
private void OnDestroy()
{
// be sure to remove callbacks as soon as not needed anymore
GameControl.instance.onScoreChanged += OnScoreChanged;
}
// automatically called once every time the score was changed
private void OnScoreChanged()
{
// update the velocity
rb2d.velocity = Vector2.right * GameControl.instance.scrollSpeed;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ColumnPool : MonoBehaviour
{
public int columnPoolSize = 5;
public GameObject columnPrefab;
public float spawnRate = 1f;
public float columnMin = -1.5f;
public float columnMax = 1.5f;
private GameObject[] columns;
private Vector2 objectPoolPosition = new Vector2(-15f, -25f);
private float timeSinceLastSpawned;
private float spawnXPosition = 10f;
private int currentColumn = 0;
// Start is called before the first frame update
void Start()
{
columns = new GameObject[columnPoolSize];
for (int i = 0; i < columnPoolSize; i++)
{
columns[i] = (GameObject)Instantiate(columnPrefab, objectPoolPosition, Quaternion.identity);
}
}
// Update is called once per frame
void Update()
{
// timeSinceLastSpawned += Time.deltaTime;
//if (GameControl.instance.score > 5)
//timeSinceLastSpawned += Time.deltaTime * GameControl.instance.score;
//// timeSinceLastSpawned += Time.deltaTime;
//if (GameControl.instance.score % 5 == 0)
if ((GameControl.instance.score > 1 && GameControl.instance.score % 5 == 0))
{
timeSinceLastSpawned += Time.deltaTime;
}
// timeSinceLastSpawned += Time.deltaTime *1.0f;
//if (GameControl.instance.score > 5)
// timeSinceLastSpawned += Time.deltaTime * 2;
if (GameControl.instance.gameOver == false && timeSinceLastSpawned >= spawnRate)
{
timeSinceLastSpawned = 0;
float spawnYPosition = Random.Range(columnMin, columnMax);
columns [currentColumn].transform.position = new Vector2(spawnXPosition, spawnYPosition);
currentColumn++;
if (currentColumn >= columnPoolSize)
{
currentColumn = 0;
}
}
}
}
The issue is with the boolean isDodging. It is set to true in the Dodge() method. That should trigger an if statement in the Movement() method (called in FixedUpdate()) but that block is always skipped over. I'm attaching all of the class' code, because there must be something I'm missing here:
using UnityEngine;
public class MovementController
{
/* COMPONENTS */
public Rigidbody2D Rigidbody { private get; set; }
/* VARIABLES */
private bool isDodging = false;
private Vector2 dodgeDirection = Vector2.right;
private float dodgeDuration = 1f;
private float dodgeSpeed = 20f;
private float timer = 0f;
/* METHODS */
// Called in fixed update (since it's dealing with physics)
public void Movement(Vector2 currentPosition, Vector2 velocity)
{
Debug.Log("In movement: " + isDodging);
if (isDodging)
{
Debug.Log("Dodge 3");
Move(currentPosition, dodgeDirection * dodgeSpeed);
timer += Time.fixedDeltaTime;
if (timer >= dodgeDuration)
{
Debug.Log("Stopped dodging " + Time.fixedTime);
isDodging = false;
}
}
else
{
Move(currentPosition, velocity);
}
}
private void Move(Vector2 currentPosition, Vector2 velocity)
{
if (Rigidbody == null)
{
Debug.LogWarning("No rigidbody to move!");
return;
}
Rigidbody.MovePosition(currentPosition + (velocity * Time.fixedDeltaTime));
}
// Must be called while Movement is being called
public void Dodge(Vector2 direction, float maxSpeed, float speedMultiplier = 2f, float duration = 1f)
{
if (direction == Vector2.zero) { return; }
Debug.Log("Dodge 1 " + isDodging);
dodgeDirection = direction;
dodgeDuration = duration;
dodgeSpeed = maxSpeed * speedMultiplier;
isDodging = true;
Debug.Log("Dodge 2" + isDodging + Time.fixedTime);
timer = 0f;
}
}
The thing is, the "In movement: " log always shows isDodging as false, and the if block under it never runs. Meanwhile, "Dodge 2" will show true (as isDodging is changed right above it). And the weirdest: "Dodge 1" shows false the first time Dodge() is called, but true everytime its called after that - as if isDodging was changed to true in the class scope, and Movement() doesn't recognize that for some reason.
Both this functions are called in a separate MonoBehaviour:
public class CreatureMovement : MonoBehaviour
{
[Header("Movement")]
[SerializeField] protected Vector2Reference moveDirection;
[SerializeField] protected FloatReference maxSpeed;
[Header("Dodge")]
[SerializeField] private FloatReference dodgeDuration;
[SerializeField] private FloatReference dodgeSpeedMultiplier;
[Header("References")]
[SerializeField] private new Rigidbody2D rigidbody;
private readonly MovementController movement = new MovementController();
public float MaxSpeed { get => maxSpeed; }
private float speed;
private float Speed { get => speed; set => speed = Mathf.Clamp(value, 0, maxSpeed); }
public virtual Vector2 Velocity
{
get => moveDirection.Value * Speed;
set
{
moveDirection.SetValue(value.normalized);
Speed = value.magnitude;
}
}
private void Start() => movement.Rigidbody = rigidbody;
private void FixedUpdate() => movement.Movement(transform.position, Velocity);
public void Dodge() => movement.Dodge(moveDirection, maxSpeed, dodgeSpeedMultiplier, dodgeDuration);
}
Where Dodge() is called from player input.
Except for dodging, movement is ocurring exactly as expected. The problem probably isn't in the Move() method, as it doesn't have isDodging in it.
I have absolutey no idea why this is happening, the code seems so simple to me, but it just isn't working. Please help out with this.
You're calling Dodge on a prefab instead of the scene instance of that prefab which is running CreatureMovement.FixedUpdate.
I believe you can verify this by placing this code in Dodge (Source: Max-Pixel):
if (gameObject.scene.name == null) Debug.Log("It's a prefab!");
You need to change your input processing to call Dodge on the instance in the scene instead of a prefab.
You can do that by dragging the instance in the scene into the button onclick event, then selecting Dodge. Or, if you are spawning the object dynamically, you could, in Start, find a reference to the button, and add Dodge to its onClick listeners:
private void Start()
{
movement.Rigidbody = rigidbody;
// something along the lines of this...
Button buttonRef = GameObject.Find("ButtonName").GetComponent<Button>();
buttonRef.onClick.AddListener(Dodge);
}
Why is this connecting all the health bars of my enemies together, even though their actual health is decreasing at its specified rate?
public class FillHealth : MonoBehaviour
{
Image HealthBar;
private NormalMonster normalMonster;
// Start is called before the first frame update
void Start()
{
HealthBar = GetComponent<Image>();
normalMonster = GameObject.FindGameObjectWithTag("Normal Monster").GetComponent<NormalMonster>();
}
// Update is called once per frame
void Update()
{
UpdateHealthLeft();
}
public void UpdateHealthLeft()
{
if (normalMonster.healthLeft > 0)
{
HealthBar.fillAmount = normalMonster.healthLeft / normalMonster.setHealth;
}
}
}
This is the script that is being referenced in FillHealth. As far as I understand it, since the variable isn't static, then the values should not be shared. It should find fill the health bar for each individual enemy.
public class NormalMonster : MonoBehaviour
{
private float _normalSpeed = 2f;
private float _BaseHealth = 20f;
private float _HealthModifier;
public float setHealth;
public float healthLeft;
// Start is called before the first frame update
void Start()
{
UpdateHealth();
}
// Update is called once per frame
void Update()
{
NormMonMov();
}
public void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Arrows")
{
healthLeft -= Arrows.Damage;
Destroy(other.gameObject);
if (healthLeft <= 0f)
{
Destroy(this.gameObject);
EarnedGold.earnedGold += 7;
Spawn_Manager.enemyCount--;
}
}
}
public void UpdateHealth()
{
if (StageMode.StageLvl > 5)
{
_HealthModifier = (StageMode.StageLvl * 0.01f) * _BaseHealth;
setHealth = Mathf.Round(_BaseHealth + _HealthModifier);
}
else
{
setHealth = _BaseHealth;
}
healthLeft = setHealth;
}
public void NormMonMov()
{
transform.Translate(Vector3.left * _normalSpeed * Time.deltaTime);
transform.position = new Vector3(Mathf.Clamp(transform.position.x, -7.0f, 10), transform.position.y, 0);
}
}
Any help would be greatly appreciated for this guy who just start playing with unity this weekend.
I believe the issue is with normalMonster = GameObject.FindGameObjectWithTag("Normal Monster").GetComponent<NormalMonster>();
If you have two Monsters
Both have two scripts attached, FillHealth and NormalMonster
Both the "FillHealth" scripts look for the FIRST gameobject in the scene that has a script with tag NormalMonster so both monsters are pointing to the exact same NormalMonster script (the first in the list)
Change "GameObject" capital G to "gameObject" lower case g
Still not the best way to code this, but that may work I think
Instead of getting the image, get the rect transform, like this
public RectTransform healthBar;
and change the length with:
healthBar.sizeDelta = new Vector2(normalMonster.healthLeft,healthBar.sizeDelta.y);
I have a waypoint system in a Unity 2D project where the GameObject will follow each waypoint in order and then repeat the process, i am wanting the GameObject to stop at each point for a fixed amount of time and i thought i could achieve this using a coroutine but not entirely sure of how to achieve this, what i have done so far is create a coroutine called WaitAtPoint and then call this on each waypoint movement but to no avail, not sure what i am doing wrong.
public class BrainBoss : MonoBehaviour {
[SerializeField]
Transform[] waypoints;
[SerializeField]
float moveSpeed = 2f;
int waypointIndex = 0;
// Start is called before the first frame update
void Start()
{
transform.position = waypoints[waypointIndex].transform.position;
}
// Update is called once per frame
void Update()
{
Move();
}
void Move()
{
transform.position = Vector2.MoveTowards(transform.position,
waypoints[waypointIndex].transform.position, moveSpeed * Time.deltaTime);
if(transform.position == waypoints[waypointIndex].transform.position)
{
StartCoroutine(WaitAtPoint());
waypointIndex += 1;
}
if(waypointIndex == waypoints.Length)
{
waypointIndex = 1;
}
}
IEnumerator WaitAtPoint()
{
yield return new WaitForSeconds(3f);
}
}
You are calling Move every update and you could also be calling that StartCoroutine multiple times so i suggest using a variable to see if you should even update the movement
public class BrainBoss : MonoBehaviour
{
[SerializeField]
Transform[] waypoints;
[SerializeField]
float moveSpeed = 2f;
int waypointIndex = 0;
private bool shouldMove = true;
// Start is called before the first frame update
void Start() {
transform.position = waypoints[waypointIndex].transform.position;
}
// Update is called once per frame
void Update() {
if (this.shouldMove) {
Move();
}
}
void Move() {
transform.position = Vector2.MoveTowards(transform.position,
waypoints[waypointIndex].transform.position, moveSpeed * Time.deltaTime);
if (transform.position == waypoints[waypointIndex].transform.position) {
StartCoroutine(WaitAtPoint(3));
waypointIndex += 1;
}
if (waypointIndex == waypoints.Length) {
waypointIndex = 1;
}
}
IEnumerator WaitAtPoint(int seconds) {
this.shouldMove = false;
int counter = seconds;
while (counter > 0) {
yield return new WaitForSeconds(1);
counter--;
}
this.shouldMove = true;
}
}
You can use a simble bool flag to know if you should move or not. In Move (or Update), check for that bool to know if you should move or not.
In WaitAtPoint, set the bool (like shouldWait) to true, then back to false after the WaitForSecond !
Well, your WaitAtPoint is not doing an awful lot at the moment. This is because it is waiting inside the IEnumerator, not where you are calling it.
There are various ways to tackle this, but I would suggest using a callback on your IEnumerator which is executed after the waiting time.
Like this:
private bool isWaiting;
void Update() {
if (!isWaiting) {
Move();
}
}
void Move()
{
transform.position = Vector2.MoveTowards(transform.position,
waypoints[waypointIndex].transform.position, moveSpeed * Time.deltaTime);
if(transform.position == waypoints[waypointIndex].transform.position)
{
StartCoroutine(WaitAtPoint(() =>
{
// All code that should be executed after waiting here.
waypointIndex += 1;
}));
}
if(waypointIndex == waypoints.Length)
{
waypointIndex = 1;
}
}
IEnumerator WaitAtPoint(Action callback)
{
isWaiting = true;
yield return new WaitForSeconds(3f);
callback.Invoke();
isWaiting = false;
}