But not without a fight...
A bit of gameplay video + code snippet
Sunday was to be the big day when I finally killed the first skeleton. Then I moved on to have skeletons fight back and kill players.
Still the AI simply walks around randomly while checking to see if any players are in melee range and then stops to hit them.
It actually surprised me how easy it was to make the skeletons attack and how few lines of code it took. Having the engine handle the death of a player character, "respawn" him (resetting health and position) and then making sure messages are sent correctly to the client was actually more of a challenge: even though that also took only a few lines of code it needed a bit more tinkering that I expected.
I'm thinking about trying out a behaviour tree for the AI but I really don't think I need it yet.
Instead I'm structuring my behaviour code in such a way that it can be converted to a tree with only a little work. Explicit methods are a bit easier to work with and I also expect them to be quite a bit faster. But code reuse is harder because the "tree" formed by the calling structure is hardcoded. While reuse of branching is just as easy you can't just make a new tree based on an old one by substituting a few child branches.
An example of the simple AI - a custom Behaviour class that is attached to an Entity - already somewhat structurally prepared for added pathfinding logic.
Notice this is server code, but I still have an animation class. It has a duration and an Animate() method that is called each game loop as long as it isn't finished. No actual "animation" actually takes place on the server of course but a running animation stops the player from thinking, and also enables running code at specified points of time during the animation. That way weapon attacks can be implemented that do hit checks halfway through the swing - or at the end - or do collision checks every frame while taking care not to hit the same entity twice.
In addition to MeleeAnimation I also had to implement BeenHitAnimation (simply prevents the player from thinking or acting while it plays) and DeathAnimation.
[source]
public override void Think(GameUpdate update)
{
if (Attack(update))
return;
if (MoveToPlayer(update))
return;
if (_entity.NextGeometry.Speed == 0 || _entity.Geometry.Pos.Equals(_entity.Geometry.Destination) && (_entity.Path == null || _entity.Path.Count == 0))
{
Point2D newDestination = GetRandomPosition();
try
{
paths++;
_entity.Path = _entity.Section.Spatial.ConnectivityIndex.GetShortestPathAStarWithGridLineOfSightMaxLengthGridReflex(_entity.Geometry.Pos, newDestination, maxDistance);
if (_entity.Path == null)
failedPaths++;
}
catch (Exception ex)
{
Console.WriteLine(_entity.Geometry.Pos + "->" + newDestination);
Console.WriteLine(ex);
}
_entity.NextGeometry.Speed = _entity.MaxSpeed;
}
}
private bool MoveToPlayer(GameUpdate update)
{
return false;//TODO: Implement
}
private bool Attack(GameUpdate update)
{
Entity victim = FindVictim();
if (victim == null)
return false;
_entity.TurnTo(victim.Geometry.Pos);
_entity.Animation = new MeleeAnimation(_entity);
return true;
}
private Entity FindVictim()
{
var victims = _entity.Section.Spatial.GetEntities(_entity.Geometry.Pos, _entity.AttackRange);
foreach (var victim in victims)
{
//check if enemy is targetable. Don't hit yourself...
if (victim == _entity || victim.IsDead)
continue;
if (victim is PlayerCharacter)
return victim;
}
return null;
}
private Point2D GetRandomPosition()
{
var grid = _entity.Section.Spatial.GridIndex;
int x = (int)(_entity.Geometry.Pos.X);
int y = (int)(_entity.Geometry.Pos.Y);
int xMin = Math.Max(x - maxDistance, grid.xMin);
int yMin = Math.Max(y - maxDistance, grid.yMin);
int xMax = Math.Min(x + maxDistance, grid.xMax);
int yMax = Math.Min(y + maxDistance, grid.yMax);
Point2D pos;
do
{
pos = new Point2D(r.Next(xMin, xMax), r.Next(yMin, yMax));
} while ( VectorMath.DistanceSquared(_entity.Geometry.Pos, pos) > maxDistance * maxDistance ||
!grid.IsOnMesh(pos));
return pos;
}
[/source]
I also went ahead and bought the Barbarian RPG pack from InfinityPBR - a big thank you to Fidelum Games for pointing me in that direction.
Mecanim is totally new for me and a bit confusing so I haven't worked on incorporating the characters into my game but it's gonna be great when it happens.
Looks like you're making quick progress, good job! And you're welcome :D. Thanks for the mention.
What are you using as an engine?
P.S. Holy cow that's a long method name: GetShortestPathAStarWithGridLineOfSightMaxLengthGridReflex(). Glad I'm not the only one who favors this kind of thing :p