Reputation: 11
I'm working on implementing Context Steering movement for the enemy AI in my unity project and running into problems when it comes to determining the interest of each angle.
I have two classes: ContextMap and DirectionContext. DirectionContext is a data storage class. It contains an angle, a Vector3 that relates that angle into a direction to move in, and a float ranging from 0 to 1 depending on how interesting that angle is to move in.
ContextMap contains a list of DirectionContext depending on the "detail" of the contextmap. So if I set the detail to 4, it will generate 4 DirectionContexts, and if I set the detail to 8, it will generate 8 DirectionContexts. The angles go from -180, to +180.
What I want to do is write a function that determins the angle between the enemy gameobject and the player gameobject, then compare it to each DirectionContext in the context map. I want it to set the interest float for each DirectionContext depending on the similarity of the angle between the objects and the DirectionContexts angle. I want it to set interest to 1 if the directions are identical, and to set interest to 0 if the difference between the angles is a high as they can be (360.)
The issue I'm having, is my scripts aren't setting the interest for the angles correctly. If the Player is directly above the enemy, it works as expected, but if the player is to the diagonals or sides it starts working opposite.
Here's a picture of what's happening: The black object is the enemy, the red is the human. The lines are longer depending on how high the interest is.
As you can see in the image, if the directions are up/down then it works fine, but things get weird when it goes diagonal or to the sides.
Here's what I've come up with in code:
public class DirectionContext
{
[SerializeField] private Vector3 direction;
[SerializeField] private float interest;
[SerializeField] private float angle;
[SerializeField] private bool isExcluded = false;
public Vector3 GetDirection() { return direction; }
public float GetInterest() { return interest; }
public void SetInterest(float value) { interest = value; }
public void CheckInterestAndSet(float newInterest)
{
if (newInterest > interest)
interest = newInterest;
}
public float GetAngle() { return angle; }
public DirectionContext(float angle)
{
this.angle = angle;
this.direction = new Vector3(Mathf.Sin(angle * Mathf.Deg2Rad), Mathf.Cos(angle * Mathf.Deg2Rad), 0).normalized;
}
}
Context map:
public class ContextMap
{
[SerializeField] private List<DirectionContext> contextMap = new List<DirectionContext>();
public List<DirectionContext> GetContext()
{
return contextMap;
}
public void GenerateMap(int detail)
{
contextMap.Clear();
for (int i = 0; i < detail; i++)
{
float angle = (360 / detail * i) - 180;
contextMap.Add(new DirectionContext(angle));
}
}
public void UpdateContext(int index, float interest)
{
contextMap[index].CheckInterestAndSet(interest);
}
public void DebugDrawMap(Vector3 position, Color color)
{
foreach (DirectionContext direction in contextMap)
{
Debug.DrawLine(position, (position + direction.GetDirection()).normalized * direction.GetInterest());
}
}
public void Clear()
{
foreach (DirectionContext dir in contextMap)
dir.Clear();
}
}
Seek: (I left out SteeringContext as it's a super simple class. It just contains variables that may be interesting. The two used in seek are parent and targetDestination. TargetDestination is the players location, and parent is a reference to the enemy.
public class Seek : SteeringBehavior
{
public override ContextMap AddContext(ContextMap contextMap, SteeringContext steeringContext)
{
float angleToTarget = AngleBetween(Vector3.up, steeringContext.targetDestination - steeringContext.parent.transform.position);
//Debug.Log(angleToTarget);
for (int i = 0; i < contextMap.GetContext().Count; i++)
{
float delta = Mathf.Abs(Mathf.Max(angleToTarget, contextMap.GetContext()[i].GetAngle()) - Mathf.Min(angleToTarget, contextMap.GetContext()[i].GetAngle()));
if (delta > 180)
{
delta = 360f - delta;
}
float interest = 1f - delta / 180f;
contextMap.UpdateContext(i, interest);
}
return contextMap;
}
private float AngleBetween(Vector3 vector1, Vector3 vector2)
{
float sin = vector1.x * vector2.y - vector2.x * vector1.y;
float cos = vector1.x * vector2.x + vector1.y * vector2.y;
return Mathf.Atan2(sin, cos) * (180) / Mathf.PI;
}
}
And I make the call to Seek using this function:
private void PopulateInterestMap()
{
interestMap.Clear();
foreach (SteeringBehavior behavior in interestBehaviors)
{
interestMap = behavior.AddContext(interestMap, steeringContext);
}
}
Thank you for any help!
Upvotes: 0
Views: 65
Reputation: 11
I solved it. I'm going to answer my own question as there's no source code online that really goes into Context Steering behaviors, so maybe the code posted here will save somebody else the trouble I went through.
Now that I have it up and working, anybody messing around with steering behaviors I highly recommend looking into Context Steering (If you google there's some good blog posts and papers about it's benefits.) It totally fixed the jitter issues with my steering behaviors, and fixed all of the issues of blended behaviors cancelling themselves out in oddball situations.
The problem wasn't in determining the weight, but the part of DirectionContext where I convert the angle into a direction vector.
this.direction = new Vector3(Mathf.Sin(angle * Mathf.Deg2Rad), Mathf.Cos(angle * Mathf.Deg2Rad), 0).normalized;
Should have been
this.direction = new Vector3(Mathf.Cos(angle * Mathf.Deg2Rad), Mathf.Sin(angle * Mathf.Deg2Rad), 0);
The normalized isn't what ruined it, it was Cos vs Sin. Normalized was just unneccessary.
Upvotes: 1