This is my second game programmed with with C++. Consists of eliminating all the enemies without being eliminated.
This project started following an udemy course made by GameDev.tv Team and Ben Tristem. (One of the best selling courses about c++ for Unreal Engine).
Still being a simple project, but was useful to go a bit further and program more complex mechanics.
Unreal 5.2
1
2 weeks
Programmer
PC
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InputActionValue.h"
#include "ShooterCharacter.generated.h"
class UInputMappingContext;
class UInputAction;
class AGun;
UCLASS()
class SIMPLESHOOTER_API AShooterCharacter : public ACharacter
{
GENERATED_BODY()
public:
AShooterCharacter();
protected:
virtual void BeginPlay() override;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
UInputMappingContext* ShooterMappingContext;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
UInputAction* MoveAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
UInputAction* LookAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
UInputAction* JumpAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
UInputAction* ShootAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
UInputAction* CrouchAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
UInputAction* RunAction;
public:
UFUNCTION(BlueprintPure)
bool IsDead() const;
UFUNCTION(BlueprintPure)
float GetHealthPercent() const;
virtual void Tick(float DeltaTime) override;
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
virtual float TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, AActor* DamageCauser) override;
void ShootBullet();
private:
void Shoot(const FInputActionValue& Value);
void Move(const FInputActionValue& Value);
void Look(const FInputActionValue& Value);
void Crouch(const FInputActionValue& Value);
void Uncrouch(const FInputActionValue& Value);
UPROPERTY(EditAnywhere)
float MaxHealth = 100;
UPROPERTY(VisibleAnywhere)
float Health;
UPROPERTY(EditDefaultsOnly)
TSubclassOf<AGun> WeaponClass;
UPROPERTY()
AGun* Weapon;
UCharacterMovementComponent* MovementComponent = this->GetCharacterMovement();;
};
#include "Characters/ShooterCharacter.h"
#include "EnhancedInputSubsystems.h"
#include "EnhancedInputComponent.h"
#include "Weapons/Gun.h"
#include "Components/CapsuleComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "SimpleShooter/SimpleShooterGameModeBase.h"
// Sets default values
AShooterCharacter::AShooterCharacter()
{
// 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;
}
// Called when the game starts or when spawned
void AShooterCharacter::BeginPlay()
{
Super::BeginPlay();
APlayerController* PlayerController = Cast<APlayerController>(GetController());
if (PlayerController)
{
UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer());
if (Subsystem)
{
Subsystem->AddMappingContext(ShooterMappingContext, 0);
}
}
Health = MaxHealth;
Weapon = GetWorld()->SpawnActor<AGun>(WeaponClass);
GetMesh()->HideBoneByName(TEXT("weapon_r"), EPhysBodyOp::PBO_None);
Weapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, TEXT("WeaponSocket"));
Weapon->SetOwner(this);
}
bool AShooterCharacter::IsDead() const
{
return Health <= 0;
}
float AShooterCharacter::GetHealthPercent() const
{
return Health / MaxHealth;
}
// Called every frame
void AShooterCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Called to bind functionality to input
void AShooterCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
{
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AShooterCharacter::Move);
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AShooterCharacter::Look);
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ACharacter::Jump);
EnhancedInputComponent->BindAction(ShootAction, ETriggerEvent::Triggered, this, &AShooterCharacter::Shoot);
EnhancedInputComponent->BindAction(CrouchAction, ETriggerEvent::Triggered, this, &AShooterCharacter::Crouch);
EnhancedInputComponent->BindAction(CrouchAction, ETriggerEvent::Completed, this, &AShooterCharacter::Uncrouch);
}
}
float AShooterCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
float DamageToApply = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
DamageToApply = FMath::Min(Health, DamageToApply);
Health -= DamageToApply;
UE_LOG(LogTemp, Warning, TEXT("Health left %f"), Health);
if (IsDead())
{
ASimpleShooterGameModeBase* GameMode = GetWorld()->GetAuthGameMode<ASimpleShooterGameModeBase>();
if (GameMode != nullptr)
{
GameMode->PawnKilled(this);
}
DetachFromControllerPendingDestroy();
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
return DamageToApply;
}
void AShooterCharacter::ShootBullet()
{
Weapon->PullTrigger();
}
void AShooterCharacter::Move(const FInputActionValue& Value)
{
if (MovementComponent->IsCrouching()) { return; };
FVector Forward = GetActorForwardVector();
FVector Right = GetActorRightVector();
const FVector2D DirectionValue = Value.Get<FVector2D>();
AddMovementInput(Forward * DirectionValue.Y);
AddMovementInput(Right * DirectionValue.X);
}
void AShooterCharacter::Look(const FInputActionValue& Value)
{
const float Pitch = Value.Get<FVector2D>().Y;
const float Yaw = Value.Get<FVector2D>().X;
AddControllerPitchInput(Pitch);
AddControllerYawInput(Yaw);
}
void AShooterCharacter::Crouch(const FInputActionValue& Value)
{
if (MovementComponent)
{
MovementComponent->bWantsToCrouch = true;
}
}
void AShooterCharacter::Uncrouch(const FInputActionValue& Value)
{
if (MovementComponent)
{
MovementComponent->bWantsToCrouch = false;
}
}
void AShooterCharacter::Shoot(const FInputActionValue& Value)
{
AShooterCharacter::ShootBullet();
}
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "ShooterPlayerController.generated.h"
/**
*
*/
UCLASS()
class SIMPLESHOOTER_API AShooterPlayerController : public APlayerController
{
GENERATED_BODY()
public:
virtual void GameHasEnded(class AActor* EndGameFocus = nullptr, bool bIsWinner = false) override;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
private:
UPROPERTY(EditAnywhere)
TSubclassOf<class UUserWidget> LoseScreenClass;
UPROPERTY(EditAnywhere)
TSubclassOf<class UUserWidget> WinScreenClass;
UPROPERTY(EditAnywhere)
TSubclassOf<class UUserWidget> HUDClass;
UPROPERTY(EditAnywhere)
float RestartDelay = 5;
FTimerHandle RestartTimer;
UPROPERTY()
UUserWidget *HUD;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "Controllers/ShooterPlayerController.h"
#include "TimerManager.h"
#include "Blueprint/UserWidget.h"
void AShooterPlayerController::GameHasEnded(AActor* EndGameFocus, bool bIsWinner)
{
Super::GameHasEnded(EndGameFocus, bIsWinner);
HUD->RemoveFromViewport();
if(bIsWinner)
{
UUserWidget* LoseScreen = CreateWidget(this, WinScreenClass);
if (LoseScreen != nullptr)
{
LoseScreen->AddToViewport();
}
}
else
{
UUserWidget* LoseScreen = CreateWidget(this, LoseScreenClass);
if (LoseScreen != nullptr)
{
LoseScreen->AddToViewport();
}
}
GetWorldTimerManager().SetTimer(RestartTimer, this, &APlayerController::RestartLevel, RestartDelay);
}
void AShooterPlayerController::BeginPlay()
{
Super::BeginPlay();
HUD = CreateWidget(this, HUDClass);
if (HUD != nullptr)
{
//HUD->AddToViewport();
}
}
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "AIController.h"
#include "ShooterAIController.generated.h"
/**
*
*/
UCLASS()
class SIMPLESHOOTER_API AShooterAIController : public AAIController
{
GENERATED_BODY()
protected:
virtual void BeginPlay();
APawn* PlayerPawn;
public:
virtual void Tick(float DeltaSeconds) override;
bool IsDead() const;
private:
UPROPERTY(EditDefaultsOnly)
class UBehaviorTree* AIBehavior;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "AI/ShooterAIController.h"
#include "Kismet/GameplayStatics.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Characters/ShooterCharacter.h"
void AShooterAIController::BeginPlay()
{
Super::BeginPlay();
PlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
if (AIBehavior != nullptr)
{
RunBehaviorTree(AIBehavior);
}
GetBlackboardComponent()->SetValueAsVector(TEXT("StartLocation"), GetPawn()->GetActorLocation());
}
void AShooterAIController::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
}
bool AShooterAIController::IsDead() const
{
AShooterCharacter* ControlledCharacter = Cast<AShooterCharacter>(GetPawn());
if (ControlledCharacter != nullptr)
{
return ControlledCharacter->IsDead();
}
return false;
}
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Gun.generated.h"
class UParticleSystem;
UCLASS()
class SIMPLESHOOTER_API AGun : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AGun();
void PullTrigger();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
private:
UPROPERTY(VisibleAnywhere)
USceneComponent* Root;
UPROPERTY(VisibleAnywhere)
USkeletalMeshComponent* Mesh;
UPROPERTY(EditAnywhere)
UParticleSystem* MuzzleFlash;
UPROPERTY(EditAnywhere)
USoundBase* MuzzleSound;
UPROPERTY(EditAnywhere)
UParticleSystem* ImpactFx;
UPROPERTY(EditAnywhere)
USoundBase* ImpactSound;
UPROPERTY(EditAnywhere)
float MaxRange = 1000;
UPROPERTY(EditAnywhere)
float Damage = 10;
bool GunTrace(FHitResult& Hit, FVector& ShotDirection);
AController* GetOwnerController() const;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "Weapons/Gun.h"
#include "Components/SkeletalMeshComponent.h"
#include "Kismet/GameplayStatics.h"
#include "DrawDebugHelpers.h"
#include "Engine/DamageEvents.h"
// Sets default values
AGun::AGun()
{
// 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;
Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
SetRootComponent(Root);
Mesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Mesh"));
Mesh->SetupAttachment(Root);
}
void AGun::PullTrigger()
{
UGameplayStatics::SpawnEmitterAttached(MuzzleFlash, Mesh, TEXT("MuzzleFlashSocket"));
UGameplayStatics::SpawnSoundAttached(MuzzleSound, Mesh, TEXT("MuzzleFlashSocket"));
FHitResult Hit;
FVector ShotDirection;
bool bSuccess = GunTrace(Hit, ShotDirection);
if (bSuccess)
{
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ImpactFx ,Hit.Location, ShotDirection.Rotation());
UGameplayStatics::PlaySoundAtLocation(GetWorld(), ImpactSound, Hit.Location);
AActor* HitActor = Hit.GetActor();
if (HitActor != nullptr)
{
FPointDamageEvent DamageEvent(Damage, Hit, ShotDirection, nullptr);
AController* OwnerController = GetOwnerController();
HitActor->TakeDamage(Damage, DamageEvent, OwnerController, this);
}
}
}
// Called when the game starts or when spawned
void AGun::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AGun::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
bool AGun::GunTrace(FHitResult& Hit, FVector& ShotDirection)
{
AController* OwnerController = GetOwnerController();
if (OwnerController == nullptr) return false;
FVector Location;
FRotator Rotation;
OwnerController->GetPlayerViewPoint(Location, Rotation);
ShotDirection = -Rotation.Vector();
FVector End = Location + Rotation.Vector() * MaxRange;
Hit;
FCollisionQueryParams Params;
Params.AddIgnoredActor(this);
Params.AddIgnoredActor(GetOwner());
return GetWorld()->LineTraceSingleByChannel(Hit, Location, End, ECollisionChannel::ECC_GameTraceChannel1, Params);
}
AController* AGun::GetOwnerController() const
{
APawn* OwnerPawn = Cast<APawn>(GetOwner());
if (OwnerPawn == nullptr) return nullptr;
return OwnerPawn->GetController();
}
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "SimpleShooter/SimpleShooterGameModeBase.h"
#include "KillEmAllGameMode.generated.h"
/**
*
*/
UCLASS()
class SIMPLESHOOTER_API AKillEmAllGameMode : public ASimpleShooterGameModeBase
{
GENERATED_BODY()
public:
virtual void PawnKilled(APawn* PawnKilled) override;
private:
void EndGame(bool bIsPlayerWinner);
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "GameModes/KillEmAllGameMode.h"
#include "EngineUtils.h"
#include "GameFramework/Controller.h"
#include "Ai/ShooterAIController.h"
void AKillEmAllGameMode::PawnKilled(APawn* PawnKilled)
{
Super::PawnKilled(PawnKilled);
APlayerController* PlayerController = Cast<APlayerController>(PawnKilled->GetController());
if (PlayerController != nullptr)
{
EndGame(false);
}
for (AShooterAIController* Controller : TActorRange<AShooterAIController>(GetWorld()))
{
if (!Controller->IsDead())
{
return;
}
}
EndGame(true);
}
void AKillEmAllGameMode::EndGame(bool bIsPlayerWinner)
{
for (AController* Controller : TActorRange<AController>(GetWorld()))
{
bool bIsWinner = Controller->IsPlayerController() == bIsPlayerWinner;
Controller->GameHasEnded(Controller->GetPawn(), bIsWinner);
}
}
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/Services/BTService_BlackboardBase.h"
#include "BTService_PlayerLocation.generated.h"
/**
*
*/
UCLASS()
class SIMPLESHOOTER_API UBTService_PlayerLocation : public UBTService_BlackboardBase
{
GENERATED_BODY()
public:
UBTService_PlayerLocation();
protected:
virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "Services/BTService_PlayerLocation.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Kismet/GameplayStatics.h"
#include "GameFramework/Pawn.h"
UBTService_PlayerLocation::UBTService_PlayerLocation()
{
NodeName = TEXT("Update Player Location");
}
void UBTService_PlayerLocation::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
APawn* PlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
if (PlayerPawn == nullptr)
{
return;
}
OwnerComp.GetBlackboardComponent()->SetValueAsVector(GetSelectedBlackboardKey(), PlayerPawn->GetActorLocation());
}
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/Services/BTService_BlackboardBase.h"
#include "BTService_PlayerLocationIfSeen.generated.h"
/**
*
*/
UCLASS()
class SIMPLESHOOTER_API UBTService_PlayerLocationIfSeen : public UBTService_BlackboardBase
{
GENERATED_BODY()
public:
UBTService_PlayerLocationIfSeen();
protected:
virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "Services/BTService_PlayerLocationIfSeen.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Kismet/GameplayStatics.h"
#include "GameFramework/Pawn.h"
#include "AIController.h"
UBTService_PlayerLocationIfSeen::UBTService_PlayerLocationIfSeen()
{
NodeName = TEXT("Update Player Location If Seen");
}
void UBTService_PlayerLocationIfSeen::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
APawn* PlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
if (PlayerPawn == nullptr)
{
return;
}
if (OwnerComp.GetAIOwner() == nullptr)
{
return;
}
if (OwnerComp.GetAIOwner()->LineOfSightTo(PlayerPawn))
{
OwnerComp.GetBlackboardComponent()->SetValueAsObject(GetSelectedBlackboardKey(), PlayerPawn);
}
else
{
OwnerComp.GetBlackboardComponent()->ClearValue(GetSelectedBlackboardKey());
}
}
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/Tasks/BTTask_BlackboardBase.h"
#include "BTTask_ClearBlackBoardValue.generated.h"
/**
*
*/
UCLASS()
class SIMPLESHOOTER_API UBTTask_ClearBlackBoardValue : public UBTTask_BlackboardBase
{
GENERATED_BODY()
public:
UBTTask_ClearBlackBoardValue();
protected:
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "Tasks/BTTask_ClearBlackBoardValue.h"
#include "BehaviorTree/BlackboardComponent.h"
UBTTask_ClearBlackBoardValue::UBTTask_ClearBlackBoardValue()
{
NodeName = TEXT("Clear BlackBoard Value");
}
EBTNodeResult::Type UBTTask_ClearBlackBoardValue::ExecuteTask(UBehaviorTreeComponent &OwnerComp, uint8* NodeMemory)
{
Super::ExecuteTask(OwnerComp, NodeMemory);
OwnerComp.GetBlackboardComponent()->ClearValue(GetSelectedBlackboardKey());
return EBTNodeResult::Succeeded;
}
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "BTTask_Shoot.generated.h"
/**
*
*/
UCLASS()
class SIMPLESHOOTER_API UBTTask_Shoot : public UBTTaskNode
{
GENERATED_BODY()
public:
UBTTask_Shoot();
protected:
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "Tasks/BTTask_Shoot.h"
#include "AIController.h"
#include "Characters/ShooterCharacter.h"
UBTTask_Shoot::UBTTask_Shoot()
{
NodeName = TEXT("Shoot");
}
EBTNodeResult::Type UBTTask_Shoot::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
Super::ExecuteTask(OwnerComp, NodeMemory);
if (OwnerComp.GetAIOwner() == nullptr)
{
return EBTNodeResult::Failed;
}
AShooterCharacter* Character = Cast<AShooterCharacter>(OwnerComp.GetAIOwner()->GetPawn());
if (Character == nullptr)
{
return EBTNodeResult::Failed;
}
Character->ShootBullet();
return EBTNodeResult::Succeeded;
}