Make top-down the default camera mode and bind camera toggle to C.

Matches Fantasy Life style gameplay; first-person remains available as an optional mode.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pixachux 2026-07-22 18:55:07 +02:00
parent 0ced5b17ef
commit c0d9450d30
5 changed files with 37 additions and 13 deletions

View File

@ -4,7 +4,7 @@ A life-simulation action RPG remake inspired by Fantasy Life, built in **Unreal
## Vision ## Vision
- First-person gameplay (primary) with classic top-down camera toggle - First-person gameplay with classic top-down camera as the default (toggle with C)
- Mouse, keyboard, and gamepad support via Enhanced Input - Mouse, keyboard, and gamepad support via Enhanced Input
- Co-op multiplayer (listen server first, dedicated server hosting) - Co-op multiplayer (listen server first, dedicated server hosting)
- Optional hardware ray tracing and scalability presets - Optional hardware ray tracing and scalability presets
@ -31,7 +31,7 @@ A life-simulation action RPG remake inspired by Fantasy Life, built in **Unreal
| Jump | Space | A / Cross | | Jump | Space | A / Cross |
| Interact / Craft | E | X / Square | | Interact / Craft | E | X / Square |
| Attack | LMB | RT | | Attack | LMB | RT |
| Toggle camera (FP / Top-Down) | V | Menu | | Toggle camera (Top-Down / FP) | C | Menu |
| Quick save | I | Y / Triangle | | Quick save | I | Y / Triangle |
| Pause | Esc | Start | | Pause | Esc | Start |

View File

@ -32,6 +32,7 @@ AVocationCharacter::AVocationCharacter()
FirstPersonCamera->SetupAttachment(GetCapsuleComponent()); FirstPersonCamera->SetupAttachment(GetCapsuleComponent());
FirstPersonCamera->SetRelativeLocation(FVector(0.f, 0.f, 64.f)); FirstPersonCamera->SetRelativeLocation(FVector(0.f, 0.f, 64.f));
FirstPersonCamera->bUsePawnControlRotation = true; FirstPersonCamera->bUsePawnControlRotation = true;
FirstPersonCamera->SetActive(false);
TopDownSpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("TopDownSpringArm")); TopDownSpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("TopDownSpringArm"));
TopDownSpringArm->SetupAttachment(RootComponent); TopDownSpringArm->SetupAttachment(RootComponent);
@ -42,7 +43,7 @@ AVocationCharacter::AVocationCharacter()
TopDownSpringArm->bInheritPitch = false; TopDownSpringArm->bInheritPitch = false;
TopDownSpringArm->bInheritRoll = false; TopDownSpringArm->bInheritRoll = false;
TopDownSpringArm->bInheritYaw = false; TopDownSpringArm->bInheritYaw = false;
TopDownSpringArm->SetActive(false); TopDownSpringArm->SetActive(true);
TopDownCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("TopDownCamera")); TopDownCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("TopDownCamera"));
TopDownCamera->SetupAttachment(TopDownSpringArm, USpringArmComponent::SocketName); TopDownCamera->SetupAttachment(TopDownSpringArm, USpringArmComponent::SocketName);
@ -54,21 +55,25 @@ AVocationCharacter::AVocationCharacter()
if (USkeletalMeshComponent* MeshComp = GetMesh()) if (USkeletalMeshComponent* MeshComp = GetMesh())
{ {
MeshComp->SetOnlyOwnerSee(true); // Visible body for top-down default; FP mode reattaches in ApplyCameraModeVisuals.
MeshComp->SetupAttachment(FirstPersonCamera); MeshComp->SetOnlyOwnerSee(false);
MeshComp->SetRelativeLocation(FVector(25.f, 0.f, -90.f)); MeshComp->SetOwnerNoSee(false);
MeshComp->SetupAttachment(GetCapsuleComponent());
MeshComp->SetRelativeLocation(FVector(0.f, 0.f, -90.f));
MeshComp->SetRelativeRotation(FRotator(0.f, -90.f, 0.f));
} }
if (UCharacterMovementComponent* Movement = GetCharacterMovement()) if (UCharacterMovementComponent* Movement = GetCharacterMovement())
{ {
Movement->bOrientRotationToMovement = false; Movement->bOrientRotationToMovement = true;
Movement->RotationRate = FRotator(0.f, 540.f, 0.f); Movement->RotationRate = FRotator(0.f, 540.f, 0.f);
Movement->JumpZVelocity = 500.f; Movement->JumpZVelocity = 500.f;
Movement->AirControl = 0.35f; Movement->AirControl = 0.35f;
Movement->MaxWalkSpeed = 500.f; Movement->MaxWalkSpeed = 500.f;
} }
bUseControllerRotationPitch = true; // Top-down is the default camera mode (Fantasy Life style).
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = true; bUseControllerRotationYaw = true;
bUseControllerRotationRoll = false; bUseControllerRotationRoll = false;
} }
@ -304,6 +309,25 @@ void AVocationCharacter::ApplyCameraModeVisuals()
TopDownSpringArm->SetActive(!bFirstPerson); TopDownSpringArm->SetActive(!bFirstPerson);
TopDownCamera->SetActive(!bFirstPerson); TopDownCamera->SetActive(!bFirstPerson);
if (USkeletalMeshComponent* MeshComp = GetMesh())
{
if (bFirstPerson)
{
MeshComp->SetOnlyOwnerSee(true);
MeshComp->SetupAttachment(FirstPersonCamera);
MeshComp->SetRelativeLocation(FVector(25.f, 0.f, -90.f));
MeshComp->SetRelativeRotation(FRotator::ZeroRotator);
}
else
{
MeshComp->SetOnlyOwnerSee(false);
MeshComp->SetOwnerNoSee(false);
MeshComp->SetupAttachment(GetCapsuleComponent());
MeshComp->SetRelativeLocation(FVector(0.f, 0.f, -90.f));
MeshComp->SetRelativeRotation(FRotator(0.f, -90.f, 0.f));
}
}
bUseControllerRotationPitch = bFirstPerson; bUseControllerRotationPitch = bFirstPerson;
if (UCharacterMovementComponent* Movement = GetCharacterMovement()) if (UCharacterMovementComponent* Movement = GetCharacterMovement())
{ {

View File

@ -76,7 +76,7 @@ void AVocationHUD::DrawMainHUD()
} }
else else
{ {
const FString Controls = TEXT("WASD Move | Mouse Look | V Camera | E Interact/Craft | LMB Attack | I Save | Esc Pause"); const FString Controls = TEXT("WASD Move | Mouse Look | C Camera | E Interact/Craft | LMB Attack | I Save | Esc Pause");
Canvas->DrawColor = FColor(200, 200, 200); Canvas->DrawColor = FColor(200, 200, 200);
Canvas->DrawText(GEngine->GetSmallFont(), Controls, 40.f * Scale, Canvas->ClipY - 60.f * Scale, Scale, Scale); Canvas->DrawText(GEngine->GetSmallFont(), Controls, 40.f * Scale, Canvas->ClipY - 60.f * Scale, Scale, Scale);
} }

View File

@ -29,7 +29,7 @@ void AVocationPlayerController::BeginPlay()
LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>()) LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
{ {
Subsystem->AddMappingContext(SharedMappingContext, SharedMappingPriority); Subsystem->AddMappingContext(SharedMappingContext, SharedMappingPriority);
Subsystem->AddMappingContext(FirstPersonMappingContext, ModeMappingPriority); Subsystem->AddMappingContext(TopDownMappingContext, ModeMappingPriority);
} }
} }
@ -175,7 +175,7 @@ void AVocationPlayerController::CreateRuntimeInputAssets()
SharedMappingContext->MapKey(JumpAction, EKeys::SpaceBar); SharedMappingContext->MapKey(JumpAction, EKeys::SpaceBar);
SharedMappingContext->MapKey(InteractAction, EKeys::E); SharedMappingContext->MapKey(InteractAction, EKeys::E);
SharedMappingContext->MapKey(AttackAction, EKeys::LeftMouseButton); SharedMappingContext->MapKey(AttackAction, EKeys::LeftMouseButton);
SharedMappingContext->MapKey(ToggleCameraAction, EKeys::V); SharedMappingContext->MapKey(ToggleCameraAction, EKeys::C);
SharedMappingContext->MapKey(InventoryAction, EKeys::I); SharedMappingContext->MapKey(InventoryAction, EKeys::I);
SharedMappingContext->MapKey(PauseAction, EKeys::Escape); SharedMappingContext->MapKey(PauseAction, EKeys::Escape);
SharedMappingContext->MapKey(JumpAction, EKeys::Gamepad_FaceButton_Bottom); SharedMappingContext->MapKey(JumpAction, EKeys::Gamepad_FaceButton_Bottom);

View File

@ -110,10 +110,10 @@ protected:
void UpdateInteractionPrompt(); void UpdateInteractionPrompt();
UPROPERTY(ReplicatedUsing = OnRep_CameraMode, BlueprintReadOnly, Category = "VocationLife|Camera") UPROPERTY(ReplicatedUsing = OnRep_CameraMode, BlueprintReadOnly, Category = "VocationLife|Camera")
EVocationCameraMode CurrentCameraMode = EVocationCameraMode::FirstPerson; EVocationCameraMode CurrentCameraMode = EVocationCameraMode::TopDown;
UPROPERTY(ReplicatedUsing = OnRep_CameraMode) UPROPERTY(ReplicatedUsing = OnRep_CameraMode)
EVocationCameraMode TargetCameraMode = EVocationCameraMode::FirstPerson; EVocationCameraMode TargetCameraMode = EVocationCameraMode::TopDown;
UPROPERTY() UPROPERTY()
float CameraBlendAlpha = 1.f; float CameraBlendAlpha = 1.f;