SubwaySurf minigame

This commit is contained in:
Oleg Petruny 2024-07-21 17:57:16 +02:00
parent f7b9942b5a
commit 346f2b22ef
22 changed files with 372 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -38,6 +38,8 @@ void UCommonFunctions::DestroyActorRecursively(AActor* actor)
actor->Destroy();
}
ALevelBase* UCommonFunctions::GetCurrentLevelScript(UObject* obj)
{
if(auto world = obj->GetWorld())

View File

@ -18,9 +18,12 @@ public:
UFUNCTION(BlueprintCallable, Category = Actor)
static void DestroyActorRecursively(class AActor* actor);
UFUNCTION(BlueprintCallable, Category = Level)
static class ALevelBase* GetCurrentLevelScript(class UObject* obj);
UFUNCTION(BlueprintCallable, Category = Player)
static class APlayerBase* GetPlayer(class UObject* obj);
};

View File

@ -220,6 +220,16 @@ void UDialogueManager::LockCallback(bool lock)
_lockCallback = lock;
}
void UDialogueManager::BeginDestroy()
{
_timersLock.Lock();
for(auto& timer : _timers)
GetWorld()->GetTimerManager().ClearTimer(timer);
_timersLock.Unlock();
UObject::BeginDestroy();
}
void UDialogueManager::OnDialogueEnd()
{
_dialoguesLock.Lock();

View File

@ -84,6 +84,9 @@ public:
UFUNCTION(BlueprintCallable)
void LockCallback(bool lock);
protected:
virtual void BeginDestroy() override;
private:
void PlayNextDialogue();
UFUNCTION()

View File

@ -0,0 +1,18 @@
// Oleg Petruny proprietary.
#include "Minigame.h"
AMinigame::AMinigame()
{
//PrimaryActorTick.bCanEverTick = true;
}
void AMinigame::End()
{}
void AMinigame::Start(class APlayerBase* playerPawn, FMinigameEndCallback delegate)
{
player = playerPawn;
callback = delegate;
}

View File

@ -0,0 +1,45 @@
// Oleg Petruny proprietary.
#pragma once
#include "GameFramework/Actor.h"
#include "Minigame.generated.h"
UENUM(BlueprintType)
enum class EMinigameResult : uint8
{
Loss,
Win,
};
DECLARE_DYNAMIC_DELEGATE_TwoParams(FMinigameEndCallback, EMinigameResult, result, int32, score);
UCLASS(Blueprintable, BlueprintType, MinimalAPI, Abstract)
class AMinigame : public AActor
{
GENERATED_BODY()
public:
AMinigame();
UFUNCTION(BlueprintCallable)
virtual void Start(class APlayerBase* playerPawn, FMinigameEndCallback delegate);
UFUNCTION(BlueprintCallable)
virtual void End();
UFUNCTION(BlueprintPure)
inline class UInputMappingContext* GetInputMappings() { return input.LoadSynchronous(); }
protected:
FMinigameEndCallback callback;
class APlayerBase* player;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 score = 0;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TSoftObjectPtr<class UInputMappingContext> input = nullptr;
};

View File

@ -0,0 +1,186 @@
// Oleg Petruny proprietary.
#include "SubwaySurfManager.h"
#include "Components/SplineComponent.h"
#include "EnhancedInputComponent.h"
#include "InputMappingContext.h"
#include "PlayerBase.h"
#include "SubwaySurfObstacle.h"
ASubwaySurfManager::ASubwaySurfManager()
{
input = { FSoftObjectPath{ TEXT("/Script/EnhancedInput.InputMappingContext'/Game/Input/Minigame/IMC_SubwaySurf.IMC_SubwaySurf'") } };
}
void ASubwaySurfManager::Start(APlayerBase* playerPawn, FMinigameEndCallback delegate)
{
AMinigame::Start(playerPawn, delegate);
player->LockPlayer(FPlayerLock::All());
for(auto& mapping : input.LoadSynchronous()->GetMappings())
{
if(mapping.Action->ActionDescription.EqualTo(FText::AsCultureInvariant(TEXT("Up"))))
inputHandlers.Add(player->inputComponent->BindAction(mapping.Action, ETriggerEvent::Triggered, this, &ASubwaySurfManager::Up).GetHandle());
else if(mapping.Action->ActionDescription.EqualTo(FText::AsCultureInvariant(TEXT("Down"))))
inputHandlers.Add(player->inputComponent->BindAction(mapping.Action, ETriggerEvent::Triggered, this, &ASubwaySurfManager::Down).GetHandle());
else if(mapping.Action->ActionDescription.EqualTo(FText::AsCultureInvariant(TEXT("Left"))))
inputHandlers.Add(player->inputComponent->BindAction(mapping.Action, ETriggerEvent::Triggered, this, &ASubwaySurfManager::Left).GetHandle());
else if(mapping.Action->ActionDescription.EqualTo(FText::AsCultureInvariant(TEXT("Right"))))
inputHandlers.Add(player->inputComponent->BindAction(mapping.Action, ETriggerEvent::Triggered, this, &ASubwaySurfManager::Right).GetHandle());
}
playerPos.SetRelativeLocation(lines[0]->GetLocationAtTime(lines[0]->Duration * 0.2f, ESplineCoordinateSpace::Local));
player->SetActorLocation(playerPos.GetComponentLocation());
player->Controller->SetControlRotation(GetActorRotation());
PrimaryActorTick.bCanEverTick = true;
}
void ASubwaySurfManager::End()
{
PrimaryActorTick.bCanEverTick = false;
for(int32 handler : inputHandlers)
player->inputComponent->RemoveActionBindingForHandle(handler);
player->UnlockPlayer(FPlayerLock::All());
AMinigame::End();
}
void ASubwaySurfManager::Hit()
{
if(score > 1000)
{
Finish();
return;
}
if(callback.IsBound())
callback.Execute(EMinigameResult::Loss, score);
End();
}
void ASubwaySurfManager::Finish()
{
if(callback.IsBound())
callback.Execute(EMinigameResult::Win, score);
End();
}
void ASubwaySurfManager::PostInitializeComponents()
{
AMinigame::PostInitializeComponents();
GetComponents<USplineComponent>(lines);
}
void ASubwaySurfManager::Tick(float deltaTime)
{
AMinigame::Tick(deltaTime);
time += deltaTime;
if(time >= spawnDelay / speed)
{
time = 0; // (time -= spawnDelay) is not good on lags
CreateObstacle();
}
for(int32 i = 0; i < obstacles.Num(); ++i)
{
if(obstacles[i].time > lines[obstacles[i].lineId]->Duration)
{
obstacles[i].actor->Destroy();
obstacles.RemoveAt(i);
//CreateObstacle();
speed += 0.1;
score += 100 * speed;
}
else
{
obstacles[i].time += deltaTime * speed;
obstacles[i].actor->SetActorLocation(lines[obstacles[i].lineId]->GetWorldLocationAtTime(obstacles[i].time));
}
}
player->SetActorLocation(FMath::VInterpTo(player->GetActorLocation(), playerPos.GetComponentLocation(), deltaTime, 1));
}
void ASubwaySurfManager::CreateObstacle(int32 index)
{
auto obstacleClass = obstacleClasses.CreateIterator();
for(int32 i = 0; i < FMath::RandRange(0, obstacleClasses.Num() - 1); ++i) // std::advance not works :(
++obstacleClass;
int32 lineId;
do
{
lineId = FMath::RandRange(0, lines.Num() - 1);
} while(lineId == lastSpawnedLine);
auto spline = lines[lineId];
FActorSpawnParameters spawnParams{};
spawnParams.Owner = this;
lastSpawnedLine = lineId;
auto obstacle = GetWorld()->SpawnActor<ASubwaySurfObstacle>(*obstacleClass, spline->GetComponentLocation(), GetActorRotation(), spawnParams);
obstacle->subwaySurf = this;
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, FString::Printf(TEXT("v: %f, %f, %f"), obstacle->GetActorLocation().X, obstacle->GetActorLocation().Y, obstacle->GetActorLocation().Z));
if(index < 0)
{
obstacles.Add({ .actor = obstacle, .lineId = lineId, .time = 0 });
}
else
{
obstacles[index].actor->Destroy();
obstacles[index].actor = obstacle;
obstacles[index].lineId = lineId;
obstacles[index].time = 0;
}
}
void ASubwaySurfManager::Up()
{
if(playerUp)
return;
playerUp = true;
playerPos.AddLocalOffset(FVector::UpVector * 300);
}
void ASubwaySurfManager::Down()
{
if(!playerUp)
return;
playerUp = false;
playerPos.AddLocalOffset(FVector::DownVector * 300);
}
void ASubwaySurfManager::Left()
{
if(playerLine == 0)
return;
--playerLine;
playerPos.AddLocalOffset(FVector::LeftVector * 300);
}
void ASubwaySurfManager::Right()
{
if(playerLine == lines.Num() - 1)
return;
++playerLine;
playerPos.AddLocalOffset(FVector::RightVector * 300);
}

View File

@ -0,0 +1,58 @@
// Oleg Petruny proprietary.
#pragma once
#include "Minigame/Minigame.h"
#include "SubwaySurfManager.generated.h"
struct ObstacleData
{
class ASubwaySurfObstacle* actor;
int32 lineId;
float time;
};
UCLASS(Blueprintable, BlueprintType, MinimalAPI)
class ASubwaySurfManager : public AMinigame
{
GENERATED_BODY()
public:
ASubwaySurfManager();
virtual void Start(class APlayerBase* playerPawn, FMinigameEndCallback delegate) override;
virtual void End() override;
void Hit();
void Finish();
protected:
virtual void PostInitializeComponents() override;
virtual void Tick(float deltaTime) override;
void CreateObstacle(int32 index = -1);
void Up();
void Down();
void Left();
void Right();
UPROPERTY(EditDefaultsOnly)
TSet<TSubclassOf<class ASubwaySurfObstacle>> obstacleClasses;
UPROPERTY(EditDefaultsOnly)
float speed = 2;
UPROPERTY(EditDefaultsOnly)
float spawnDelay = 3;
float time = spawnDelay;
TArray<class USplineComponent*> lines;
TArray<ObstacleData> obstacles;
int32 lastSpawnedLine = -1;
TArray<int32> inputHandlers;
class USceneComponent playerPos;
bool playerUp = false;
int32 playerLine = 0;
};

View File

@ -0,0 +1,17 @@
// Oleg Petruny proprietary.
#include "SubwaySurfObstacle.h"
#include "SubwaySurfManager.h"
void ASubwaySurfObstacle::Hit()
{
if(!subwaySurf)
{
Destroy();
return;
}
subwaySurf->Hit();
}

View File

@ -0,0 +1,21 @@
// Oleg Petruny proprietary.
#pragma once
#include "GameFramework/Actor.h"
#include "SubwaySurfObstacle.generated.h"
UCLASS(Blueprintable, BlueprintType, MinimalAPI, Abstract)
class ASubwaySurfObstacle : public AActor
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
void Hit();
class ASubwaySurfManager* subwaySurf = nullptr;
};

View File

@ -336,3 +336,10 @@ void APlayerBase::ShowJournal()
if(auto WM = AMainGameModeBase::GetWidgetsManager())
WM->ShowJournal();
}
FPlayerLock FPlayerLock::All()
{
FPlayerLock lock;
std::memset(&lock, 1, sizeof(FPlayerLock));
return std::move(lock);
}

View File

@ -21,6 +21,8 @@ struct FPlayerLock
uint8 interaction : 1;
uint8 camera : 1;
uint8 inventory : 1;
static FPlayerLock All();
};
UCLASS(Blueprintable, BlueprintType)