Project Type | Personal project |
Software Used | Unreal Engine 4 |
Languages Used | C++, Blueprints |
Primary Role(s) | Gameplay, A.I and Multiplayer programming |
Made the entire process of setting up a character
Made Character Class that Drives our player character such as Weapon system, Character movememnt and controls, death and respawn..etc
Setting up Abilities in C++ and Blueprints such as:
Setting Up weapon in C++:
Setting up Advanced Enemy A.I using Behaviour tree:
Setting up Simple Tracker bot A.I using C++:
Wave Spawning system, i.e spawning enemies then wait for next wave then spawn more enemies and so on, Logic made in C++ and blueprint.
Player state to keep every player score consistent on the server.
ASWeapon::ASWeapon() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = false; MeshComp = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Mesh Component")); RootComponent = MeshComp; MuzzleSocketName = "MuzzleSocket"; TracerTargetName = "Target"; BaseDamage = 25.0f; BulletSpread = 10.0f; HeadShotDamageMultiplier = 4.0f; RateOfFire = 600.0f; // Network SetReplicates(true); NetUpdateFrequency = 66.0f; MinNetUpdateFrequency = 33.0f; } void ASWeapon::BeginPlay() { Super::BeginPlay(); TimeBetweenShots = 60 / RateOfFire; } void ASWeapon::Fire() { if (Role < ROLE_Authority) { ServerFire(); } AActor* MyOwner = GetOwner(); if (MyOwner) { FVector EyeLocation; FRotator EyeRotation; MyOwner->GetActorEyesViewPoint(EyeLocation, EyeRotation); FVector HitDirection = EyeRotation.Vector(); float HalfRad = FMath::DegreesToRadians(BulletSpread); HitDirection = FMath::VRandCone(HitDirection, HalfRad, HalfRad); FVector TraceEnd = EyeLocation + (HitDirection * 10000); FVector TracerEndPoint = TraceEnd; EPhysicalSurface SurfaceType = SurfaceType_Default; FCollisionQueryParams QueryParams; QueryParams.AddIgnoredActor(MyOwner); QueryParams.AddIgnoredActor(this); QueryParams.bTraceComplex = true; QueryParams.bReturnPhysicalMaterial = true; FHitResult Hit; bool bHitSuccess = GetWorld()->LineTraceSingleByChannel(Hit, EyeLocation, TraceEnd, COLLISION_WEAPON, QueryParams); if (bHitSuccess) { AActor* HitActor = Hit.GetActor(); SurfaceType = UPhysicalMaterial::DetermineSurfaceType(Hit.PhysMaterial.Get()); float ActualDamage = BaseDamage; if (SurfaceType == SURFACE_FLESH_VULNRABLE) { ActualDamage *= HeadShotDamageMultiplier; } UGameplayStatics::ApplyPointDamage(HitActor, ActualDamage, HitDirection, Hit, MyOwner->GetInstigatorController(), MyOwner, DamageType); PlayImpactEffect(SurfaceType, Hit.ImpactPoint); TracerEndPoint = Hit.ImpactPoint; } if (DebugWeaponDrawing > 0) { DrawDebugLine(GetWorld(), EyeLocation, TraceEnd, FColor::Red, false, 2.0f); } PlayFireEffects(TracerEndPoint); if (Role == ROLE_Authority) { HitScanTrace.TraceTo = TracerEndPoint; HitScanTrace.SurfaceType = SurfaceType; } LastFireTime = GetWorld()->TimeSeconds; } } void ASWeapon::PlayImpactEffect(EPhysicalSurface SurfaceType, FVector ImpactPoint) { UParticleSystem* SelectedEffect = nullptr; switch (SurfaceType) { case SURFACE_FLESH_DEFAULT: SelectedEffect = FleshImpactEffect; break; case SURFACE_FLESH_VULNRABLE: SelectedEffect = FleshImpactEffect; break; default: SelectedEffect = DefaultImpactEffect; break; } if (SelectedEffect) { FVector MuzzleLocation = MeshComp->GetSocketLocation(MuzzleSocketName); FVector ShotDirection = ImpactPoint - MuzzleLocation; ShotDirection.Normalize(); UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), SelectedEffect, ImpactPoint, ShotDirection.Rotation()); } } void ASWeapon::OnRep_HitScanTrace() { // PlayCosmiticEffects PlayFireEffects(HitScanTrace.TraceTo); PlayImpactEffect(HitScanTrace.SurfaceType, HitScanTrace.TraceTo); } void ASWeapon::ServerFire_Implementation() { Fire(); } bool ASWeapon::ServerFire_Validate() { return true; } void ASWeapon::StartFire() { float FirstDelay = FMath::Max(LastFireTime + TimeBetweenShots - GetWorld()->TimeSeconds, 0.0f); GetWorldTimerManager().SetTimer(TimeHandle_TimeBetweenShots, this, &ASWeapon::Fire, TimeBetweenShots, true, FirstDelay); } void ASWeapon::StopFire() { GetWorldTimerManager().ClearTimer(TimeHandle_TimeBetweenShots); } void ASWeapon::PlayFireEffects(FVector TracerEndPoint) { if (MuzzleEffect) { UGameplayStatics::SpawnEmitterAttached(MuzzleEffect, MeshComp, MuzzleSocketName); } FVector MuzzleLocation = MeshComp->GetSocketLocation(MuzzleSocketName); if (TraceEffect) { auto TracerParticleComp = UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), TraceEffect, MuzzleLocation); if (TracerParticleComp) { TracerParticleComp->SetVectorParameter(TracerTargetName, TracerEndPoint); } } APawn* MyOwner = Cast<APawn>(GetOwner()); if (MyOwner) { APlayerController* PC = Cast<APlayerController>(MyOwner->GetController()); if (PC) { PC->ClientPlayCameraShake(CameraShakeClass); } } } void ASWeapon::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME_CONDITION(ASWeapon, HitScanTrace, COND_SkipOwner); }
// Sets default values ASPowerupActor::ASPowerupActor() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = false; TotalNumberOfTicks = 0; PowerupInterval = 0.0f; SetReplicates(true); } void ASPowerupActor::OnTickPowerup(AActor* ActivateForActor) { TicksProcessed++; OnPowerupTicked(ActivateForActor); if (TicksProcessed >= TotalNumberOfTicks) { OnExpierd(); bIsPowerupActive = false; OnRep_PowerupActive(); // Delete Timer ! GetWorldTimerManager().ClearTimer(TimerHandle_PowerupTick); } } void ASPowerupActor::OnRep_PowerupActive() { OnPowerStateChanged(bIsPowerupActive); } void ASPowerupActor::ActivatePowerup(AActor* ActivateForActor) { OnActivated(ActivateForActor); bIsPowerupActive = true; OnRep_PowerupActive(); if (PowerupInterval > 0.0f) { /* using timer delegete so we can be able to Bind a UFUNCTION, by doing this we can pass the function with parameters */ FTimerDelegate TimerDelegate_OnTickPowerup; TimerDelegate_OnTickPowerup.BindUFunction(this, FName("OnTickPowerup"), ActivateForActor); GetWorldTimerManager().SetTimer(TimerHandle_PowerupTick, TimerDelegate_OnTickPowerup, PowerupInterval, true); } else { OnTickPowerup(ActivateForActor); } } void ASPowerupActor::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME(ASPowerupActor, bIsPowerupActive); }
// Sets default values ASTrackerBot::ASTrackerBot() { // Set this pawn to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; ExplosionDamage = 60; ExplosionRadius = 350 ; MovementForce = 1000.0f; Accurecy = 50.0f; bUseVelocityChange = true; SelfDestructInterval = 0.5f; MeshComp = CreateDefaultSubobject(TEXT("Mesh Component")); MeshComp->SetCanEverAffectNavigation(false); MeshComp->SetSimulatePhysics(true); RootComponent = MeshComp; HealthComp = CreateDefaultSubobject (TEXT("Health Component")); HealthComp->OnHealthChanged.AddDynamic(this, &ASTrackerBot::HandleTakeDamage); SphereComp = CreateDefaultSubobject (TEXT("Sphere Component")); SphereComp->SetupAttachment(RootComponent); SphereComp->SetSphereRadius(ExplosionRadius); SphereComp->SetCollisionEnabled(ECollisionEnabled::QueryOnly); SphereComp->SetCollisionResponseToAllChannels(ECR_Ignore); SphereComp->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap); SetReplicates(true); } // Called when the game starts or when spawned void ASTrackerBot::BeginPlay() { Super::BeginPlay(); if (Role == ROLE_Authority) { NextPathPoint = GetNextPathPoint(); } } FVector ASTrackerBot::GetNextPathPoint() { AActor* BestTarget = nullptr; float NearestTargetDistance = FLT_MAX; for (FConstPawnIterator It = GetWorld()->GetPawnIterator(); It; ++It) { APawn* TestPawn = It->Get(); if (TestPawn == nullptr || USHealthComponent::IsFriendly(TestPawn, this)) { continue; } USHealthComponent* TestPawnHealthComp = Cast (TestPawn->GetComponentByClass(USHealthComponent::StaticClass())); if (TestPawnHealthComp && TestPawnHealthComp->GetHealth() > 0) { float Distance = (TestPawn->GetActorLocation() - GetActorLocation()).Size(); if (NearestTargetDistance > Distance) { BestTarget = TestPawn; NearestTargetDistance = Distance; } } } // To use UNavigationSystemV1 this we must add NavigationSystem in the CoopGame.Build.cs module. if (BestTarget) { auto NavPath = UNavigationSystemV1::FindPathToActorSynchronously(this, GetActorLocation(), BestTarget, 50.0f); GetWorldTimerManager().ClearTimer(TimerHandle_RefreshPath); GetWorldTimerManager().SetTimer(TimerHandle_RefreshPath, this, &ASTrackerBot::RefreshPath, 5.0f, false); if (!NavPath) { return FVector::ZeroVector; } if (NavPath->PathPoints.Num() > 1) { // Return next point in the path return NavPath->PathPoints[1]; } } // if it fails return actor location! return GetActorLocation(); } // Called every frame void ASTrackerBot::Tick(float DeltaTime) { Super::Tick(DeltaTime); MoveToPlayer(); }
enum class EWaveState : uint8; // Custom Event DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnActorKilled, AActor*, VictimActor, AActor*, KillerActor, AController*, KillerController); /** * */ UCLASS() class COOPGAME_API ASGameMode : public AGameModeBase { GENERATED_BODY() public: ASGameMode(); /** Transitions to calls BeginPlay on actors. */ virtual void StartPlay() override; virtual void Tick(float DeltaSeconds) override; UPROPERTY(BlueprintAssignable, Category = "SGameMode") FOnActorKilled OnActorKilled; protected: UPROPERTY(EditDefaultsOnly, Category = "SGameMode") float TimeBetweenWaves; FTimerHandle TimerHandle_SpawnBot; FTimerHandle TimerHandle_NextWaveStart; int32 NumberOfBotsToSpawn; int32 WaveCount; protected: UFUNCTION(BlueprintImplementableEvent, Category = "SGameMode") void SpawnNewBot(); // Start Spawning Bot and start a timer void StartWave(); // Stop spawning bots and reset Timer void EndWave(); // Set Timer for next wave void PrepareForNextWave(); void CheckWaveState(); void CheckAnyPlayerAlive(); void GameOver(); void SetWaveState(EWaveState NewState); UFUNCTION() void SpawnBotTimerElapsed(); void RestartDeadPlayer();
ASGameMode::ASGameMode() { GameStateClass = ASGameState::StaticClass(); PlayerStateClass = ASPlayerState::StaticClass(); TimeBetweenWaves = 5; PrimaryActorTick.bCanEverTick = true; PrimaryActorTick.TickInterval = 0.5; } void ASGameMode::StartPlay() { Super::StartPlay(); SetWaveState(EWaveState::StartGame); PrepareForNextWave(); } void ASGameMode::Tick(float DeltaSeconds) { Super::Tick(DeltaSeconds); CheckWaveState(); CheckAnyPlayerAlive(); } void ASGameMode::StartWave() { WaveCount++; NumberOfBotsToSpawn = 2 * WaveCount; GetWorldTimerManager().SetTimer(TimerHandle_SpawnBot, this, &ASGameMode::SpawnBotTimerElapsed, 2.0, true, 0.0f); SetWaveState(EWaveState::WaveInProgress); } void ASGameMode::SpawnBotTimerElapsed() { SpawnNewBot(); NumberOfBotsToSpawn--; if (NumberOfBotsToSpawn <= 0) { EndWave(); } } void ASGameMode::RestartDeadPlayer() { for (FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator(); It; ++It) { APlayerController* PC = It->Get(); if (PC && PC->GetPawn() == nullptr) { RestartPlayer(PC); } } } void ASGameMode::EndWave() { GetWorldTimerManager().ClearTimer(TimerHandle_SpawnBot); SetWaveState(EWaveState::WaitingToComplete); } void ASGameMode::CheckWaveState() { bool bIsPreparingForWave = GetWorldTimerManager().IsTimerActive(TimerHandle_NextWaveStart); if (NumberOfBotsToSpawn > 0 || bIsPreparingForWave) { return; } bool bIsAnybotAlive = false; for (FConstPawnIterator It = GetWorld()->GetPawnIterator(); It; ++It) { APawn* TestPawn = It->Get(); if (TestPawn == nullptr || TestPawn->IsPlayerControlled()) { continue; } USHealthComponent* HealthComp = Cast(TestPawn->GetComponentByClass(USHealthComponent::StaticClass())); if (HealthComp && HealthComp->GetHealth() > 0) { bIsAnybotAlive = true; break; } } if (!bIsAnybotAlive) { SetWaveState(EWaveState::WaveComplete); PrepareForNextWave(); } } void ASGameMode::CheckAnyPlayerAlive() { for (FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator(); It; ++It) { APlayerController* PC = It->Get(); if (PC && PC->GetPawn()) { APawn* MyPawn = PC->GetPawn(); USHealthComponent* HealthComp = Cast (MyPawn->GetComponentByClass(USHealthComponent::StaticClass())); if (ensure(HealthComp) && HealthComp->GetHealth() > 0.0f) { return; } } } // All players are dead. GameOver(); } void ASGameMode::GameOver() { EndWave(); SetWaveState(EWaveState::GameOver); // @TODO: Finish up the match and present game over to the player along with game stats. UE_LOG(LogTemp, Warning, TEXT("Game Over: Owari da")); } void ASGameMode::SetWaveState(EWaveState NewState) { ASGameState* GM = GetGameState (); if (ensureAlways(GM)) { GM->SetWaveState(NewState); } } void ASGameMode::PrepareForNextWave() { GetWorldTimerManager().SetTimer(TimerHandle_NextWaveStart, this, &ASGameMode::StartWave, TimeBetweenWaves, false); RestartDeadPlayer(); SetWaveState(EWaveState::WaitingForStart); }