Can now switch the skeletal mesh on a character

This commit is contained in:
2026-02-17 15:49:52 -05:00
parent 3f975dbada
commit a987754b38
16 changed files with 157 additions and 125 deletions

View File

@@ -627,45 +627,43 @@ void UlxAnimationStepLibrary::AnimationStepApplyMaterials(const FlxAnimationStep
}
}
void UlxAnimationStepLibrary::AnimationStepApplyMesh(const FlxAnimationStep& step, bool FallbackToBP, AActor* actor) {
if (actor == nullptr) return;
void UlxAnimationStepLibrary::AnimationStepApplyStaticMesh(const FlxAnimationStep& step, bool FallbackToBP,
UStaticMeshComponent* MeshComp, UStaticMesh* Fallback) {
if (MeshComp == nullptr) return;
// Step 1: Look for a "mesh" or "bp" string field in the animation step.
//
FString MeshName = AnimationStepGetString(step, TEXT("mesh"));
if (MeshName.IsEmpty() && FallbackToBP) {
MeshName = AnimationStepGetString(step, TEXT("bp"));
}
if (MeshName.IsEmpty()) return;
// Step 2: Find the actor's mesh component. There must be exactly one.
//
TInlineComponentArray<UMeshComponent*> MeshComponents;
actor->GetComponents<UMeshComponent>(MeshComponents);
if (MeshComponents.Num() != 1) {
UE_LOG(LogLuprexIntegration, Error, TEXT("AnimationStepApplyMesh: Actor %s has %d mesh components, expected exactly 1"), *actor->GetName(), MeshComponents.Num());
return;
UStaticMesh* NewMesh = nullptr;
if (!MeshName.IsEmpty()) {
UlxAssetLookup::LoadStaticMeshAsset(NewMesh, MeshComp, MeshName);
}
UMeshComponent* MeshComp = MeshComponents[0];
// Step 3: Apply the mesh based on the component type.
//
if (UStaticMeshComponent* StaticComp = Cast<UStaticMeshComponent>(MeshComp)) {
UStaticMesh* NewMesh = nullptr;
UlxAssetLookup::LoadStaticMeshAsset(NewMesh, actor, MeshName, false);
if (NewMesh == nullptr) return;
if (StaticComp->GetStaticMesh() != NewMesh) {
StaticComp->SetStaticMesh(NewMesh);
}
} else if (USkeletalMeshComponent* SkelComp = Cast<USkeletalMeshComponent>(MeshComp)) {
USkeletalMesh* NewMesh = nullptr;
UlxAssetLookup::LoadSkeletalMeshAsset(NewMesh, actor, MeshName, true);
if (NewMesh == nullptr) return;
if (SkelComp->GetSkeletalMeshAsset() != NewMesh) {
SkelComp->SetSkeletalMeshAsset(NewMesh);
}
} else {
UE_LOG(LogLuprexIntegration, Error, TEXT("AnimationStepApplyMesh: Actor %s has unsupported mesh component type"), *actor->GetName());
if (NewMesh == nullptr) NewMesh = Fallback;
if (NewMesh == nullptr) return;
if (MeshComp->GetStaticMesh() != NewMesh) {
MeshComp->SetStaticMesh(NewMesh);
}
}
void UlxAnimationStepLibrary::AnimationStepApplySkeletalMesh(const FlxAnimationStep& step, bool FallbackToBP,
USkeletalMeshComponent* MeshComp, USkeletalMesh* Fallback) {
if (MeshComp == nullptr) return;
FString MeshName = AnimationStepGetString(step, TEXT("mesh"));
if (MeshName.IsEmpty() && FallbackToBP) {
MeshName = AnimationStepGetString(step, TEXT("bp"));
}
USkeletalMesh* NewMesh = nullptr;
if (!MeshName.IsEmpty()) {
UlxAssetLookup::LoadSkeletalMeshAsset(NewMesh, MeshComp, MeshName);
}
if (NewMesh == nullptr) NewMesh = Fallback;
if (NewMesh == nullptr) return;
if (MeshComp->GetSkeletalMeshAsset() != NewMesh) {
MeshComp->SetSkeletalMeshAsset(NewMesh);
}
}

View File

@@ -125,55 +125,40 @@ public:
UFUNCTION(BlueprintPure, meta = (BlueprintAutocast), Category = "Luprex|Animation Step")
static int64 AnimationStepID(const FlxAnimationStep& step) { return step.Hash; }
<<<<<<< HEAD
// Using mat_xxxx values from the animation step, update the actor's
// material parameters. Doing this may involve creating or replacing
// dynamic material instances for the actor.
=======
// Scan an animation step for key-value pairs of the
// form mat_XXXX={x,y,z}. For each match, create a
// dynamic material instance on the actor's mesh
// components and set the vector parameter XXXX.
// Materials are restored to their base (non-dynamic)
// state before applying, so parameters from previous
// calls do not persist.
>>>>>>> 9b1dd00a45a7b17c3546f8574d00e5ec78f17c75
//
UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "actor"), Category = "Luprex|Animation Step")
static void AnimationStepApplyMaterials(const FlxAnimationStep& step, AActor* actor);
<<<<<<< HEAD
// Look for a mesh=name key-value pair in the animation step.
// If found, load the named mesh and apply it to the actor's
// mesh component. The actor must have exactly one mesh component.
// If FallbackToBP is true, and mesh=name is not present, looks
// for a bp=name pair instead.
=======
// Look for a mesh=name key-value pair. If found, load
// the named mesh and apply it to the actor's mesh
// component. The actor must have exactly one mesh
// component.
>>>>>>> 9b1dd00a45a7b17c3546f8574d00e5ec78f17c75
// Loads a new static mesh, using the animation step to choose it.
// If the chosen mesh is already installed, no changes are made.
// Looks for 'mesh=name' first; if not found and FallbackToBP is
// true, looks for 'bp=name'. If no mesh is successfully loaded,
// uses the Fallback mesh. Failed loads are logged once.
//
UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "actor"), Category = "Luprex|Animation Step")
static void AnimationStepApplyMesh(const FlxAnimationStep& step, bool FallbackToBP, AActor* actor);
UFUNCTION(BlueprintCallable, Category = "Luprex|Animation Step")
static void AnimationStepApplyStaticMesh(const FlxAnimationStep& step, bool FallbackToBP,
UStaticMeshComponent* MeshComp, UStaticMesh* Fallback = nullptr);
// Loads a new skeletal mesh, using the animation step to choose it.
// If the chosen mesh is already installed, no changes are made.
// Looks for 'mesh=name' first; if not found and FallbackToBP is
// true, looks for 'bp=name'. If no mesh is successfully loaded,
// uses the Fallback mesh. Failed loads are logged once.
//
UFUNCTION(BlueprintCallable, Category = "Luprex|Animation Step")
static void AnimationStepApplySkeletalMesh(const FlxAnimationStep& step, bool FallbackToBP,
USkeletalMeshComponent* MeshComp, USkeletalMesh* Fallback = nullptr);
};
////////////////////////////////////////////////////////////
//
<<<<<<< HEAD
// An animation step that doesn't actually store the step,
// it just contains a pointer to the string.
//
////////////////////////////////////////////////
=======
// FlxAnimationStepView
//
// A lightweight, non-owning view of an animation
// step (hash + body as a string_view).
//
////////////////////////////////////////////////////////////
>>>>>>> 9b1dd00a45a7b17c3546f8574d00e5ec78f17c75
struct FlxAnimationStepView {
int64 Hash;
@@ -266,13 +251,8 @@ private:
FlxStreamBuffer Decoder;
public:
<<<<<<< HEAD
// Initialize the FlxAnimationStepDecoder.
//
=======
// Initialize from an encoded step body.
//
>>>>>>> 9b1dd00a45a7b17c3546f8574d00e5ec78f17c75
FlxAnimationStepDecoder(std::string_view body) : Decoder(body) {}
// Return true if the parser has reached EOF.

View File

@@ -20,7 +20,7 @@ void UlxAssetLookup::RebuildIndex()
IAssetRegistry::GetChecked().WaitForCompletion();
AssetPaths.Empty();
AddAssets(TEXT("/Game/StaticMeshes"), UStaticMesh::StaticClass(), TEXT("SM_"));
AddAssets(TEXT("/Game/SkeletalMeshes"), USkeletalMesh::StaticClass(), TEXT("SK_"));
AddAssets(TEXT("/Game/SkeletalMeshes"), USkeletalMesh::StaticClass(), TEXT("SKM_"));
AddAssets(TEXT("/Game/AnimSequences"), UAnimSequence::StaticClass(), TEXT("SEQ_"));
AddAssets(TEXT("/Game/Tangibles"), UBlueprint::StaticClass(), TEXT("TAN_"));
AddAssets(TEXT("/Game/Widgets"), UWidgetBlueprint::StaticClass(), TEXT("WB_"));
@@ -52,16 +52,22 @@ void UlxAssetLookup::AddAssets(const TCHAR *Path, UClass *Class, const TCHAR *Na
FoundData.Num(), *Class->GetName(), Path);
}
UObject *UlxAssetLookup::LoadAsset(const UObject *Context, UClass *Class, UClass *ChildOf, const FString &Name, bool ErrorIfNotFound)
void UlxAssetLookup::ReportFailedLoad(const FString &ClassName, const FString &Name, const FString &Reason)
{
static TSet<FString> Reported;
FString Key = ClassName + TEXT(":") + Name + TEXT(":") + Reason;
if (Reported.Contains(Key)) return;
Reported.Add(Key);
UE_LOG(LogLuprexIntegration, Display, TEXT("Loading %s %s: %s"), *ClassName, *Name, *Reason);
}
UObject *UlxAssetLookup::LoadAsset(const UObject *Context, UClass *Class, UClass *ChildOf, const FString &Name)
{
const UlxAssetLookup *Lookup = ALuprexGameModeBase::FromContext(Context)->GetAssetLookup();
const FString *Path = Lookup->AssetPaths.Find(MakeTuple(Class->GetName(), FName(Name)));
if (Path == nullptr)
{
if (ErrorIfNotFound)
{
UE_LOG(LogLuprexIntegration, Error, TEXT("Loading %s %s: asset not found"), *Class->GetName(), *Name);
}
ReportFailedLoad(Class->GetName(), Name, TEXT("asset not found"));
return nullptr;
}
@@ -77,7 +83,7 @@ UObject *UlxAssetLookup::LoadAsset(const UObject *Context, UClass *Class, UClass
if (Result == nullptr)
{
UE_LOG(LogLuprexIntegration, Error, TEXT("Loading %s %s: unknown load failure"), *Class->GetName(), *Name);
ReportFailedLoad(Class->GetName(), Name, TEXT("unknown load failure"));
return nullptr;
}
@@ -86,7 +92,7 @@ UObject *UlxAssetLookup::LoadAsset(const UObject *Context, UClass *Class, UClass
UClass *ResClass = (UClass *)Result;
if (!ResClass->IsChildOf(ChildOf))
{
UE_LOG(LogLuprexIntegration, Error, TEXT("Loading %s %s: blueprint not a child of %s"), *Class->GetName(), *Name, *ChildOf->GetName());
ReportFailedLoad(Class->GetName(), Name, FString::Printf(TEXT("blueprint not a child of %s"), *ChildOf->GetName()));
return nullptr;
}
}
@@ -95,50 +101,50 @@ UObject *UlxAssetLookup::LoadAsset(const UObject *Context, UClass *Class, UClass
}
ElxValidOrNotValid UlxAssetLookup::LoadStaticMeshAsset(
UStaticMesh *&Result, const UObject *Context, const FString &Name, bool ErrorIfNotFound)
UStaticMesh *&Result, const UObject *Context, const FString &Name)
{
Result = (UStaticMesh *)LoadAsset(Context, UStaticMesh::StaticClass(), nullptr, Name, ErrorIfNotFound);
Result = (UStaticMesh *)LoadAsset(Context, UStaticMesh::StaticClass(), nullptr, Name);
return Result ? Valid : NotValid;
}
ElxValidOrNotValid UlxAssetLookup::LoadSkeletalMeshAsset(
USkeletalMesh *&Result, const UObject *Context, const FString &Name, bool ErrorIfNotFound)
USkeletalMesh *&Result, const UObject *Context, const FString &Name)
{
Result = (USkeletalMesh *)LoadAsset(Context, USkeletalMesh::StaticClass(), nullptr, Name, ErrorIfNotFound);
Result = (USkeletalMesh *)LoadAsset(Context, USkeletalMesh::StaticClass(), nullptr, Name);
return Result ? Valid : NotValid;
}
ElxValidOrNotValid UlxAssetLookup::LoadAnimSequenceAsset(
UAnimSequence *&Result, const UObject *Context, const FString &Name, bool ErrorIfNotFound)
UAnimSequence *&Result, const UObject *Context, const FString &Name)
{
Result = (UAnimSequence *)LoadAsset(Context, UAnimSequence::StaticClass(), nullptr, Name, ErrorIfNotFound);
Result = (UAnimSequence *)LoadAsset(Context, UAnimSequence::StaticClass(), nullptr, Name);
return Result ? Valid : NotValid;
}
ElxValidOrNotValid UlxAssetLookup::LoadTangibleBlueprintAsset(
TSubclassOf<AActor> &Result, const UObject *Context, const FString &Name, bool ErrorIfNotFound)
TSubclassOf<AActor> &Result, const UObject *Context, const FString &Name)
{
Result = (UClass*)LoadAsset(Context, UBlueprint::StaticClass(), AActor::StaticClass(), Name, ErrorIfNotFound);
Result = (UClass*)LoadAsset(Context, UBlueprint::StaticClass(), AActor::StaticClass(), Name);
if (Result == nullptr) return NotValid;
UFunction *aqchanged = Result->FindFunctionByName(FName(TEXT("Animation Queue Changed")));
if ((aqchanged == nullptr)||(aqchanged->ParmsSize != 0))
{
UE_LOG(LogLuprexIntegration, Error, TEXT("Loading Blueprint %s: Tangible does not have 'Animation Queue Changed' function"), *Name);
ReportFailedLoad(TEXT("Blueprint"), Name, TEXT("tangible does not have 'Animation Queue Changed' function"));
Result = nullptr; return NotValid;
}
return Valid;
}
ElxValidOrNotValid UlxAssetLookup::LoadUserWidgetAsset(
TSubclassOf<UUserWidget> &Result, const UObject *Context, const FString &Name, bool ErrorIfNotFound)
TSubclassOf<UUserWidget> &Result, const UObject *Context, const FString &Name)
{
Result = (UClass *)LoadAsset(Context, UWidgetBlueprint::StaticClass(), UUserWidget::StaticClass(), Name, ErrorIfNotFound);
Result = (UClass *)LoadAsset(Context, UWidgetBlueprint::StaticClass(), UUserWidget::StaticClass(), Name);
return Result ? Valid : NotValid;
}
ElxValidOrNotValid UlxAssetLookup::LoadLuaWidgetAsset(
TSubclassOf<UlxLuaWidget> &Result, const UObject *Context, const FString &Name, bool ErrorIfNotFound)
TSubclassOf<UlxLuaWidget> &Result, const UObject *Context, const FString &Name)
{
Result = (UClass *)LoadAsset(Context, UWidgetBlueprint::StaticClass(), UlxLuaWidget::StaticClass(), Name, ErrorIfNotFound);
Result = (UClass *)LoadAsset(Context, UWidgetBlueprint::StaticClass(), UlxLuaWidget::StaticClass(), Name);
return Result ? Valid : NotValid;
}

View File

@@ -43,7 +43,9 @@ private:
void AddAssets(const TCHAR *Path, UClass *Class, const TCHAR *NamePrefix);
static UObject *LoadAsset(const UObject *Context, UClass *Class, UClass *ChildOf, const FString &Name, bool ErrorIfNotFound);
static void ReportFailedLoad(const FString &ClassName, const FString &Name, const FString &Reason);
static UObject *LoadAsset(const UObject *Context, UClass *Class, UClass *ChildOf, const FString &Name);
public:
void RebuildIndex();
@@ -52,41 +54,35 @@ public:
//
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context", ExpandEnumAsExecs="ReturnValue"), Category = "Luprex|Asset Loading")
static ElxValidOrNotValid LoadStaticMeshAsset(
UStaticMesh *&Result,
const UObject *Context, const FString &Name, bool ErrorIfNotFound = false);
UStaticMesh *&Result, const UObject *Context, const FString &Name);
// Get a skeletal mesh by name.
//
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context", ExpandEnumAsExecs="ReturnValue"), Category = "Luprex|Asset Loading")
static ElxValidOrNotValid LoadSkeletalMeshAsset(
USkeletalMesh *&Result,
const UObject *Context, const FString &Name, bool ErrorIfNotFound = false);
USkeletalMesh *&Result, const UObject *Context, const FString &Name);
// Get an animation sequence by name.
//
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context", ExpandEnumAsExecs="ReturnValue"), Category = "Luprex|Asset Loading")
static ElxValidOrNotValid LoadAnimSequenceAsset(
UAnimSequence *&Result,
const UObject *Context, const FString &Name, bool ErrorIfNotFound = false);
UAnimSequence *&Result, const UObject *Context, const FString &Name);
// Get a tangible class by name.
//
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context", ExpandEnumAsExecs="ReturnValue"), Category = "Luprex|Asset Loading")
static ElxValidOrNotValid LoadTangibleBlueprintAsset(
TSubclassOf<AActor> &Result,
const UObject *Context, const FString &Name, bool ErrorIfNotFound = false);
TSubclassOf<AActor> &Result, const UObject *Context, const FString &Name);
// Get a widget blueprint by name.
//
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context", ExpandEnumAsExecs="ReturnValue"), Category = "Luprex|Asset Loading")
static ElxValidOrNotValid LoadUserWidgetAsset(
TSubclassOf<UUserWidget> &Result,
const UObject *Context, const FString &Name, bool ErrorIfNotFound = false);
TSubclassOf<UUserWidget> &Result, const UObject *Context, const FString &Name);
// Get a look-at widget blueprint by name.
//
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context", ExpandEnumAsExecs="ReturnValue"), Category = "Luprex|Asset Loading")
static ElxValidOrNotValid LoadLuaWidgetAsset(
TSubclassOf<UlxLuaWidget> &Result,
const UObject *Context, const FString &Name, bool ErrorIfNotFound = false);
TSubclassOf<UlxLuaWidget> &Result, const UObject *Context, const FString &Name);
};

View File

@@ -15,18 +15,12 @@
#pragma once
#include "Containers/UnrealString.h"
#include "ConsoleOutput.generated.h"
<<<<<<< HEAD
//////////////////////////////////////////////////////////////
=======
////////////////////////////////////////////////////////////
>>>>>>> 9b1dd00a45a7b17c3546f8574d00e5ec78f17c75
//
// UlxConsoleOutput
//
<<<<<<< HEAD
// When the lua code executes a print statement, the text
// eventually gets passed to the GameMode blueprint: see
// Docs/Print-Statement-Handling.md for more information.
@@ -52,10 +46,7 @@
// If the GameMode wants to use some other framework to
// implement the virtual console, that's perfectly fine.
//
//////////////////////////////////////////////////////////////
=======
////////////////////////////////////////////////////////////
>>>>>>> 9b1dd00a45a7b17c3546f8574d00e5ec78f17c75
UCLASS(BlueprintType)
class UlxConsoleOutput : public UObject

View File

@@ -3,6 +3,7 @@
#pragma once
#include "CoreMinimal.h"
#include "Engine/HitResult.h"
#include "GameFramework/GameModeBase.h"
#include "lpx-enginewrapper.hpp"
#include "StringDecoder.h"