Rewriting the AI Controller in C++

Published September 29, 2015
Advertisement
Hi everyone!

So it's been a while since i last posted a blog entry. It has been an extremely busy time for me, tweaking and improving the performance and design of my game. I have finally moved on from using Blueprint for parts of my game to using C++, where i feel much more at home and it feels more like "proper" programming.

Part of this has been the rewrite of my AI controller, which manages all enemy types which attack the player. The Blueprint version had become a complete spaghetti mess, partly due to the fact that unreal is still new territory to me, and secondly due to the fact that the language is supposed to look a bit like spaghetti to start with, nodes all over the screen connected by linked 'wires'.

I settled upon a finite state machine as my AI implementation. There is a decision tree system already built into unreal engine 4, but implementing my AI using this means treading water in the "GUI-designed-program-code" pool again, so i decided that it would be easier and more readable just to use C++ for it.

This new AI controller is designed to tie in with the new sword-and-shield combat techniques that are now part of the game, as the player is able to dodge, block, and jump as well as backpeddle:



The AI controller has several states:enum AggressiveState{ SSOD_AGGRESSIVE_STATE_PATROL, SSOD_AGGRESSIVE_STATE_RANDOM_WALK, SSOD_AGGRESSIVE_STATE_FLEEING, SSOD_AGGRESSIVE_STATE_BACKOFF, SSOD_AGGRESSIVE_STATE_FOUND_PLAYER, SSOD_AGGRESSIVE_STATE_GETTING_CLOSER_FOR_ATTACK, SSOD_AGGRESSIVE_STATE_ATTACKING, SSOD_AGGRESSIVE_STATE_BLOCKING, SSOD_AGGRESSIVE_STATE_DODGING};
The AI starts out in one of the first two states "Patrol" or "Random Walk" depending on if it has a set of patrol waypoints set up in its properties or not. If it has a set of waypoints (a simple array of ATargetPoint actors) then it will walk around these waypoints in a loop without stopping. If it does not have a set of patrol waypoints, instead it simply walks to random navigable locations within a defined patrol radius:void AGenericAggressiveAI_v2::BeginMoveToNextPatrolPoint(){ if (me) { ATargetPoint* nextwaypoint = me->Waypoints[PatrolIndex++]; MoveToLocation(nextwaypoint->GetActorLocation(), 30.f); if (PatrolIndex == me->Waypoints.Num()) PatrolIndex = 0; }} void AGenericAggressiveAI_v2::BeginMoveToLocation(){ if (me) { MoveToLocation(UNavigationSystem::GetRandomPointInNavigableRadius(this, HomeLocation, me->PatrolRadius), 30.f); }}
The AI character has three movement speeds, starting off in its "Walk" speed (1.0 times the Maximum Walk Rate), then "Jog" speed (1.8 times the maximum walk rate) and finally the "Run" state (2.5 times the maximum walk rate). The AI can decide to walk, jog, or run depending upon what it is doing. Dodging attacks is done at the running speed, whilst trying to get close to the player is done at jogging speed. Patrolling and random movement is always done at normal walking speed.

For simple communication with the rest of the game, there are several delegated events attached to the AI controller:UPROPERTY(BlueprintAssignable) FBeginAttackingPlayerDelegate BeginAttackingPlayer; UPROPERTY(BlueprintAssignable) FStopAttackingPlayerDelegate StopAttackingPlayer; UPROPERTY(BlueprintAssignable) FTriggerAggroDelegate TriggerAggro; UPROPERTY(BlueprintCallable) FPlayerIsAttackingDelegate PlayerIsAttacking;
The BeginAttackingPlayer is called by the AI controller when we are close enough to launch a melee attack against the player. This happens once, then once the attack is complete the AI controller once again tries to get close to the player for another attack, repeating the process until itself or the player is dead.

The TriggerAggro event is called when the AI has spotted the player for the first time. It triggers the aggro sound and animation (character specific) in the character class.

The PlayerIsAttacking event is called by the blueprint portion of the code when the player tries to attack an AI combatant. This is picked up by the AI, and if there is any immediate risk to the AI character (e.g. they are close enough to the player to be harmed) then the AI character will try to backpeddle out of reach of the player, then regroup to try again:void AGenericAggressiveAI_v2::BeginBackOff(){ if (me) { FVector Direction = me->GetActorLocation() - TrackedCharacter->GetActorLocation(); FVector BackOffPosition = TrackedCharacter->GetActorLocation() + 2.5f * Direction; state = SSOD_AGGRESSIVE_STATE_BACKOFF; MoveToLocation(BackOffPosition, 30.f); }} void AGenericAggressiveAI_v2::OnPlayerAttacking(ACharacter* TrackedCharacter){ /* We receive this delegate when the player is executing an attack. * If we are close enough to the player that this might hurt us, * attempt to dodge it - backpeddle immediately. */ if (DistanceFromTrackedPlayer() <= me->GetCapsuleComponent()->GetScaledCapsuleRadius() * 20.0f) { me->GetCharacterMovement()->bOrientRotationToMovement = false; me->GetCharacterMovement()->bUseControllerDesiredRotation = true; Run(); StopMovement(); GetWorld()->GetTimerManager().SetTimer(MoveDelay, this, &AGenericAggressiveAI_v2::BeginBackOff, 0.1, false); }}
This provides for a level of intelligence which seems to be quite smart to the player, even though the base rules behind it are quite simple. Compared to the Blueprint based AI, the reactions are smoother, smarter, and more aggressive. Playing against an AI enemy, the results speak for themselves in this video:



Comments and feedback as always are welcome below smile.png
1 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement