I don't know where I went wrong here, I'm trying to make a random choice attack system, well kind of but instead it's not waiting and at all for anything but probably for the first wait time. The results I'm looking for is: wait for amount of seconds then pick a random attack then change color and go back to original color then repeat process
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BossMovement : MonoBehaviour
{
// changes color
public Color originalColor;
public Transform spawnPoint;
// weapons
//choses a Attackmove to do
int AttackChoice;
public float distanceStop;
public float distanceR;
/// changes the material of the boss
private Material mymaterial;
//animator
public Animator anim;
public float distance;
public float distance3;
// player
public Transform target;
// how fast we are going
public float speed = 4f;
public float speedR;
Rigidbody rig;
// Start is called before the first frame update
private void Start()
{
rig = GetComponent<Rigidbody>();
anim = GetComponentInChildren<Animator>();
}
private void Update()
{
whento();
StopFollowing();
StartCoroutine(AttackChoices());
}
// Update is called once per frame
void followPlayer()
{
var step = speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(rig.transform.position, target.position, step);
}
IEnumerator AttackChoices()
{
yield return new WaitForSeconds(15);
AttackChoice = Random.Range(0, 4);
if (AttackChoice == 1)
{
gameObject.GetComponentInChildren<Renderer>().material.color = Color.white;
yield return new WaitForSeconds(15);
gameObject.GetComponentInChildren<Renderer>().material.color = originalColor;
}
if (AttackChoice == 2)
{
gameObject.GetComponentInChildren<Renderer>().material.color = Color.blue;
yield return new WaitForSeconds(15);
gameObject.GetComponentInChildren<Renderer>().material.color = originalColor;
}
if (AttackChoice == 3)
{
gameObject.GetComponentInChildren<Renderer>().material.color = Color.red;
yield return new WaitForSeconds(15);
gameObject.GetComponentInChildren<Renderer>().material.color = originalColor;
}
if (AttackChoice == 4)
{
yield return new WaitForSeconds(15);
}
}
void whento()
{
// follow player
transform.LookAt(target);
//raycast that shoots at player
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.forward, out hit, distance))
{
if (hit.transform.CompareTag("Player"))
{
followPlayer();
anim.SetFloat("Speed", 1, 0f, Time.deltaTime);
}
}
else
{
anim.SetFloat("Speed", 0, 0.1f, Time.deltaTime);
}
}
void StopFollowing()
{
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.forward, out hit, distance3))
{
if (hit.transform.CompareTag("Player"))
{
distance = distanceStop;
}
}
else
{
distance = distanceR;
}
}
private void ChargeUpAndRealse()
{
anim.SetLayerWeight(anim.GetLayerIndex("ChargeLayer"), 1);
anim.SetTrigger("Charge");
anim.SetLayerWeight(anim.GetLayerIndex("ChargeLayer"), 0);
}
}
CodePudding user response:
You are doing
StartCoroutine(AttackChoices());
EVERY FRAME within Update
starting thousands of concurrent running routines. A Coroutine does not delay the code which is using StartCoroutine
.
You rather need to
- make sure to run only one routine, wait for i to finish, then start the next one
- or simply use a
while
loop
Like e.g.
private void Start()
{
rig = GetComponent<Rigidbody>();
anim = GetComponentInChildren<Animator>();
StartCoroutine(AttackChoices());
}
private void Update()
{
whento();
StopFollowing();
}
void followPlayer()
{
var step = speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(rig.transform.position, target.position, step);
}
IEnumerator AttackChoices()
{
while(true)
{
yield return new WaitForSeconds(15);
AttackChoice = Random.Range(0, 4);
.....
}
}
instead of true
you could of course also have a certain flag field you can disable when you want to terminate the routine
Then note that
Random.Range(0, 4);
will return one of 0, 1, 2, 3
as the upper bound is exclusive for the int
overload!
In general for better performance you should cache things and use a switch
:
IEnumerator AttackChoices()
{
var material = GetComponentInChildren<Renderer>().material;
while(true)
{
yield return new WaitForSeconds(15);
AttackChoice = Random.Range(0, 4);
switch(AttackChoice)
{
case 0:
yield return ColorAndReset(Color.white);
break;
case 1:
yield return ColorAndReset(Color.blue);
break;
case 2:
yield return ColorAndReset(Color.red);
break;
case 3:
yield return new WaitForSeconds(15);
break;
}
}
}
private static IEnumerator ColorAndReset(Material material, Color color)
{
material.color = color;
yield return new WaitForSeconds(15);
material.color = originalColor;
}
And a last general note: If you are dealing with Rigidbody
(=> Physics) you do not want to do anything via transform
directly but rather only go through the Rigibody
component! See e.g.
Rigibody.position
for gettingRigibody.MovePosition
or setting wihout jumping
Also you want to do things in FixedUpdate
(the Physics frame) in order to not break / fight against the Physics engine resulting in undesired behavior and breaking collision detection
Little hint here: The rigidbody alternative to
transform.forward
would be e.g.
rb.rotation * Vector3.forward