Implement interaction prompt
This commit is contained in:
@@ -378,7 +378,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
|
||||
- [x] Implement crouching and prone movement stances. Decision: `C` toggles crouch at `55%` movement speed and `Z` toggles prone at `25%` movement speed. Gamepad mappings are Right Shoulder for crouch and Left Shoulder for prone. Sprint is disabled while crouched or prone.
|
||||
- [x] Implement jumping if needed.
|
||||
- [x] Implement interaction trace.
|
||||
- [ ] Implement interact prompt.
|
||||
- [x] Implement interact prompt. Implemented as a local trace-backed HUD prompt using each interactable's `GetInteractionText`, rendered as `[E] <action>` through the Agrarian HUD.
|
||||
- [ ] Implement basic animation blueprint.
|
||||
- [x] Implement placeholder character mesh.
|
||||
- [~] Add replication for player movement and core state.
|
||||
@@ -1465,4 +1465,4 @@ Earliest incomplete foundation items:
|
||||
|
||||
Immediate next item:
|
||||
|
||||
- [ ] Implement interact prompt.
|
||||
- [ ] Implement basic animation blueprint.
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,24 @@
|
||||
import unreal
|
||||
|
||||
|
||||
def load(path):
|
||||
asset = unreal.EditorAssetLibrary.load_asset(path)
|
||||
if not asset:
|
||||
raise RuntimeError(f"Could not load {path}")
|
||||
return asset
|
||||
|
||||
|
||||
def main():
|
||||
game_mode_bp = load("/Game/ThirdPerson/Blueprints/BP_ThirdPersonGameMode")
|
||||
game_mode_cdo = unreal.get_default_object(game_mode_bp.generated_class())
|
||||
hud_class = unreal.load_class(None, "/Script/AgrarianGame.AgrarianDebugHUD")
|
||||
if not hud_class:
|
||||
raise RuntimeError("Could not load /Script/AgrarianGame.AgrarianDebugHUD")
|
||||
|
||||
game_mode_cdo.set_editor_property("hud_class", hud_class)
|
||||
unreal.EditorAssetLibrary.save_loaded_asset(game_mode_bp)
|
||||
|
||||
unreal.log("Agrarian interaction prompt setup complete.")
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,42 @@
|
||||
import unreal
|
||||
|
||||
|
||||
def load(path):
|
||||
asset = unreal.EditorAssetLibrary.load_asset(path)
|
||||
if not asset:
|
||||
raise RuntimeError(f"Could not load {path}")
|
||||
return asset
|
||||
|
||||
|
||||
def main():
|
||||
game_mode_bp = load("/Game/ThirdPerson/Blueprints/BP_ThirdPersonGameMode")
|
||||
game_mode_cdo = unreal.get_default_object(game_mode_bp.generated_class())
|
||||
expected_hud_class = unreal.load_class(None, "/Script/AgrarianGame.AgrarianDebugHUD")
|
||||
character_class = unreal.load_class(None, "/Script/AgrarianGame.AgrarianGameCharacter")
|
||||
hud_cdo = unreal.get_default_object(expected_hud_class) if expected_hud_class else None
|
||||
character_cdo = unreal.get_default_object(character_class) if character_class else None
|
||||
|
||||
missing = []
|
||||
if not expected_hud_class:
|
||||
missing.append("could not load AgrarianDebugHUD class")
|
||||
elif game_mode_cdo.get_editor_property("hud_class") != expected_hud_class:
|
||||
missing.append("BP_ThirdPersonGameMode HUD class is not AgrarianDebugHUD")
|
||||
elif hud_cdo:
|
||||
if not bool(hud_cdo.get_editor_property("bShowInteractionPrompt")):
|
||||
missing.append("AgrarianDebugHUD bShowInteractionPrompt is disabled")
|
||||
if float(hud_cdo.get_editor_property("PromptTextScale")) <= 0.0:
|
||||
missing.append("AgrarianDebugHUD PromptTextScale is not positive")
|
||||
|
||||
if not character_class:
|
||||
missing.append("could not load AgrarianGameCharacter class")
|
||||
elif character_cdo:
|
||||
character_cdo.get_editor_property("InteractionPromptText")
|
||||
character_cdo.get_editor_property("FocusedInteractableActor")
|
||||
|
||||
if missing:
|
||||
raise RuntimeError("Interaction prompt verification failed: " + "; ".join(missing))
|
||||
|
||||
unreal.log("Agrarian interaction prompt verification complete.")
|
||||
|
||||
|
||||
main()
|
||||
@@ -4,12 +4,13 @@
|
||||
#include "AgrarianGameCharacter.h"
|
||||
#include "AgrarianInventoryComponent.h"
|
||||
#include "AgrarianSurvivalComponent.h"
|
||||
#include "Engine/Canvas.h"
|
||||
|
||||
void AAgrarianDebugHUD::DrawHUD()
|
||||
{
|
||||
Super::DrawHUD();
|
||||
|
||||
if (!bShowDebugHUD || !Canvas)
|
||||
if (!Canvas)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -20,12 +21,34 @@ void AAgrarianDebugHUD::DrawHUD()
|
||||
return;
|
||||
}
|
||||
|
||||
float Y = 32.0f;
|
||||
constexpr float X = 32.0f;
|
||||
DrawInteractionPrompt(AgrarianCharacter);
|
||||
|
||||
DrawLine(TEXT("AGRARIAN DEV HUD"), X, Y, FColor(160, 220, 140));
|
||||
DrawSurvival(AgrarianCharacter->GetSurvivalComponent(), X, Y);
|
||||
DrawInventory(AgrarianCharacter->GetInventoryComponent(), X, Y);
|
||||
if (bShowDebugHUD)
|
||||
{
|
||||
float Y = 32.0f;
|
||||
constexpr float X = 32.0f;
|
||||
|
||||
DrawLine(TEXT("AGRARIAN DEV HUD"), X, Y, FColor(160, 220, 140));
|
||||
DrawSurvival(AgrarianCharacter->GetSurvivalComponent(), X, Y);
|
||||
DrawInventory(AgrarianCharacter->GetInventoryComponent(), X, Y);
|
||||
}
|
||||
}
|
||||
|
||||
void AAgrarianDebugHUD::DrawInteractionPrompt(const AAgrarianGameCharacter* AgrarianCharacter)
|
||||
{
|
||||
if (!bShowInteractionPrompt || !AgrarianCharacter || !AgrarianCharacter->HasInteractionPrompt() || !Canvas)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FString Prompt = FString::Printf(TEXT("[E] %s"), *AgrarianCharacter->GetInteractionPromptText().ToString());
|
||||
float TextWidth = 0.0f;
|
||||
float TextHeight = 0.0f;
|
||||
GetTextSize(Prompt, TextWidth, TextHeight, nullptr, PromptTextScale);
|
||||
|
||||
const float X = (Canvas->ClipX - TextWidth) * 0.5f;
|
||||
const float Y = Canvas->ClipY * 0.62f;
|
||||
DrawText(Prompt, FColor(245, 245, 225), X, Y, nullptr, PromptTextScale, true);
|
||||
}
|
||||
|
||||
void AAgrarianDebugHUD::DrawSurvival(const UAgrarianSurvivalComponent* SurvivalComponent, float X, float& Y)
|
||||
|
||||
@@ -23,7 +23,14 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD", meta = (ClampMin = "0.25"))
|
||||
float TextScale = 1.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD")
|
||||
bool bShowInteractionPrompt = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD", meta = (ClampMin = "0.25"))
|
||||
float PromptTextScale = 1.15f;
|
||||
|
||||
protected:
|
||||
void DrawInteractionPrompt(const class AAgrarianGameCharacter* AgrarianCharacter);
|
||||
void DrawSurvival(const UAgrarianSurvivalComponent* SurvivalComponent, float X, float& Y);
|
||||
void DrawInventory(const UAgrarianInventoryComponent* InventoryComponent, float X, float& Y);
|
||||
void DrawLine(const FString& Text, float X, float& Y, const FColor& Color = FColor::White);
|
||||
|
||||
@@ -69,6 +69,11 @@ void AAgrarianGameCharacter::Tick(float DeltaSeconds)
|
||||
{
|
||||
Super::Tick(DeltaSeconds);
|
||||
|
||||
if (IsLocallyControlled())
|
||||
{
|
||||
UpdateInteractionPrompt();
|
||||
}
|
||||
|
||||
ApplyMovementSpeed();
|
||||
|
||||
if (!HasAuthority() || !bWantsToSprint)
|
||||
@@ -498,6 +503,36 @@ void AAgrarianGameCharacter::DoJumpEnd()
|
||||
}
|
||||
|
||||
void AAgrarianGameCharacter::TryInteract()
|
||||
{
|
||||
AActor* HitActor = FindFocusedInteractable();
|
||||
if (!HitActor)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasAuthority())
|
||||
{
|
||||
IAgrarianInteractable::Execute_Interact(HitActor, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerInteract(HitActor);
|
||||
}
|
||||
}
|
||||
|
||||
bool AAgrarianGameCharacter::HasInteractionPrompt() const
|
||||
{
|
||||
return FocusedInteractableActor != nullptr && !InteractionPromptText.IsEmpty();
|
||||
}
|
||||
|
||||
void AAgrarianGameCharacter::UpdateInteractionPrompt()
|
||||
{
|
||||
FText NewPromptText;
|
||||
FocusedInteractableActor = FindFocusedInteractable(&NewPromptText);
|
||||
InteractionPromptText = FocusedInteractableActor ? NewPromptText : FText::GetEmpty();
|
||||
}
|
||||
|
||||
AActor* AAgrarianGameCharacter::FindFocusedInteractable(FText* OutPromptText) const
|
||||
{
|
||||
FVector TraceStart;
|
||||
FRotator TraceRotation;
|
||||
@@ -518,26 +553,26 @@ void AAgrarianGameCharacter::TryInteract()
|
||||
|
||||
if (!GetWorld() || !GetWorld()->LineTraceSingleByChannel(Hit, TraceStart, TraceEnd, ECC_Visibility, Params))
|
||||
{
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AActor* HitActor = Hit.GetActor();
|
||||
if (!HitActor || !HitActor->GetClass()->ImplementsInterface(UAgrarianInteractable::StaticClass()))
|
||||
{
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (HasAuthority())
|
||||
if (!IAgrarianInteractable::Execute_CanInteract(HitActor, this))
|
||||
{
|
||||
if (IAgrarianInteractable::Execute_CanInteract(HitActor, this))
|
||||
{
|
||||
IAgrarianInteractable::Execute_Interact(HitActor, this);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
|
||||
if (OutPromptText)
|
||||
{
|
||||
ServerInteract(HitActor);
|
||||
*OutPromptText = IAgrarianInteractable::Execute_GetInteractionText(HitActor, this);
|
||||
}
|
||||
|
||||
return HitActor;
|
||||
}
|
||||
|
||||
void AAgrarianGameCharacter::ServerSetWantsToSprint_Implementation(bool bNewWantsToSprint)
|
||||
|
||||
@@ -93,6 +93,14 @@ protected:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Interaction", meta = (ClampMin = "100"))
|
||||
float InteractionDistance = 450.0f;
|
||||
|
||||
/** Actor currently under the local interaction trace. */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Agrarian|Interaction", meta = (AllowPrivateAccess = "true"))
|
||||
TObjectPtr<AActor> FocusedInteractableActor = nullptr;
|
||||
|
||||
/** Prompt text for the currently focused interactable. */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Agrarian|Interaction", meta = (AllowPrivateAccess = "true"))
|
||||
FText InteractionPromptText;
|
||||
|
||||
/** Baseline movement speed before sprint, skill, injury, load, and terrain modifiers. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Movement", meta = (ClampMin = "0"))
|
||||
float WalkSpeed = 140.0f;
|
||||
@@ -252,6 +260,18 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category="Agrarian|Interaction")
|
||||
virtual void TryInteract();
|
||||
|
||||
/** Returns true when the local interaction trace has a usable target. */
|
||||
UFUNCTION(BlueprintPure, Category="Agrarian|Interaction")
|
||||
bool HasInteractionPrompt() const;
|
||||
|
||||
/** Returns the current local interaction prompt text. */
|
||||
UFUNCTION(BlueprintPure, Category="Agrarian|Interaction")
|
||||
FText GetInteractionPromptText() const { return InteractionPromptText; }
|
||||
|
||||
/** Returns the actor currently under the local interaction trace, if any. */
|
||||
UFUNCTION(BlueprintPure, Category="Agrarian|Interaction")
|
||||
AActor* GetFocusedInteractableActor() const { return FocusedInteractableActor; }
|
||||
|
||||
/** Returns true when this local character is using first-person camera presentation. */
|
||||
UFUNCTION(BlueprintPure, Category="Agrarian|Camera")
|
||||
bool IsFirstPersonCamera() const { return bFirstPersonCamera; }
|
||||
@@ -287,6 +307,12 @@ public:
|
||||
UFUNCTION(Server, Reliable)
|
||||
void ServerSetProne(bool bNewProne);
|
||||
|
||||
/** Refreshes local interactable focus and prompt text. */
|
||||
void UpdateInteractionPrompt();
|
||||
|
||||
/** Finds a valid interactable in front of this character. */
|
||||
AActor* FindFocusedInteractable(FText* OutPromptText = nullptr) const;
|
||||
|
||||
public:
|
||||
|
||||
/** Returns CameraBoom subobject **/
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "AgrarianGameGameMode.h"
|
||||
#include "AgrarianDebugHUD.h"
|
||||
#include "AgrarianGameState.h"
|
||||
|
||||
AAgrarianGameGameMode::AAgrarianGameGameMode()
|
||||
{
|
||||
GameStateClass = AAgrarianGameState::StaticClass();
|
||||
HUDClass = AAgrarianDebugHUD::StaticClass();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user