2D Using C# moving game object back and forth and random stop in between - all in x-axis
Hi all,
I'm currently learning on how to use C# script to randomly spawn enemy objects that always spawn from right side of the screen in x-axis. After spawn, it will move towards left side of x-axis until it reach a coordinate specified in my C# script, it will turn around and move different direction. This is working fine but, I would like to have the random spawned enemy objects to pause/stop in between the x-axis after 5 seconds before it continue to move on...
Any advise?
Current code implementation:
public class EnemyA : MonoBehaviour {
private Vector3 MovingDirection = Vector3.left; //initial movement direction
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
UpdateMovement ();
}
void UpdateMovement(){
if (this.transform.position.x > 2.4f) {
MovingDirection = Vector3.left;
gameObject.GetComponent<SpriteRenderer> ().flipX = true;
} else if (this.transform.position.x < -2f) {
MovingDirection = Vector3.right;
gameObject.GetComponent<SpriteRenderer> ().flipX = false;
}
this.transform.Translate (MovingDirection * Time.smoothDeltaTime);
}
}
FYI, the animator of the said game object representing the enemy AI is having the default as walking (instead of idle). Hence, the code above contains flipX changes.
Answer by KenAragorn · Mar 07, 2017 at 03:22 AM
Ok, here is the concept and then follow by sample codes of that works well - allowing objects to move from point A to point B and at the same time stop in between ...but this code have a fix waiting time for each stops.
Let say you want to have 2 stop points along x-axis (exclude spawning/starting point), create 2 empty game objects. Name it accordingly, such as point1 and point2. If you want more stopping points, create more. For this example, let say you just need 2.
Create another empty game object. Lets say we call this Enemy1Stuff. The previous game character/enemy character, just drag it to be subset/under this new empty game object. Do the same for the 2 points (point1 and point2).
This will looks like this in your Hierarchy: Enemy1Stuff ->Enemy ->point1 ->point2
Set the coordinates for point1 and point2 accordingly ...let say point 1 is the 1st stopping point (0,0,0) and 2nd stopping point (-3,0,0).
Add/Change the Enemy script to following:
using UnityEngine;
using System.Collections;
public class Enemy : MonoBehaviour {
private const int ANIM_STATE_IDLE = 0;
private const int ANIM_STATE_WALKING = 1;
private const int ANIM_STATE_ATTACK = 2;
public Transform[] patrolPoints;
public float speed;
public float distance;
int currentPoint;
Animator anim;
SpriteRenderer sprite;
// Use this for initialization
void Start () {
anim = GetComponent<Animator> ();
sprite = GetComponent<SpriteRenderer> ();
anim.SetInteger ("state",ANIM_STATE_WALKING); //to force initial animation to walking
StartCoroutine ("Patrol");
}
// Update is called once per frame
void Update () {
RaycastHit2D hit = Physics2D.Raycast (transform.position, transform.localScale.x * Vector3.left, distance);//needed to check in front of him got any object
if (hit.collider != null && hit.collider.tag == "Player") {
//GetComponent<Rigidbody2D> ().AddForce (Vector3.up * 100);
anim.SetInteger ("state", ANIM_STATE_ATTACK);
StopCoroutine ("Patrol");
}
}
IEnumerator Patrol(){
while (true) { //this is to make sure this method is infinite loop
if (this.transform.position.x == patrolPoints [currentPoint].position.x) {
currentPoint++;
anim.SetInteger ("state", ANIM_STATE_IDLE);
yield return new WaitForSeconds (2); //once it rech the point, stop for a while 1st
anim.SetInteger ("state",ANIM_STATE_WALKING);
}
if (currentPoint >= patrolPoints.Length) {
currentPoint = 0; //reset it to zero
}
this.transform.position = Vector2.MoveTowards (this.transform.position, new Vector2 (patrolPoints [currentPoint].position.x, transform.position.y),speed);
if (transform.position.x > patrolPoints [currentPoint].position.x) {
sprite.flipX = true;
//transform.localScale = new Vector3 (1,1,1); //either 1 of this method will change the rotation of the object
} else if (transform.position.x < patrolPoints [currentPoint].position.x) {
sprite.flipX = false;
//transform.localScale = new Vector3 (-1,1,1);
}
yield return null; //this will tells this method to execute the above code again once next frame started
}
}
void OnTriggerEnter2D(Collider2D coll){
}
void OnDrawGizmos(){
Gizmos.color = Color.red; //for visual debugging purpose
Gizmos.DrawLine(transform.position,transform.position + transform.localScale.x * Vector3.left * distance);
}
}
You can ignore the RayCast and Rigidbody as those are optional for this...just part of the incomplete code I want to share it here.
Back to Unity editor, drag the point1 and point2 to the Enemy Script in Inspector. Hope this helps ...for those need it for future reference.
Answer by Tourist · Mar 03, 2017 at 02:37 PM
In the Start method, keep a float value that you initialize to 0. In the Update method, add Time.deltaTime to this float value and then check if the value is grearter than 5. If it is, do not call UpdateMovement, else call UpdateMovement.
As for the animations, add a boolean parameter "IsMoving" in your animator. Add a transition from Idle to Moving states when the parameter is set to true. Add a transition from Moving to Idle states when the parameter is set to false. Set the moving state as default.
In Update method, in the condition you added, set the parameter to true or false as wanted.
The enemy will spawn, move as before and after 5 seconds, he will stay idle.
Hi @Tourist, thanks for the tips. For the first part, I tried it and the object is still moving without any pausing/stopping. Do hope my code is right.
The updated code: public class WolfEnemy : $$anonymous$$onoBehaviour { private Vector3 $$anonymous$$ovingDirection = Vector3.left; //initial movement direction private float timer;
// Use this for initialization
void Start () {
timer = 0;
}
// Update is called once per frame
void Update () {
timer = Time.deltaTime;
if (timer > 5) {
return;
} else {
Update$$anonymous$$ovement ();
}
}
void Update$$anonymous$$ovement(){
if (this.transform.position.x > 2.4f) {
$$anonymous$$ovingDirection = Vector3.left;
gameObject.GetComponent<SpriteRenderer> ().flipX = true;
} else if (this.transform.position.x < -2f) {
$$anonymous$$ovingDirection = Vector3.right;
gameObject.GetComponent<SpriteRenderer> ().flipX = false;
}
this.transform.Translate ($$anonymous$$ovingDirection * Time.smoothDeltaTime);
}
}
Your issue is when you update the timer. You set timer to Time.deltaTime, but Time.deltaTime is just the duration between the last frame and this frame, so he will never be up to 5. You should change to : timer += Time.deltaTime.
@Tourist, yes you're right as I miss out and miss-understood the Time.deltaTime logic. But, after I make the changes, although the game object started to move back, it will stop once it reach the starting of its spawning point.
While trying to look around in the net, I found another solution that used multiple transform objects, coroutine + WaitForSeconds that give a more flexible in adding more 'stopping points' easily (through Transform object) and WaitForSeconds method. Will share the sample code of it here.
Also, when I debug the timer value from timer = Time.deltaTime inside the Update() method, the range is always within 0.006xxxxxx to max around 0.3333333...for the 1st spawn object, but as more of the same instance been spawned, this range slightly increase.
Your answer
Follow this Question
Related Questions
How do I get a character to walk on walls and ceilings? 1 Answer
How do I fix my FPS controller from sliding? 0 Answers
my box movements look jitter, how to fix it 0 Answers
How do I make an object move in the direction its facing? 1 Answer
How to apply Character Movement Controls to a skeletal rig? 0 Answers