Project Type | Persoanl project |
Software Used | Unreal Engine 4 |
Languages Used | C++, Blueprints |
Primary Role(s) | Gameplay, A.I and Multiplayer programming |
// This is the .h class UPawnSensingComponent; UENUM(BlueprintType) enum class AIState : uint8 { Idle, Suspecious, Alerted, Patroling }; UCLASS() class FPSGAME_API AFPSAIGuard : public ACharacter { GENERATED_BODY() public: // Sets default values for this character's properties AFPSAIGuard(); protected: // Called when the game starts or when spawned virtual void BeginPlay() override; UFUNCTION() void OnPawnSeen(APawn* SeenPawn); UFUNCTION() void OnNoiseHeard(APawn* HeardInstigator, const FVector& Location, float Volume); UFUNCTION() void ResetOrientation(); UFUNCTION() void OnRep_GuardState(); UFUNCTION(BlueprintImplementableEvent, Category = "AI") void OnStateChanged(AIState NewState); void SetGuardState(AIState NewState); void MoveToNextPatrolPoint(); protected: UPROPERTY(VisibleAnywhere, Category = "Component") UPawnSensingComponent* SensingComponent = nullptr; UPROPERTY() FRotator OriginalRotation; UPROPERTY() FTimerHandle TimerHandle_ResetOrientation; UPROPERTY(ReplicatedUsing=OnRep_GuardState) AIState GuardState; ///* Let the guard go on patrol */ UPROPERTY(EditInstanceOnly, Category = "AI") bool bPatrol; ///* First of two patrol points to patrol between */ UPROPERTY(EditInstanceOnly, Category = "AI", meta = (EditCondition = "bPatrol")) AActor* FirstPatrolPoint; ///* Second of two patrol points to patrol between */ UPROPERTY(EditInstanceOnly, Category = "AI", meta = (EditCondition = "bPatrol")) AActor* SecondPatrolPoint = nullptr; //// The current point the actor is either moving to or standing at AActor* CurrentPatrolPoint; public: // Called every frame virtual void Tick(float DeltaTime) override; };
// Sets default values AFPSAIGuard::AFPSAIGuard() { // Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; SensingComponent = CreateDefaultSubobject(TEXT("Sensing Component")); SensingComponent->OnSeePawn.AddDynamic(this, &AFPSAIGuard::OnPawnSeen); SensingComponent->OnHearNoise.AddDynamic(this, &AFPSAIGuard::OnNoiseHeard); GuardState = AIState::Patroling; } void AFPSAIGuard::MoveToNextPatrolPoint() { if (CurrentPatrolPoint == nullptr || CurrentPatrolPoint == SecondPatrolPoint ) { CurrentPatrolPoint = FirstPatrolPoint; } else { CurrentPatrolPoint = SecondPatrolPoint; } UAIBlueprintHelperLibrary::SimpleMoveToActor(GetController(), CurrentPatrolPoint); } // Called when the game starts or when spawned void AFPSAIGuard::BeginPlay() { Super::BeginPlay(); OriginalRotation = GetActorRotation(); if (bPatrol) { MoveToNextPatrolPoint(); } } void AFPSAIGuard::OnPawnSeen(APawn* SeenPawn) { if (!SeenPawn) { return; } AFPSGameMode* MyGameMode = Cast (GetWorld()->GetAuthGameMode()); if (!MyGameMode) { return; } MyGameMode->MissionComplete(SeenPawn, false); SetGuardState(AIState::Alerted); AController* MyController = GetController(); if (MyController) { MyController->StopMovement(); } } void AFPSAIGuard::OnNoiseHeard(APawn* HeardInstigator, const FVector& Location, float Volume) { if (GuardState == AIState::Alerted) { return; } UE_LOG(LogTemp, Warning, TEXT("Hear Component: %s"), *HeardInstigator->GetName()); DrawDebugSphere(GetWorld(), Location, 34.0f, 12, FColor::Red, false, 10.0f); FVector Direction = Location - GetActorLocation(); Direction.Normalize(); FRotator NewLookAt = FRotationMatrix::MakeFromX(Direction).Rotator(); NewLookAt.Pitch = 0.0f; NewLookAt.Roll = 0.0f; SetActorRotation(NewLookAt); GetWorldTimerManager().ClearTimer(TimerHandle_ResetOrientation); GetWorldTimerManager().SetTimer(TimerHandle_ResetOrientation, this, &AFPSAIGuard::ResetOrientation, 3.0f); SetGuardState(AIState::Suspecious); AController* MyController = GetController(); if (MyController) { MyController->StopMovement(); } } void AFPSAIGuard::ResetOrientation() { SetActorRotation(OriginalRotation); SetGuardState(AIState::Patroling); if (bPatrol) { MoveToNextPatrolPoint(); } } void AFPSAIGuard::OnRep_GuardState() { OnStateChanged(GuardState); } void AFPSAIGuard::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME(AFPSAIGuard, GuardState); } void AFPSAIGuard::SetGuardState(AIState NewState) { if (GuardState != NewState) { GuardState = NewState; } OnRep_GuardState(); } // Called every frame void AFPSAIGuard::Tick(float DeltaTime) { Super::Tick(DeltaTime); if (CurrentPatrolPoint) { FVector DeltaLocation = GetActorLocation() - CurrentPatrolPoint->GetActorLocation(); float DistanceToGoal = DeltaLocation.Size(); if (DistanceToGoal < 200) { MoveToNextPatrolPoint(); } } }
AFPSGameMode::AFPSGameMode() { // set default pawn class to our Blueprinted character static ConstructorHelpers::FClassFinderIn this pic we use the on mission complete event from c++ in blueprint to manage the mission complete status whether the player won or lost.PlayerPawnClassFinder(TEXT("/Game/Blueprints/BP_Player")); DefaultPawnClass = PlayerPawnClassFinder.Class; // use our custom HUD class HUDClass = AFPSHUD::StaticClass(); // Setting Game State GameStateClass = AFPSGameState::StaticClass(); } void AFPSGameMode::MissionComplete(APawn* InstigatorPawn, bool bMissionSuccess) { if (!InstigatorPawn) { return; } TArray FetchedActors; UGameplayStatics::GetAllActorsOfClass(this, Spectating, FetchedActors); if (FetchedActors.Num() > 0) { AActor* NewSpectateView = nullptr; NewSpectateView = FetchedActors[0]; for (FConstPlayerControllerIterator it = GetWorld()->GetPlayerControllerIterator(); it; it++) { APlayerController* PC = it->Get(); if (PC) { PC->SetViewTargetWithBlend(NewSpectateView, 0.5f, EViewTargetBlendFunction::VTBlend_Cubic); } } } AFPSGameState* GS = GetGameState (); if (GS) { GS->MulticastOnMissionCompleted(InstigatorPawn, bMissionSuccess); } // Blueprint Implementable Event in ue4 OnMissionCompleted(InstigatorPawn, bMissionSuccess); }
// THIS IS the Header file UCLASS() class FPSGAME_API AFPSGameState : public AGameStateBase { GENERATED_BODY() public: // To ensure that an RPC call is executed on the remote machine, you can specify the Reliable keyword UFUNCTION(NetMulticast, Reliable) void MulticastOnMissionCompleted(APawn* InestigatorPawn, bool bMissionSuccess); };
// CPP File void AFPSGameState::MulticastOnMissionCompleted_Implementation(APawn* InestigatorPawn, bool bMissionSuccess) { for (FConstPlayerControllerIterator it = GetWorld()->GetPlayerControllerIterator(); it; it++) { AFPSPlayerController* PlayerController = Cast(it->Get()); if (PlayerController && PlayerController->IsLocalController()) { PlayerController->OnMissionComplete(InestigatorPawn, bMissionSuccess); // Disable Input APawn* MyPawn = PlayerController->GetPawn(); if (MyPawn) { MyPawn->DisableInput(PlayerController); } } } }
// Sets default values AFPSBlackHole::AFPSBlackHole() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; SceneComponent = CreateDefaultSubobject(TEXT("SceneComponent")); RootComponent = SceneComponent; MeshComp = CreateDefaultSubobject (TEXT("MeshComp")); MeshComp->SetCollisionEnabled(ECollisionEnabled::NoCollision); InnerSphereComponent = CreateDefaultSubobject (TEXT("InnerSphereComp")); InnerSphereComponent->SetSphereRadius(100); // Bind to Event OuterSphereComponent = CreateDefaultSubobject (TEXT("OuterSphereComp")); OuterSphereComponent->SetSphereRadius(3000); // Setting Up Attachment MeshComp->SetupAttachment(SceneComponent); InnerSphereComponent->SetupAttachment(SceneComponent); OuterSphereComponent->SetupAttachment(SceneComponent); } // Called every frame void AFPSBlackHole::Tick(float DeltaTime) { Super::Tick(DeltaTime); OuterSphereComponent->GetOverlappingComponents(Boxes); for (UPrimitiveComponent* Box : Boxes) { Box->AddRadialForce(GetActorLocation(), OuterSphereComponent->GetScaledSphereRadius(), -2000.0f, ERadialImpulseFalloff::RIF_Constant, true); } }
// Sets default values AFPSLaunchPad::AFPSLaunchPad() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; BoxComp = CreateDefaultSubobject(TEXT("Box Component")); StaticMeshComp = CreateDefaultSubobject (TEXT("Mesh Component")); RootComponent = StaticMeshComp; BoxComp->SetupAttachment(RootComponent); BoxComp->OnComponentBeginOverlap.AddDynamic(this, &AFPSLaunchPad::LaunchOverlapedObjects); LaunchPitchAngle = 35.5f; LaunchStrength = 1500.0f; } void AFPSLaunchPad::LaunchOverlapedObjects(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) { FRotator LaunchDirection = GetActorRotation(); LaunchDirection.Pitch += LaunchPitchAngle; FVector LaunchVelocity = LaunchDirection.Vector() * LaunchStrength; ACharacter* MyCharacter = Cast (OtherActor); if (MyCharacter) { MyCharacter->LaunchCharacter(LaunchVelocity, true, true); //Spawn FX UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ActivateLaunchPadEmitter, GetActorLocation()); } else if (OtherComp && OtherComp->IsSimulatingPhysics()) { OtherComp->AddImpulse(LaunchVelocity, NAME_None, true); UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ActivateLaunchPadEmitter, GetActorLocation()); } }