initial flight

This commit is contained in:
farsight-andre
2026-04-22 09:35:02 +03:00
parent 003311e98d
commit 31f910e51d
8 changed files with 299 additions and 68 deletions

View File

@@ -1,5 +1,7 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Game/WhipWingGameMode.h" #include "Game/WhipWingGameMode.h"
#include "Player/WhipWingPawn.h"
AWhipWingGameMode::AWhipWingGameMode()
{
DefaultPawnClass = AWhipWingPawn::StaticClass();
}

View File

@@ -1,17 +1,14 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h" #include "GameFramework/GameModeBase.h"
#include "WhipWingGameMode.generated.h" #include "WhipWingGameMode.generated.h"
/**
*
*/
UCLASS() UCLASS()
class WHIPWING_API AWhipWingGameMode : public AGameModeBase class WHIPWING_API AWhipWingGameMode : public AGameModeBase
{ {
GENERATED_BODY() GENERATED_BODY()
public:
AWhipWingGameMode();
}; };

View File

@@ -1,34 +1,168 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Player/FlightMovementComponent.h" #include "Player/FlightMovementComponent.h"
#include "Engine/Engine.h"
// Sets default values for this component's properties
UFlightMovementComponent::UFlightMovementComponent() UFlightMovementComponent::UFlightMovementComponent()
{ {
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
// off to improve performance if you don't need them.
PrimaryComponentTick.bCanEverTick = true; PrimaryComponentTick.bCanEverTick = true;
// ...
} }
// Called when the game starts
void UFlightMovementComponent::BeginPlay() void UFlightMovementComponent::BeginPlay()
{ {
Super::BeginPlay(); Super::BeginPlay();
ForwardYaw = GetOwner()->GetActorRotation().Yaw;
// ... CurrentSpeed = BaseSpeed;
} }
void UFlightMovementComponent::UpdateControllerTransforms(const FTransform& Left, const FTransform& Right)
{
CachedLeftTransform = Left;
CachedRightTransform = Right;
bHasControllerData = true;
}
// Called every frame
void UFlightMovementComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) void UFlightMovementComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{ {
Super::TickComponent(DeltaTime, TickType, ThisTickFunction); Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// ... if (!bHasControllerData)
return;
float TargetPitch = CalculateTargetPitch();
float TargetRoll = CalculateTargetRoll();
CurrentPitch = FMath::FInterpTo(CurrentPitch, TargetPitch, DeltaTime, SteeringSmoothingSpeed);
CurrentRoll = FMath::FInterpTo(CurrentRoll, TargetRoll, DeltaTime, SteeringSmoothingSpeed);
if (DetectFlap(DeltaTime))
{
VerticalVelocity += FlapLiftImpulse;
if (bShowDebug && GEngine)
{
GEngine->AddOnScreenDebugMessage(10, 0.5f, FColor::Yellow, TEXT(">>> FLAP <<<"));
}
}
VerticalVelocity -= Gravity * DeltaTime;
VerticalVelocity = FMath::Clamp(VerticalVelocity, -MaxFallVelocity, MaxLiftVelocity);
FlapCooldownTimer = FMath::Max(0.f, FlapCooldownTimer - DeltaTime);
PrevLeftPos = CachedLeftTransform.GetLocation();
PrevRightPos = CachedRightTransform.GetLocation();
bHasPreviousPositions = true;
float PitchRad = FMath::DegreesToRadians(CurrentPitch);
CurrentSpeed -= Gravity * FMath::Sin(PitchRad) * GlideSpeedTransfer * DeltaTime;
CurrentSpeed = FMath::Clamp(CurrentSpeed, MinSpeed, MaxSpeed);
FVector ForwardDir = FRotationMatrix(FRotator(CurrentPitch, ForwardYaw, 0.f)).GetUnitAxis(EAxis::X);
FVector RightDir = FRotationMatrix(FRotator(0.f, ForwardYaw, 0.f)).GetUnitAxis(EAxis::Y);
FVector Movement = ForwardDir * CurrentSpeed * DeltaTime;
Movement += RightDir * -CurrentRoll * LateralStrafeSpeed * DeltaTime;
Movement.Z += VerticalVelocity * DeltaTime;
FHitResult HitResult;
GetOwner()->AddActorWorldOffset(Movement, true, &HitResult);
if (HitResult.bBlockingHit)
{
if (HitResult.Normal.Z > 0.5f && VerticalVelocity < 0.f)
{
VerticalVelocity = 0.f;
}
else if (HitResult.Normal.Z < -0.5f && VerticalVelocity > 0.f)
{
VerticalVelocity = 0.f;
}
}
if (bShowDebug)
{
DrawDebugVisualization();
}
} }
float UFlightMovementComponent::CalculateTargetPitch() const
{
FVector LeftForward = CachedLeftTransform.GetRotation().GetForwardVector();
FVector RightForward = CachedRightTransform.GetRotation().GetForwardVector();
float LeftPitch = FMath::RadiansToDegrees(FMath::Asin(LeftForward.Z));
float RightPitch = FMath::RadiansToDegrees(FMath::Asin(RightForward.Z));
float AveragePitch = (LeftPitch + RightPitch) * 0.5f;
return FMath::Clamp(AveragePitch * PitchSensitivity, -MaxPitchAngle, MaxPitchAngle);
}
float UFlightMovementComponent::CalculateTargetRoll() const
{
float HeightDiff = CachedRightTransform.GetLocation().Z - CachedLeftTransform.GetLocation().Z;
return FMath::Clamp(HeightDiff * RollSensitivity, -MaxRollAngle, MaxRollAngle);
}
bool UFlightMovementComponent::DetectFlap(float DeltaTime)
{
if (!bHasPreviousPositions)
return false;
if (FlapCooldownTimer > 0.f)
return false;
FVector LeftPos = CachedLeftTransform.GetLocation();
FVector RightPos = CachedRightTransform.GetLocation();
float LeftVertVel = (LeftPos.Z - PrevLeftPos.Z) / DeltaTime;
float RightVertVel = (RightPos.Z - PrevRightPos.Z) / DeltaTime;
float AvgVertVel = (LeftVertVel + RightVertVel) * 0.5f;
bool bIsMovingDown = AvgVertVel < -FlapDetectionThreshold;
if (bWasMovingDown && !bIsMovingDown)
{
bWasMovingDown = false;
FlapCooldownTimer = FlapCooldown;
return true;
}
bWasMovingDown = bIsMovingDown;
return false;
}
void UFlightMovementComponent::SetForwardDirection(float NewYaw)
{
ForwardYaw = NewYaw;
}
void UFlightMovementComponent::DrawDebugVisualization() const
{
if (!GEngine)
return;
float HeightDiff = CachedRightTransform.GetLocation().Z - CachedLeftTransform.GetLocation().Z;
GEngine->AddOnScreenDebugMessage(1, 0.f, FColor::Cyan,
FString::Printf(TEXT("Roll: %+.1f (height diff: %+.1f)"), CurrentRoll, HeightDiff));
GEngine->AddOnScreenDebugMessage(2, 0.f, FColor::Cyan,
FString::Printf(TEXT("Pitch: %+.1f"), CurrentPitch));
GEngine->AddOnScreenDebugMessage(3, 0.f, FColor::Cyan,
FString::Printf(TEXT("Forward Yaw: %.1f (fixed)"), ForwardYaw));
GEngine->AddOnScreenDebugMessage(11, 0.f, FColor::Cyan,
FString::Printf(TEXT("Current Speed: %.1f"), CurrentSpeed));
GEngine->AddOnScreenDebugMessage(9, 0.f, FColor::Cyan,
FString::Printf(TEXT("Lateral: %+.1f"), -CurrentRoll * LateralStrafeSpeed));
GEngine->AddOnScreenDebugMessage(4, 0.f, VerticalVelocity >= 0.f ? FColor::Yellow : FColor::Red,
FString::Printf(TEXT("Vert Vel: %+.1f"), VerticalVelocity));
GEngine->AddOnScreenDebugMessage(5, 0.f, FColor::Green,
FString::Printf(TEXT("Position: %s"), *GetOwner()->GetActorLocation().ToString()));
GEngine->AddOnScreenDebugMessage(6, 0.f, FColor::White,
FString::Printf(TEXT("Grounded: %s"), VerticalVelocity == 0.f && FlapCooldownTimer <= 0.f ? TEXT("YES") : TEXT("no")));
FVector LP = CachedLeftTransform.GetLocation();
FVector RP = CachedRightTransform.GetLocation();
GEngine->AddOnScreenDebugMessage(7, 0.f, FColor::Orange,
FString::Printf(TEXT("Left ctrl: X=%+.0f Y=%+.0f Z=%+.0f"), LP.X, LP.Y, LP.Z));
GEngine->AddOnScreenDebugMessage(8, 0.f, FColor::Orange,
FString::Printf(TEXT("Right ctrl: X=%+.0f Y=%+.0f Z=%+.0f"), RP.X, RP.Y, RP.Z));
}

View File

@@ -1,28 +1,98 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "Components/ActorComponent.h" #include "Components/ActorComponent.h"
#include "FlightMovementComponent.generated.h" #include "FlightMovementComponent.generated.h"
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class WHIPWING_API UFlightMovementComponent : public UActorComponent class WHIPWING_API UFlightMovementComponent : public UActorComponent
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
// Sets default values for this component's properties
UFlightMovementComponent(); UFlightMovementComponent();
protected: void UpdateControllerTransforms(const FTransform& Left, const FTransform& Right);
// Called when the game starts
virtual void BeginPlay() override;
public:
// Called every frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Flight|Speed")
float BaseSpeed = 500.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Flight|Speed")
float MinSpeed = 150.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Flight|Speed")
float MaxSpeed = 1200.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Flight|Speed")
float GlideSpeedTransfer = 1.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Flight|Gravity")
float Gravity = 980.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Flight|Flap")
float FlapLiftImpulse = 400.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Flight|Flap")
float MaxLiftVelocity = 600.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Flight|Flap")
float MaxFallVelocity = 800.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Flight|Flap")
float FlapCooldown = 0.5f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Flight|Flap")
float FlapDetectionThreshold = 100.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Flight|Steering")
float PitchSensitivity = 1.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Flight|Steering")
float RollSensitivity = 1.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Flight|Steering")
float MaxPitchAngle = 45.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Flight|Steering")
float MaxRollAngle = 45.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Flight|Steering")
float SteeringSmoothingSpeed = 5.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Flight|Steering")
float LateralStrafeSpeed = 400.f;
void SetForwardDirection(float NewYaw);
float GetForwardYaw() const { return ForwardYaw; }
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Flight|Debug")
bool bShowDebug = false;
protected:
virtual void BeginPlay() override;
private:
float CurrentSpeed = 0.f;
float VerticalVelocity = 0.f;
float CurrentPitch = 0.f;
float CurrentRoll = 0.f;
float ForwardYaw = 0.f;
FVector PrevLeftPos = FVector::ZeroVector;
FVector PrevRightPos = FVector::ZeroVector;
bool bHasPreviousPositions = false;
float FlapCooldownTimer = 0.f;
bool bWasMovingDown = false;
FTransform CachedLeftTransform;
FTransform CachedRightTransform;
bool bHasControllerData = false;
float CalculateTargetPitch() const;
float CalculateTargetRoll() const;
bool DetectFlap(float DeltaTime);
void DrawDebugVisualization() const;
}; };

View File

@@ -1,34 +1,47 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Player/WhipWingPawn.h" #include "Player/WhipWingPawn.h"
#include "Player/FlightMovementComponent.h"
#include "Camera/CameraComponent.h"
#include "MotionControllerComponent.h"
#include "Components/SphereComponent.h"
// Sets default values
AWhipWingPawn::AWhipWingPawn() AWhipWingPawn::AWhipWingPawn()
{ {
// 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; PrimaryActorTick.bCanEverTick = true;
AutoPossessPlayer = EAutoReceiveInput::Player0;
CollisionSphere = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionSphere"));
CollisionSphere->InitSphereRadius(34.f);
CollisionSphere->SetCollisionProfileName(TEXT("Pawn"));
SetRootComponent(CollisionSphere);
VROrigin = CreateDefaultSubobject<USceneComponent>(TEXT("VROrigin"));
VROrigin->SetupAttachment(CollisionSphere);
Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
Camera->SetupAttachment(VROrigin);
LeftController = CreateDefaultSubobject<UMotionControllerComponent>(TEXT("LeftController"));
LeftController->SetupAttachment(VROrigin);
LeftController->MotionSource = FName("Left");
RightController = CreateDefaultSubobject<UMotionControllerComponent>(TEXT("RightController"));
RightController->SetupAttachment(VROrigin);
RightController->MotionSource = FName("Right");
FlightMovement = CreateDefaultSubobject<UFlightMovementComponent>(TEXT("FlightMovement"));
} }
// Called when the game starts or when spawned
void AWhipWingPawn::BeginPlay() void AWhipWingPawn::BeginPlay()
{ {
Super::BeginPlay(); Super::BeginPlay();
} }
// Called every frame
void AWhipWingPawn::Tick(float DeltaTime) void AWhipWingPawn::Tick(float DeltaTime)
{ {
Super::Tick(DeltaTime); Super::Tick(DeltaTime);
FlightMovement->UpdateControllerTransforms(
LeftController->GetRelativeTransform(),
RightController->GetRelativeTransform()
);
} }
// Called to bind functionality to input
void AWhipWingPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}

View File

@@ -1,29 +1,43 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "GameFramework/Pawn.h" #include "GameFramework/Pawn.h"
#include "WhipWingPawn.generated.h" #include "WhipWingPawn.generated.h"
class USphereComponent;
class UCameraComponent;
class UMotionControllerComponent;
class UFlightMovementComponent;
UCLASS() UCLASS()
class WHIPWING_API AWhipWingPawn : public APawn class WHIPWING_API AWhipWingPawn : public APawn
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
// Sets default values for this pawn's properties
AWhipWingPawn(); AWhipWingPawn();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override; virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; UFlightMovementComponent* FlightMovement;
protected:
virtual void BeginPlay() override;
private:
UPROPERTY(VisibleAnywhere, Category = "Components")
USphereComponent* CollisionSphere;
UPROPERTY(VisibleAnywhere, Category = "Components")
USceneComponent* VROrigin;
UPROPERTY(VisibleAnywhere, Category = "Components")
UCameraComponent* Camera;
UPROPERTY(VisibleAnywhere, Category = "Components")
UMotionControllerComponent* LeftController;
UPROPERTY(VisibleAnywhere, Category = "Components")
UMotionControllerComponent* RightController;
}; };

View File

@@ -8,7 +8,7 @@ public class WhipWing : ModuleRules
{ {
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay" });
PrivateDependencyModuleNames.AddRange(new string[] { }); PrivateDependencyModuleNames.AddRange(new string[] { });

View File

@@ -20,8 +20,8 @@
"SupportedTargetPlatforms": [ "SupportedTargetPlatforms": [
"Win64", "Win64",
"Linux", "Linux",
"Android", "LinuxArm64",
"VisionOS" "Android"
] ]
}, },
{ {
@@ -30,6 +30,7 @@
"SupportedTargetPlatforms": [ "SupportedTargetPlatforms": [
"Win64", "Win64",
"Linux", "Linux",
"LinuxArm64",
"Android" "Android"
] ]
}, },
@@ -39,8 +40,8 @@
"SupportedTargetPlatforms": [ "SupportedTargetPlatforms": [
"Win64", "Win64",
"Linux", "Linux",
"Android", "LinuxArm64",
"VisionOS" "Android"
] ]
} }
], ],