Hello,
I have a problem with enemy AI. He’s walking towards player but when he’s 4-5 steps near him, he stops moving smoothly and he’s ‘jumping’ when A* path is being updated. The bug doesn’t occur when player is standing still on his starting position.
Here is script for controlling enemy AI and for his movement using raycasts:
using UnityEngine;
using System.Collections;
using Pathfinding;
[RequireComponent(typeof(MovementController))]
public class MeleeSkeletonController : MonoBehaviour
{
//Player position, enemy will follow to this point.
public Transform playerPos;
//Enemy starting posistion and maximum distance from this point untill he'll start going back
[HideInInspector]
public Vector3 startingPos;
private float distanceToStartGoingBack = 30f;
private Vector3 distanceTolerance = new Vector3(0.1f, 0.1f, 0f);
private float gravity = -10f;
//Refresh delay for pathfinding player is equal = 1/updateRate
public float updateRate = 2f;
//Getting components
private Seeker seeker;
private Animator anim;
private SpriteRenderer sr;
private MovementController movController;
//Calculating path
public Path path;
public float speed; //Enemy movement speed
[HideInInspector]
bool pathIsEnded = false; //Checking if enemy reached end of his path
[HideInInspector]
bool goingBack = false; //Checking if enemy going back to his starting point (spawn)
public float nextWayPointDistance = 1; //Maximal enemy distance from his next waypoint
private int currentWayPoint = 0; //Current waypoint toward which enemy is moving
/*End of variables******************************************************************************************************************************/
void Awake()
{
playerPos = GameObject.FindGameObjectWithTag("Player").transform;
startingPos = gameObject.transform.position;
}
void Start()
{
seeker = GetComponent<Seeker>();
anim = GetComponent<Animator>();
sr = GetComponent<SpriteRenderer>();
movController = GetComponent<MovementController>();
if (playerPos == null)
{
Debug.LogError("Brak gracza!");
return;
}
//Seeking path - player position, enemy position and calling method
seeker.StartPath(transform.position, playerPos.position, OnPathComplete);
StartCoroutine(UpdatePath());
}
IEnumerator UpdatePath() //Main script which searches for path toward player
{
if (playerPos == null)
{
playerPos = GameObject.FindGameObjectWithTag("Player").transform;
}
seeker.StartPath(transform.position, playerPos.position, OnPathComplete);
yield return new WaitForSeconds(1f / updateRate); //Waiting for next method call. Refresh rate is equal to 1 / updateRate
if (goingBack) yield return null; //Stopping method if GoingBackPath() courutine has started
else if (!goingBack) StartCoroutine(UpdatePath()); //Calling coroutine again
}
IEnumerator GoingBackPath() //Script which searches for path for enemy, who is going back to his spawn point TODO - GoingBackPath = delete?
{
seeker.StartPath(transform.position, startingPos, OnPathComplete);
yield return new WaitForSeconds(1f / updateRate); //Waiting
if (goingBack == false) yield return null; //If enemy has stopped going back to his spawn
else if (goingBack) StartCoroutine(GoingBackPath()); //Calling again
}
public void OnPathComplete(Path p) //Function called to get another path after UpdatePath() and Start()
{
//Debug.Log("Jest błąd? : " + p.error); //Unneceserry - huge spam in Log
if (!p.error)
{
path = p; //Passing variable p to "path" that is being used for pathfinding
currentWayPoint = 0; //Choosing initial index (0)
}
}
void FixedUpdate()
{
//If enemy is too far from his spawn, he starts going back to it //TODO - GoingBack = delete?
if (Vector3.Distance(transform.position, startingPos) > distanceToStartGoingBack && goingBack == false)
{
Debug.Log("Rozpoczęcie powrotu przez przeciwnika");
goingBack = true;
StartCoroutine(GoingBackPath());
}
/* Calculating paths and controlling movement toward player */
if (playerPos == null)
{
playerPos = GameObject.FindGameObjectWithTag("Player").transform;
}
if (path == null)
return;
if (currentWayPoint >= path.vectorPath.Count)
{
if (pathIsEnded)
return;
//Debug.Log("Koniec ścieżki");
pathIsEnded = true;
return;
}
pathIsEnded = false;
//Direction of enemy movement
Vector3 dir = (path.vectorPath[currentWayPoint] - transform.position).normalized;
Debug.Log(dir);
Debug.Log(Mathf.Sign(dir.x));
if (dir.x != 0)
dir = new Vector3(Mathf.Sign(dir.x), 0f, 0f);
dir.x *= speed * Time.fixedDeltaTime;
movController.Move(dir);
float distanceToWaypoint = Vector3.Distance(transform.position, path.vectorPath[currentWayPoint]);
if (distanceToWaypoint < nextWayPointDistance)
{
currentWayPoint++;
return;
}
//Gravity
dir.y += gravity * Time.fixedDeltaTime;
/* Animations */
if (dir.x != 0)
{
dir.x = Mathf.Sign(dir.x);
anim.SetBool("isMoving", true);
anim.SetFloat("dirX", dir.x);
}
else
{
anim.SetBool("isMoving", false);
}
}
}
And another script:
using UnityEngine;
using System.Collections;
[RequireComponent (typeof(BoxCollider2D))]
public class MovementController : MonoBehaviour
{
/* Structures */
struct RaycastOrigins
{
public Vector2 topLeft, topRight;
public Vector2 bottomLeft, bottomRight;
}
public LayerMask collisionMask;
public struct CollisionInfo
{
public bool above, below;
public bool left, right;
public void Reset()
{
above = below = false;
left = right = false;
}
}
/* Variables */
const float skinWidth = 0.015f;
public int horizontalRayCount = 4;
public int verticalRayCount = 4;
float horizontalRaySpacing;
float verticalRaySpacing;
BoxCollider2D boxCol2d;
RaycastOrigins raycastOrigins;
public CollisionInfo collisions;
void Start ()
{
boxCol2d = GetComponent<BoxCollider2D>();
CalculateRaySpacing();
}
public void Move(Vector3 velocity)
{
UpdateRaycastOrigins();
collisions.Reset();
if (velocity.x != 0)
{
HorizontalCollisions(ref velocity);
}
if (velocity.y != 0)
{
VerticalCollisions(ref velocity);
}
transform.Translate(velocity);
}
void HorizontalCollisions(ref Vector3 velocity)
{
float directionX = Mathf.Sign(velocity.x);
float rayLength = Mathf.Abs(velocity.x) + skinWidth;
for (int i = 0; i < verticalRayCount; i++)
{
Vector2 rayOrigin = (directionX == -1) ? raycastOrigins.bottomLeft : raycastOrigins.bottomRight;
rayOrigin += Vector2.up * (horizontalRaySpacing * i + velocity.y);
RaycastHit2D hit = Physics2D.Raycast(rayOrigin, Vector2.right * directionX, rayLength, collisionMask);
if (hit)
{
velocity.x = (hit.distance - skinWidth) * directionX;
rayLength = hit.distance;
collisions.left = directionX == -1;
collisions.right = directionX == 1;
Debug.DrawRay(rayOrigin, Vector2.right * directionX * hit.distance, Color.green);
}
}
}
void VerticalCollisions(ref Vector3 velocity)
{
float directionY = Mathf.Sign(velocity.y);
float rayLength = Mathf.Abs(velocity.y) + skinWidth;
for (int i = 0; i < verticalRayCount; i++)
{
Vector2 rayOrigin = (directionY == -1) ? raycastOrigins.bottomLeft : raycastOrigins.topLeft;
rayOrigin += Vector2.right * (verticalRaySpacing * i + velocity.x);
RaycastHit2D hit = Physics2D.Raycast(rayOrigin, Vector2.up * directionY, rayLength, collisionMask);
if (hit)
{
velocity.y = (hit.distance - skinWidth) * directionY;
rayLength = hit.distance;
collisions.below = directionY == -1;
collisions.above = directionY == 1;
Debug.DrawRay(rayOrigin, Vector2.up * directionY * rayLength, Color.green);
}
}
}
void UpdateRaycastOrigins()
{
Bounds bounds = boxCol2d.bounds;
bounds.Expand(skinWidth * -2);
raycastOrigins.bottomLeft = new Vector2(bounds.min.x, bounds.min.y);
raycastOrigins.bottomRight = new Vector2(bounds.max.x, bounds.min.y);
raycastOrigins.topLeft = new Vector2(bounds.min.x, bounds.max.y);
raycastOrigins.topRight = new Vector2(bounds.max.x, bounds.max.y);
}
void CalculateRaySpacing()
{
Bounds bounds = boxCol2d.bounds;
bounds.Expand(skinWidth * -2);
horizontalRayCount = Mathf.Clamp(horizontalRayCount, 2, int.MaxValue);
verticalRayCount = Mathf.Clamp(verticalRayCount, 2, int.MaxValue);
horizontalRaySpacing = bounds.size.y / (horizontalRayCount - 1);
verticalRaySpacing = bounds.size.x / (verticalRayCount - 1);
}
}
Thank you in advance.