Octorush
Octorush is a project that I and 10 others worked on for 4 weeks using the Unreal engine at Future games. The game is a time attack based where you must collect as many of your children as possible. Through revisions in the product, we scrapped a lot halfway through including the AI that I was working on. I instead got to work on the main interactable and game feel. In this project, I worked in the Unreal engine with mainly C++, but sometimes shifted things into blueprints for convinces of the group.
AI
The scraped AI was supposed to patrol until it saw or heard the player at which point it would chase them down until it came close enough where it would stop for a moment before rapidly throwing itself towards the player.
ALAIController::ALAIController()
{
LineTraceCollisionQueryParams = FCollisionQueryParams(SCENE_QUERY_STAT(LineTraceSingle), false);
}
void ALAIController::Tick(float Delattime)
{
Super::Tick(Delattime);
float Distance = (PlayerPawn->GetActorLocation() - ControlledPawn->GetActorLocation()).Size();
UCharacterMovementComponent* MovementComp = Cast<UCharacterMovementComponent>(ControlledPawn->GetMovementComponent());
if (ControlledPawn->GetGuardState() == EAIState::idle || ControlledPawn->GetGuardState() == EAIState::sleep)
{
return;
}
if (ControlledPawn->GetGuardState() == EAIState::charging)
{
MoveToLocation(FVector(ChargePoint.X,ChargePoint.Y, ControlledPawn->GetActorLocation().Z));//TODO check if its possible to move to player
if ((ControlledPawn->GetActorLocation() - FVector(ChargePoint.X, ChargePoint.Y, ControlledPawn->GetActorLocation().Z)).Size() < 50)
ControlledPawn->StartCooldown();
}
else
{
SoundCheck(Distance);
SightCheck(Distance);
//patroling
if (ControlledPawn->GetGuardState() == EAIState::patroling && ControlledPawn->GetVelocity().Size() == 0)
MoveToLocation(ControlledPawn->GetNextPatrolPoint());
}
}
void ALAIController::BeginPlay()
{
Super::BeginPlay();
ControlledPawn = Cast<AALGuard>(GetPawn());
PlayerPawn = Cast<AALCharacter>(GetWorld()->GetFirstPlayerController()->GetPawn());
LineTraceCollisionQueryParams.AddIgnoredActor(ControlledPawn);
}
void ALAIController::SightCheck(float Distance)
{
if (ControlledPawn->CanISeePlayer(PlayerPawn->GetActorLocation()) && ControlledPawn->GetGuardState() != EAIState::idle)
{
//TODO make less taxing on system
LineTraceCollisionQueryParams.AddIgnoredActor(ControlledPawn);
GetWorld()->LineTraceSingleByChannel(HitResult, ControlledPawn->GetActorLocation(),
PlayerPawn->GetActorLocation(), ECC_WorldStatic, LineTraceCollisionQueryParams);
if (HitResult.GetActor() == PlayerPawn)
{
ControlledPawn->SetGuardState(EAIState::chasing);
MoveToLocation(PlayerPawn->GetActorLocation());
}
if (Distance < ControlledPawn->GetChargRange() || ControlledPawn->GetIfCanCharge() == false)
ControlledPawn->GuardChargeWindUp(ChargePoint = PlayerPawn->GetActorLocation());
}
else
ControlledPawn->SetGuardState(EAIState::patroling);
}
void ALAIController::SoundCheck(float Distance)
{
if (ControlledPawn->GetIfCanHearPlayer())
{
if (PlayerPawn->FindComponentByClass<UALRadioactiveAbilityComponent>() != nullptr)
{
if (PlayerPawn->FindComponentByClass<UALRadioactiveAbilityComponent>()->IsOn == true)
{
ControlledPawn->TurnTowardsPlayer(PlayerPawn->GetActorLocation());
return;
}
}
if (PlayerPawn->ReturnIfSneaking() == false && PlayerPawn->ReturnIfSprinting() == false)
{
if (Distance <= ControlledPawn->GetSensingSphereRadius()/2.f)
ControlledPawn->TurnTowardsPlayer(PlayerPawn->GetActorLocation());
}
else if (PlayerPawn->ReturnIfSprinting() == true)
{
ControlledPawn->TurnTowardsPlayer(PlayerPawn->GetActorLocation());
}
}
}
AALGuard::AALGuard()
{
PrimaryActorTick.bCanEverTick = false;
SensingSphere = CreateDefaultSubobject<USphereComponent>(TEXT("Sensing Sphere"));
SensingSphere->SetSphereRadius(96);
SensingSphere->SetupAttachment(RootComponent);
SensingSphere->OnComponentBeginOverlap.AddDynamic(this, &AALGuard::OnSphereOverlap);
SensingSphere->OnComponentEndOverlap.AddDynamic(this, &AALGuard::OnSphereEndOverlap);
GetCapsuleComponent()->OnComponentHit.AddDynamic(this, &AALGuard::GuardHit);
GuardState = EAIState::patroling;
}
void AALGuard::BeginPlay()
{
Super::BeginPlay();
PatrolPoint = PatrolPoints.Num()-1;
StartPosition = GetActorLocation();
CanHearPlayer = false;
}
void AALGuard::SetGuardState(EAIState NewState)
{
if (GuardState == NewState) return;
GuardState = NewState;
}
EAIState AALGuard::GetGuardState()
{
return GuardState;
}
FVector AALGuard::GetNextPatrolPoint()
{
if (PatrolPoints[PatrolPoint] == nullptr)
return StartPosition;
if (!IsCharacterAtPatrolpoint())
return PatrolPoints[PatrolPoint]->GetActorLocation();
PatrolPoint++;
if (PatrolPoint <= PatrolPoints.Num() - 1)
return PatrolPoints[PatrolPoint]->GetActorLocation();
else
PatrolPoint = 0;
return PatrolPoints[PatrolPoint]->GetActorLocation();
}
bool AALGuard::IsCharacterAtPatrolpoint()
{
FVector Distance = PatrolPoints[PatrolPoint]->GetActorLocation() - GetActorLocation();
if (Distance.Size() < PatrolPointRadius)
return true;
else
return false;
}
bool AALGuard::CanISeePlayer(FVector PlayerPos)
{
FVector Distance = PlayerPos - GetActorLocation();
float DotProduct = FVector::DotProduct(Distance.GetSafeNormal(), GetActorForwardVector());
if (Distance.Size() > ViewMaxRange)
return false;
if (FMath::Acos(DotProduct) * (180 / 3.1415926) < FieldOfView / 2)
{
PlayerSpoted.Broadcast();
return true;
}
else
return false;
}
void AALGuard::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent,
AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult)
{
if (AALCharacter* Player = Cast<AALCharacter>(OtherActor))
CanHearPlayer = true;
}
void AALGuard::OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
if (AALCharacter* Player = Cast<AALCharacter>(OtherActor))
CanHearPlayer = false;
}
void AALGuard::DrawSightCone(float DotProduct)
{
FRotator Rot(0, 0, 0);
FVector Rotator = GetActorForwardVector()*ViewMaxRange;
DrawDebugLine(GetWorld(), GetActorLocation(),GetActorLocation() + GetActorForwardVector()*ViewMaxRange, FColor::Red, false, 0);
DrawDebugLine(GetWorld(), GetActorLocation(), GetActorLocation() + GetActorForwardVector()*ViewMaxRange, FColor::Red, false, 0);
}
bool AALGuard::GetIfCanHearPlayer()
{
return CanHearPlayer;
}
bool AALGuard::GetIfCanCharge()
{
return CanMove;
}
void AALGuard::TurnTowardsPlayer(FVector PlayerPos)
{
FVector Direction = PlayerPos - GetActorLocation();
Direction.Normalize();
FRotator NewLookAt = FRotationMatrix::MakeFromX(Direction).Rotator();
NewLookAt.Pitch = 0.0f;
NewLookAt.Roll = 0.0f;
SetActorRotation(NewLookAt);
}
float AALGuard::GetSensingSphereRadius()
{
return SensingSphere->GetUnscaledSphereRadius();
}
void AALGuard::GuardChargeWindUp(FVector PlayerPos)
{
GetController()->StopMovement();
TurnTowardsPlayer(PlayerPos);
if (!CanMove) return;
CanMove = false;
ChargeWindUp.Broadcast();
if (GetCharacterMovement()->MaxWalkSpeed != SlowSpeed)
GetCharacterMovement()->MaxWalkSpeed = ChargeSpeed;
GetWorldTimerManager().SetTimer(WindUpHandler, this, &AALGuard::GuardCharge, WindUpTime, false);
}
void AALGuard::StartSleep()
{
UE_LOG(LogTemp, Warning, TEXT("zZz zZz zZz"))
GetWorldTimerManager().SetTimer(SleepHandler, this, &AALGuard::EndSleep, SleepTime, false);
SetGuardState(EAIState::sleep);
}
void AALGuard::EndSleep()
{
SetGuardState(EAIState::patroling);
}
float AALGuard::GetChargRange()
{
return ChargeRange;
}
void AALGuard::GuardCharge()
{
if (GetGuardState() == EAIState::sleep) return;
SetGuardState(EAIState::charging);
CanMove = true;
ChargeStart.Broadcast();
}
void AALGuard::StartCooldown()
{
if (GetGuardState() == EAIState::idle) return;
GetWorldTimerManager().SetTimer(CooldownHandler, this, &AALGuard::EndCooldown, CooldownTime, false);
UE_LOG(LogTemp, Warning, TEXT("Cooldown has been started"))
if (GetCharacterMovement()->MaxWalkSpeed != SlowSpeed)
GetCharacterMovement()->MaxWalkSpeed = WalkSpeed;
SetGuardState(EAIState::idle);
CooldownStart.Broadcast();
}
void AALGuard::EndCooldown()
{
SetGuardState(EAIState::chasing);
CooldownEnd.Broadcast();
}
void AALGuard::StartSlow()
{
GetCharacterMovement()->MaxWalkSpeed = SlowSpeed;
}
void AALGuard::EndSlow()
{
GetCharacterMovement()->MaxWalkSpeed = WalkSpeed;
}
It was planned that the AI was supposed to take note of things that had changed in the environment because of the player. So, if an AI in its patrol sees a misplaced box it will move to the closest position of it and look around for other misplaced objects, rinse and repeat until the player is found or the trail leads to a dead end.
Oil Barrel interactable
The main interactable was the common red barrel. The player has a projectile which when hitting a barrel directly would cause an explosion but when hitting something else would cause a small black hole to appear that pulls objects towards it. If a barrel was caught in the black hole effect it would spill out oil on the floor. If the player then walks through the oil, they gain the oily status. Which for game feel makes the player move faster with more sliding movement, but gameplaywise makes the player able to traverse through tight spaces.
AALOilBarrel::AALOilBarrel()
{
BarrelMesh = CreateDefaultSubobject(TEXT("BarrelMesh"));
BarrelMesh->SetupAttachment(RootComponent);
BarrelHitBox = CreateDefaultSubobject(TEXT("BarrelHitBox"));
BarrelHitBox->SetupAttachment(BarrelMesh);
BarrelHitBox->OnComponentHit.AddDynamic(this, &AALOilBarrel::ExploadOnHit);
}
void AALOilBarrel::BeginPlay()
{
Super::BeginPlay();
StartPos = GetActorLocation();
LineTraceCollisionQueryParams.AddIgnoredActor(this);
}
void AALOilBarrel::ExploadOnHit(UPrimitiveComponent* HitComponent,
AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
if (AALProjectile* Projectile = Cast(OtherActor))
{
if (Projectile->IsExploding)
{
Expload();
return;
}
}
else if (AALCharacter* Player = Cast(OtherActor))
{
return;
}
else if (!IsEmpty && OtherActor->ActorHasTag(FName("Floor")))
{
SpawnOil();
IsEmpty = true;
}
}
void AALOilBarrel::SpawnOil()
{
FVector GLocation;
FRotator GRotation;
FindGround(GLocation,GRotation);
GetWorld()->SpawnActor(OilClass, GLocation, GRotation);
BPSpawnOil(GLocation,GRotation);
}
void AALOilBarrel::FindGround(FVector& GroundLocation, FRotator& GroundRotation)
{
GetWorld()->LineTraceSingleByChannel(OilTrace, GetActorLocation(),
GetActorLocation() + FVector(0, 0, -1) * 1000, ECC_WorldStatic, LineTraceCollisionQueryParams);
FName TagToLookFor = "Floor";
if (OilTrace.Actor == nullptr)
{
return;
}
if (OilTrace.Actor->ActorHasTag(TagToLookFor))
{
GroundLocation = OilTrace.Location;
GroundRotation = FRotator(0);
return;
}
else
{
AActor* HitActor = Cast(OilTrace.Actor);
LineTraceCollisionQueryParams.AddIgnoredActor(HitActor);
FindGround(GroundLocation, GroundRotation);
}
}