联机版实现,先备份

This commit is contained in:
2025-09-24 13:57:17 +08:00
parent c90b46f430
commit 4bb809b8e3
22 changed files with 824 additions and 454 deletions

View File

@ -47,8 +47,8 @@ UClass* UBusyGamePlayLibrary::GetGameUIClass(const FString& ClassName){
return Class ? Class->Get() : nullptr;
}
UWorld* UBusyGamePlayLibrary::K2_GetWorld(UObject* obj){
return Cast<UWorld>(obj->GetOuter());
UWorld* UBusyGamePlayLibrary::K2_GetWorld(const UObject* obj){
return obj->GetWorld();
}
bool UBusyGamePlayLibrary::GetLevelBaseConfig(const FName& RowName, FBusyLevelBaseConfig& RowData){
@ -84,7 +84,6 @@ bool UBusyGamePlayLibrary::GetCookMaterialStateConfig(const FName& RowName, FBus
}
FLuaBPVar UBusyGamePlayLibrary::CreateTextureBuffer(UObject* WorldContextObject){
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
auto DataTexture = UTexture2D::CreateTransient(512, 1, PF_R32_FLOAT);
DataTexture->Filter = TF_Trilinear;
DataTexture->AddressX = TA_Clamp;
@ -94,15 +93,12 @@ FLuaBPVar UBusyGamePlayLibrary::CreateTextureBuffer(UObject* WorldContextObject)
}
void UBusyGamePlayLibrary::UpdateTextureBuffer(UTexture2D *DataTexture, TArray<float> FloatData){
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ա<EFBFBD>д<EFBFBD><D0B4>
DataTexture->GetPlatformData()->Mips[0];
FTexture2DMipMap& Mip = DataTexture->GetPlatformData()->Mips[0];
void* Data = Mip.BulkData.Lock(LOCK_READ_WRITE);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݸ<EFBFBD><DDB8>Ƶ<EFBFBD><C6B5><EFBFBD><EFBFBD><EFBFBD>
FMemory::Memcpy(Data, FloatData.GetData(), FloatData.Num() * sizeof(float));
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
Mip.BulkData.Unlock();
DataTexture->UpdateResource();
}

View File

@ -1,5 +0,0 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Gas/BusyAbilitySystemComponent.h"

View File

@ -7,21 +7,30 @@
ABusyPawnBase::ABusyPawnBase()
{
RootScene = CreateDefaultSubobject<USceneComponent>(TEXT("RootScene"));
SpineRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SpineRoot"));
SphereComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
SpineRenderComponent = CreateDefaultSubobject<USpineSkeletonRendererComponent>(TEXT("SpineRenderComponent"));
SpineAnimationComponent = CreateDefaultSubobject<USpineSkeletonAnimationComponent>(TEXT("SpineAnimationComponent"));
AbilitySystemComponent = CreateDefaultSubobject<UBusyAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
MovementComponent = CreateDefaultSubobject<UBusyPawnMovement>(TEXT("MovementComponent"));
RootComponent = RootScene;
SpineRoot->SetupAttachment(RootScene);
SphereComponent->SetupAttachment(SpineRoot);
SpineRenderComponent->SetupAttachment(SpineRoot);
SpineRoot->SetRelativeRotation(FRotator(0, 0, -90));
bReplicates = true;
SetReplicatingMovement(true);
NetUpdateFrequency = 60.0f;
MovementComponent->SetIsReplicated(true);
AbilitySystemComponent->SetIsReplicated(true);
}
void ABusyPawnBase::BeginPlay()
@ -40,3 +49,4 @@ float ABusyPawnBase::GetSpeed_Implementation()const
{
return 280;
}

View File

@ -0,0 +1,6 @@
#include "Level/Actor/Components/BusyAbilitySystemComponent.h"
FString UBusyAbilitySystemComponent::GetLuaFilePath_Implementation() const
{
return LuaFilePath;
}

View File

@ -30,10 +30,14 @@ FVector2D UBusyPawnMovement::GetMoveDirection()const
return FVector2D();
}
#pragma optimize("",off)
void UBusyPawnMovement::TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (!GetOwner()->HasAuthority()) return;
AActor* Owner = GetOwner();
const IBusyMovable* Movable = Cast<IBusyMovable>(Owner);
if (!Owner || !Movable) return;
@ -62,7 +66,11 @@ void UBusyPawnMovement::TickComponent(float DeltaTime, ELevelTick TickType,
if (!NewLocation.Equals(CurrentLocation))
{
Owner->SetActorLocation(NewLocation, true);
}
Movable->Execute_UpdateMoveDirection(Owner, GetMoveDirection());
Owner->ForceNetUpdate();
}
// Movable->Execute_UpdateMoveDirection(Owner, GetMoveDirection());
}
#pragma optimize("",on)

View File

@ -4,7 +4,6 @@
#include "Level/BusyLevelItem.h"
#include "Components/CapsuleComponent.h"
#include "Components/WidgetComponent.h"
#include "Gas/BusyAbilitySystemComponent.h"
#include "BusyGamePlayLibrary.h"
ABusyLevelItem::ABusyLevelItem(): CurrentItemID("100001") {
@ -16,7 +15,6 @@ ABusyLevelItem::ABusyLevelItem(): CurrentItemID("100001") {
PickBar = CreateDefaultSubobject<UWidgetComponent>(TEXT("PickBar"));
LevelItemAttribute = CreateDefaultSubobject<UBusyLevelItemAttributeSet>("LevelItemAttribute");
AbilityComponent = CreateDefaultSubobject<UBusyAbilitySystemComponent>("RoleAbility");
InitSprite();
InitCapsule();

View File

@ -1,6 +1,10 @@
#include "Level/LevelPlayerController.h"
#include "Level/Actor/BusyPlayerRole.h"
#include "EnhancedInput/Public/EnhancedInputSubsystems.h"
#include "Level/LevelPlayerState.h"
#include "EnhancedInput/Public/EnhancedInputSubsystems.h"
#include "EnhancedInput/Public/EnhancedInputComponent.h"
ALevelPlayerController::ALevelPlayerController()
{
@ -16,11 +20,27 @@ void ALevelPlayerController::BeginPlay()
SetInputMode(InputMode);
// 注册输入
if (!HasAuthority())
{
const auto Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer());
if (InputMapping && Subsystem)
{
Subsystem->AddMappingContext(InputMapping, 0);
}
if (UEnhancedInputComponent* EnhancedInput = CastChecked<UEnhancedInputComponent>(InputComponent)) {
EnhancedInput->BindAction(TouchAction, ETriggerEvent::Triggered, this, FName("OnTouch"));
}
}
}
void ALevelPlayerController::SetRoleMoveTo_Implementation(const FVector2D& Location)
{
ABusyPlayerRole* ControlledRole = GetControlledRole();
if (!ControlledRole) return;
ControlledRole->MovementComponent->MoveTo(Location);
}
bool ALevelPlayerController::GetCursorPosition(FVector2D& Position) const
@ -42,11 +62,25 @@ void ALevelPlayerController::GetCursorHitResult(TArray<AActor*>& Results) const
{
}
void ALevelPlayerController::GetControlledRole() const
ABusyPlayerRole* ALevelPlayerController::GetControlledRole() const
{
if (const ALevelPlayerState* PS = GetPlayerState<ALevelPlayerState>())
{
return PS->GetControlledRole();
}
return nullptr;
}
void ALevelPlayerController::SwitchControlledRole(ABusyPlayerRole* Target)
{
this->SetViewTarget(Target);
}
void ALevelPlayerController::OnTouch(const FInputActionValue& Value)
{
FVector2D Position;
if (GetCursorPosition(Position))
{
SetRoleMoveTo(Position);
}
}

View File

@ -1,6 +1,5 @@
#include "Level/LevelPlayerState.h"
#include "Level/LevelPlayerController.h"
#include "Net/UnrealNetwork.h"
#include "Level/Actor/BusyPlayerRole.h"
@ -10,38 +9,62 @@ DEFINE_LOG_CATEGORY(LogLevelPlayerState);
void ALevelPlayerState::BeginPlay()
{
Super::BeginPlay();
UWorld* World = GetWorld();
const auto PC = Cast<ALevelPlayerController>(GetPlayerController());
if (!World || !PC)
{
UE_LOG(LogLevelPlayerState, Error, TEXT("ALevelPlayerController::BeginPlay() failed!"));
return;
}
FVector2D SpawnLocation = GetSpawnLocation();
for (auto PawnClass : RoleClasses)
void ALevelPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
FTransform SpawnTransform;
FActorSpawnParameters Params;
SpawnTransform.SetLocation(FVector(SpawnLocation, 50));
if (auto *NewRole = Cast<ABusyPlayerRole>(World->SpawnActor(PawnClass, nullptr, Params)))
{
Roles.Add(NewRole);
}
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ALevelPlayerState, RoleRoster);
DOREPLIFETIME(ALevelPlayerState, ControlledRoleIndex);
}
if (Roles.IsValidIndex(0))
AActor* ALevelPlayerState::CreateRoleRoster(class APlayerController* PlayerController)
{
PC->SwitchControlledRole(Roles[0]);
if (!HasAuthority())
{
return nullptr;
}
UWorld* World = PlayerController->GetWorld();
if (!World)
{
return nullptr;
}
const FVector Location = FVector(GetSpawnLocation(), 10);
FActorSpawnParameters SpawnParameters;
for (const auto RoleClass : RoleClasses)
{
SpawnParameters.Owner = PlayerController;
ABusyPlayerRole* NewRole = World->SpawnActor<ABusyPlayerRole>(
RoleClass, Location,
FRotator(), SpawnParameters
);
RoleRoster.Add(NewRole);
}
if (RoleRoster.IsValidIndex(0))
{
ControlledRoleIndex = 0;
return RoleRoster[0];
}
else
{
ControlledRoleIndex = -1;
return nullptr;
}
}
ABusyPlayerRole* ALevelPlayerState::GetControlledRole() const
{
if (Roles.IsValidIndex(ControlledRoleIndex))
if (RoleRoster.IsValidIndex(ControlledRoleIndex))
{
return Roles[ControlledRoleIndex];
return RoleRoster[ControlledRoleIndex];
}
else
{
@ -53,3 +76,11 @@ FVector2D ALevelPlayerState::GetSpawnLocation()const
{
return FVector2D::ZeroVector;
}
void ALevelPlayerState::SetRoleRoster(const TArray<ABusyPlayerRole*>& Roster)
{
if (HasAuthority())
{
RoleRoster = Roster;
}
}

View File

@ -1,275 +0,0 @@
#include "Level/PaperTerrainMapActor.h"
#include "Level/TerrainGenerator.h"
#include "Level/TerrainGeneratorBlueprintLibrary.h"
#include "Paper2D/Classes/PaperTileMapComponent.h"
#include "Paper2D/Classes/PaperTileMap.h"
#include "Paper2D/Classes/PaperTileLayer.h"
#include "Engine/Engine.h"
#include "UObject/ConstructorHelpers.h"
/* 相邻四格地形信息映射到TileSet的索引
* 以左上、右上、左下、右下地形块存在为1不存在为0, 从左到右构建一个4位二进制整数
* 假设原始TileSet中0号索引左上、右上、右下为空, 对应二进制整数为0x0010
* 那么TileSet的这个索引与相邻数据对应关系就是[0] -> 2
* 也就是DefaultNeighborDataToIdxMappings[2] = 0
*/
const static int DefaultNeighborDataToIdxMappings[16] = {
12, 13, 0, 3, 8, 1, 14, 5, 15, 4, 11, 2, 9, 10, 7, 6
};
APaperTerrainMapActor::APaperTerrainMapActor()
{
PrimaryActorTick.bCanEverTick = false;
// 创建根组件
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
// 创建TileMap组件
TileMapComponent = CreateDefaultSubobject<UPaperTileMapComponent>(TEXT("TileMapComponent"));
TileMapComponent->SetupAttachment(RootComponent);
}
void APaperTerrainMapActor::BeginPlay()
{
Super::BeginPlay();
if (bAutoGenerateOnBeginPlay)
{
GenerateTerrainMap();
}
}
void APaperTerrainMapActor::OnConstruction(const FTransform& Transform)
{
Super::OnConstruction(Transform);
// 初始化地形生成器
InitializeGenerator();
// 初始化TileMap
InitializeTileMap();
if (bAutoGenerateOnConstruction)
{
GenerateTerrainMap();
}
}
void APaperTerrainMapActor::GenerateTerrainMap()
{
if (!TerrainGenerator)
{
UE_LOG(LogTemp, Error, TEXT("TerrainGenerator is null!"));
return;
}
if (!TerrainTileSetConfigs)
{
UE_LOG(LogTemp, Error, TEXT("TerrainTileSetConfigs is null!"));
return;
}
// 生成地形数据
GeneratedTerrainData = TerrainGenerator->GenerateMap(MapWidth, MapHeight);
// 应用到TileMap
ApplyTerrainToTileMap(GeneratedTerrainData);
UE_LOG(LogTemp, Log, TEXT("Terrain map generated successfully! Size: %dx%d"), MapWidth, MapHeight);
}
void APaperTerrainMapActor::ClearMap()
{
if (!TileMapComponent || !TileMapComponent->TileMap)
{
return;
}
// 清除所有Tile - 通过重新创建空的TileMap来实??
if (TileMapComponent->TileMap)
{
UPaperTileMap* NewTileMap = NewObject<UPaperTileMap>(this);
NewTileMap->MapWidth = MapWidth;
NewTileMap->MapHeight = MapHeight;
NewTileMap->TileWidth = TileSize;
NewTileMap->TileHeight = TileSize;
// 添加一个空图层
UPaperTileLayer* NewLayer = NewObject<UPaperTileLayer>(NewTileMap);
NewLayer->LayerName = FText::FromString("TerrainLayer");
NewTileMap->TileLayers.Add(NewLayer);
TileMapComponent->SetTileMap(NewTileMap);
}
GeneratedTerrainData.Empty();
UE_LOG(LogTemp, Log, TEXT("Map cleared!"));
}
#if WITH_EDITOR
void APaperTerrainMapActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
FName PropertyName = (PropertyChangedEvent.Property != nullptr)
? PropertyChangedEvent.Property->GetFName()
: NAME_None;
// 当地图尺寸或Tile大小改变时重新初始化TileMap
if (PropertyName == GET_MEMBER_NAME_CHECKED(APaperTerrainMapActor, MapWidth) ||
PropertyName == GET_MEMBER_NAME_CHECKED(APaperTerrainMapActor, MapHeight) ||
PropertyName == GET_MEMBER_NAME_CHECKED(APaperTerrainMapActor, TileSize) ||
PropertyName == GET_MEMBER_NAME_CHECKED(APaperTerrainMapActor, TileSetDataAsset))
{
InitializeTileMap();
GenerateTerrainMap();
}
}
#endif
void APaperTerrainMapActor::InitializeTileMap()
{
if (!TileMapComponent) return;
if (TileSetDataAsset)
{
TerrainTileSetConfigs = &TileSetDataAsset.Get()->TerrainTileSetConfigs;
}
else
{
TerrainTileSetConfigs = nullptr;
}
// 创建TileMap
UPaperTileMap *TileMap = NewObject<UPaperTileMap>(this);
// 设置TileMap参数
TileMap->MapWidth = MapWidth;
TileMap->MapHeight = MapHeight;
TileMap->TileWidth = TileSize;
TileMap->TileHeight = TileSize;
// 创建四层layer
for (int i = 0; i < 4; ++i)
{
UPaperTileLayer* NewLayer = NewObject<UPaperTileLayer>(TileMap);
NewLayer->LayerName = FText::FromString(
FString::Format(TEXT("TerrainLayer{0}"), {FString::FromInt(i)})
);
NewLayer->ResizeMap(MapWidth, MapHeight);
TileMap->TileLayers.Add(NewLayer);
}
TileMapComponent->SetTileMap(TileMap);
UE_LOG(LogTemp, Log, TEXT("TileMap initialized: %dx%d, TileSize: %d"), MapWidth, MapHeight, TileSize);
}
void APaperTerrainMapActor::InitializeGenerator()
{
// 创建地形生成器
if (GeneratorClass.Get())
{
TerrainGenerator = NewObject<UTerrainGeneratorBase>(this, GeneratorClass.Get());
UTerrainGeneratorBlueprintLibrary::SetupExampleTerrainConfig(TerrainGenerator);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("APaperTerrainMapActor::APaperTerrainMapActor, GeneratorClass is nullptr"));
}
}
void APaperTerrainMapActor::ApplyTerrainToTileMap(const TArray<FGameplayTag>& TerrainData) const
{
if (!TileMapComponent || !TileMapComponent->TileMap || TerrainData.Num() != MapWidth * MapHeight)
{
UE_LOG(LogTemp, Error, TEXT("Cannot apply terrain data to TileMap!"));
return;
}
if (TileMapComponent->TileMap->TileLayers.Num() == 0)
{
UE_LOG(LogTemp, Error, TEXT("No tile layers in TileMap!"));
return;
}
FGameplayTag TerrainTags[4];
const int32 LimitedWidth = MapWidth - 1;
const int32 LimitedHeight = MapHeight - 1;
for (int32 Y = 0; Y < LimitedHeight; Y++)
{
for (int32 X = 0; X < LimitedWidth; X++)
{
TerrainTags[0] = TerrainData[Y * MapWidth + X];
TerrainTags[1] = TerrainData[Y * MapWidth + X + 1];
TerrainTags[2] = TerrainData[(Y + 1) * MapWidth + X];
TerrainTags[3] = TerrainData[(Y + 1) * MapWidth + X + 1];
DrawTile(X, Y, TerrainTags);
}
}
UE_LOG(LogTemp, Log, TEXT("Terrain data applied to TileMap!"));
}
static inline int32 GetNeighborInfo(const FGameplayTag& TargetTag, FGameplayTag TerrainTags[4])
{
int32 NeighborInfo = 0;
NeighborInfo += (TargetTag == TerrainTags[0] ? 1 : 0) << 3;
NeighborInfo += (TargetTag == TerrainTags[1] ? 1 : 0) << 2;
NeighborInfo += (TargetTag == TerrainTags[2] ? 1 : 0) << 1;
NeighborInfo += (TargetTag == TerrainTags[3] ? 1 : 0);
return NeighborInfo;
}
static inline void GetSortedLayerDrawingInfo(FGameplayTag **LayerTerrainTags, int32 *LayerNeighborInfo, FGameplayTag TerrainTags[4])
{
// 返回0-3层每层应该画哪种地形格子以及这个格子的哪种形状可能需要排序
LayerTerrainTags[0] = &TerrainTags[0];
LayerTerrainTags[1] = &TerrainTags[1];
LayerTerrainTags[2] = &TerrainTags[2];
LayerTerrainTags[3] = &TerrainTags[3];
LayerNeighborInfo[0] = GetNeighborInfo(TerrainTags[0], TerrainTags);
LayerNeighborInfo[1] = GetNeighborInfo(TerrainTags[1], TerrainTags);
LayerNeighborInfo[2] = GetNeighborInfo(TerrainTags[2], TerrainTags);
LayerNeighborInfo[3] = GetNeighborInfo(TerrainTags[3], TerrainTags);
}
void APaperTerrainMapActor::DrawTile(const int32 X, const int32 Y, FGameplayTag TerrainTags[4])const
{
int32 LayerNeighborInfo[4];
FGameplayTag *LayerTerrainTags[4];
UPaperTileMap* TileMap = TileMapComponent->TileMap;
GetSortedLayerDrawingInfo(LayerTerrainTags, LayerNeighborInfo, TerrainTags);
for (int32 Index = 0; Index < 4; ++Index)
{
int32 TileIndex = 0;
FPaperTileInfo TileInfo;
const FTerrainTileSetConfig *TileSetConfig = TerrainTileSetConfigs->Find(*LayerTerrainTags[Index]);
if (!TileSetConfig)
{
UE_LOG(LogTemp, Warning, TEXT("APaperTerrainMapActor::DrawTile TileSetConfig not found: %s"), *LayerTerrainTags[Index]->GetTagName().ToString());
continue;
}
const int32 NeighborInfo = LayerNeighborInfo[Index];
if (TileSetConfig->bNeedOverrideMappings)
{
if (NeighborInfo < TileSetConfig->NeighborDataToIdxMappings.Num())
{
TileIndex = TileSetConfig->NeighborDataToIdxMappings[NeighborInfo];
}
else
{
UE_LOG(LogTemp, Warning, TEXT("APaperTerrainMapActor::DrawTile Neighbor Data not find: %s, %d"),
*LayerTerrainTags[Index]->GetTagName().ToString(), NeighborInfo
)
}
}
else
{
TileIndex = DefaultNeighborDataToIdxMappings[NeighborInfo];
}
UPaperTileLayer* TerrainLayer = TileMap->TileLayers[Index];
TileInfo.TileSet = TileSetConfig->TileSet;
TileInfo.PackedTileIndex = TileIndex;
TerrainLayer->SetCell(X, Y, TileInfo);
}
}

View File

@ -10,7 +10,6 @@
#include "Role/BusyRoleMovement.h"
#include "Role/RoleAnimation.h"
#include "BusyGamePlayLibrary.h"
#include "Gas/BusyAbilitySystemComponent.h"
#include "Components/InventoryComponent.h"
#include "Core/PW_AbilitySystemComponent.h"
#include "EnhancedInputComponent.h"
@ -71,7 +70,7 @@ void ABusyRole::SetRole(const FName& Name){
RoleName = Name;
RoleConfig = *Row;
// <20><><EFBFBD>û<EFBFBD><C3BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// <20><><EFBFBD>û<EFBFBD><C3BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
if (RoleAttribute) {
RoleAttribute->InitHealth(Row->Health);
RoleAttribute->InitHunger(Row->Hunger);
@ -79,7 +78,7 @@ void ABusyRole::SetRole(const FName& Name){
RoleAttribute->RegisterCustomAttribute();
}
// <20><><EFBFBD>ü<EFBFBD><C3BC><EFBFBD>
// <20><><EFBFBD>ü<EFBFBD><C3BC><EFBFBD>
for (UClass* AbilityClass : Row->DefaultAbilities) {
if (AbilityClass) {
RoleAbility->GiveAbility(FGameplayAbilitySpec(AbilityClass));

View File

@ -33,7 +33,7 @@ public:
static UClass* GetGameUIClass(const FString& ClassName);
UFUNCTION(BlueprintPure)
static UWorld* K2_GetWorld(UObject* obj);
static UWorld* K2_GetWorld(const UObject* obj);
UFUNCTION(BlueprintPure)
static bool GetLevelBaseConfig(const FName& RowName, FBusyLevelBaseConfig& RowData);

View File

@ -1,17 +0,0 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "AbilitySystemComponent.h"
#include "BusyAbilitySystemComponent.generated.h"
/**
*
*/
UCLASS()
class BUSYRABBIT_API UBusyAbilitySystemComponent : public UAbilitySystemComponent
{
GENERATED_BODY()
};

View File

@ -1,6 +1,7 @@
#pragma once
#include "LuaPawn.h"
#include "Level/Actor/Components/BusyPawnMovement.h"
#include "Level/Actor/Components/BusyAbilitySystemComponent.h"
#include "BusyPawnBase.generated.h"
@ -8,6 +9,7 @@ class USphereComponent;
class USpineBoneFollowerComponent;
class USpineSkeletonRendererComponent;
class USpineSkeletonAnimationComponent;
class UBusyPawnMovementComponent;
UCLASS()
@ -44,7 +46,13 @@ protected:
TObjectPtr<USpineSkeletonAnimationComponent> SpineAnimationComponent;
/*-------------------------------------------------------------------*/
/*-----------------------------GAS相关--------------------------------*/
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TObjectPtr<UBusyAbilitySystemComponent> AbilitySystemComponent;
/*-------------------------------------------------------------------*/
public:
/*-------------------------------移动组件------------------------------*/
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TObjectPtr<UBusyPawnMovement> MovementComponent;

View File

@ -0,0 +1,17 @@
#pragma once
#include "slua.h"
#include "AbilitySystemComponent.h"
#include "BusyAbilitySystemComponent.generated.h"
UCLASS()
class UBusyAbilitySystemComponent : public UAbilitySystemComponent, public ILuaOverriderInterface
{
GENERATED_BODY()
public:
virtual FString GetLuaFilePath_Implementation() const override;
protected:
FString LuaFilePath;
};

View File

@ -37,9 +37,6 @@ public:
FVector2D GetMoveDirection()const;
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)override;
public:
UPROPERTY(EditAnywhere, BlueprintReadOnly)
float MoveSpeed = 400;
protected:
UPROPERTY(EditAnywhere, BlueprintReadOnly)

View File

@ -16,6 +16,12 @@ public:
virtual void BeginPlay() override;
public: // RPC相关
UFUNCTION(Server, Reliable)
void SetRoleMoveTo(const FVector2D& Location);
void SetRoleMoveTo_Implementation(const FVector2D& Location);
public:
UFUNCTION(BlueprintCallable, Category = "Controller")
bool GetCursorPosition(FVector2D& Position) const;
@ -24,7 +30,7 @@ public:
void GetCursorHitResult(TArray<AActor*>& Results) const;
UFUNCTION(BlueprintCallable, Category = "Controller")
void GetControlledRole() const;
ABusyPlayerRole* GetControlledRole() const;
UFUNCTION(BlueprintCallable, Category = "Controller")
void SwitchControlledRole(ABusyPlayerRole* Target);
@ -38,4 +44,7 @@ public: // 输入相关
UPROPERTY(EditDefaultsOnly, Category = "Input")
TObjectPtr<class UInputAction> TouchAction;
UFUNCTION()
void OnTouch(const FInputActionValue& Value);
};

View File

@ -15,11 +15,28 @@ class ALevelPlayerState : public ALuaPlayerState
public:
virtual void BeginPlay() override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
public: // 给蓝图初始化的接口
UFUNCTION(BlueprintCallable)
AActor* CreateRoleRoster(class APlayerController* PlayerController);
public: // 给蓝图的Get接口
UFUNCTION(BlueprintCallable)
ABusyPlayerRole* GetControlledRole() const;
public: // 给蓝图的Set接口
UFUNCTION(BlueprintCallable)
void SetRoleRoster(const TArray<ABusyPlayerRole*>& Roster);
public:
public:
UFUNCTION(BlueprintCallable)
ABusyPlayerRole* GetControlledRole() const;
@ -27,14 +44,17 @@ protected:
virtual FVector2D GetSpawnLocation()const;
public:
UPROPERTY(EditAnywhere)
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TArray<TSubclassOf<ABusyPlayerRole>> RoleClasses;
protected:
UPROPERTY()
UPROPERTY(Replicated, BlueprintReadOnly)
int ControlledRoleIndex = -1;
UPROPERTY()
TArray<ABusyPlayerRole*> Roles;
UPROPERTY(Replicated, BlueprintReadOnly)
TArray<ABusyPlayerRole*> RoleRoster;
};

View File

@ -1,97 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "GameplayTagContainer.h"
#include "Generator/TerrainGeneratorBase.h"
#include "Map/TerrainLayerComponent.h"
#include "PaperTerrainMapActor.generated.h"
UCLASS(Blueprintable, BlueprintType)
class BUSYRABBIT_API APaperTerrainMapActor : public AActor
{
GENERATED_BODY()
public:
APaperTerrainMapActor();
protected:
virtual void BeginPlay() override;
virtual void OnConstruction(const FTransform& Transform) override;
public:
// 生成地图(可在蓝图中调用)
UFUNCTION(BlueprintCallable, Category = "Terrain Map")
void GenerateTerrainMap();
// 清除当前地图
UFUNCTION(BlueprintCallable, Category = "Terrain Map")
void ClearMap();
#if WITH_EDITOR
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
protected: // 生成器相关
// 地形生成器类
UPROPERTY(EditAnywhere, Category = "Terrain Map")
TSubclassOf<class UTerrainGeneratorBase> GeneratorClass;
// 地形生成器实例
UPROPERTY()
TObjectPtr<class UTerrainGeneratorBase> TerrainGenerator;
// 生成的地形数据
TArray<FGameplayTag> GeneratedTerrainData;
protected: // 地图配置相关
// 地图宽度
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings", meta = (ClampMin = "1", ClampMax = "1024"))
int32 MapWidth = 16;
// 地图高度
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings", meta = (ClampMin = "1", ClampMax = "1024"))
int32 MapHeight = 16;
// 瓦片大小(像素)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings", meta = (ClampMin = "1"))
int32 TileSize = 16;
protected: // 瓦片相关
TMap<FGameplayTag, FTerrainTileSetConfig> *TerrainTileSetConfigs;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings", DisplayName="瓦片设置")
TObjectPtr<UTerrainDoubleGridDataAsset> TileSetDataAsset;
protected:
// Paper2D瓦片地图组件
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TObjectPtr<class UPaperTileMapComponent> TileMapComponent;
// 是否在构造时自动生成地图
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings")
bool bAutoGenerateOnConstruction = true;
// 是否在开始时自动生成地图
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings")
bool bAutoGenerateOnBeginPlay = true;
private:
// 初始化TileMap组件
void InitializeTileMap();
void InitializeGenerator();
// 应用地形数据到TileMap
void ApplyTerrainToTileMap(const TArray<FGameplayTag>& TerrainData)const;
void DrawTile(const int32 X, const int32 Y, FGameplayTag TerrainTags[4])const;
};

125
Tools/README.md Normal file
View File

@ -0,0 +1,125 @@
# UE头文件解析工具
这是一个用于扫描UE头文件并生成slua插件的emmy-lua注解的Python工具。
## 功能特性
- 自动扫描指定目录下的所有UE头文件(.h)
- 解析UCLASS、USTRUCT、UENUM、UFUNCTION、UPROPERTY等宏
- 生成符合emmy-lua标准的类型注解文件(.d.lua)
- 支持C++类型到Lua类型的自动转换
- 支持递归扫描子目录
## 支持的UE宏
- **UCLASS**: 解析类定义,包括继承关系
- **USTRUCT**: 解析结构体定义和属性
- **UENUM**: 解析枚举定义和值
- **UFUNCTION**: 解析函数声明,包括参数和返回值
- **UPROPERTY**: 解析属性声明
- **DECLARE_DYNAMIC_DELEGATE**: 解析委托声明
## 安装和使用
### 前置要求
- Python 3.6+
- 无需额外依赖
### 基本用法
```bash
# 扫描Source目录并生成注解文件到Content/Lua/@types目录
python ue_header_parser.py Source/BusyRabbit -o Content/Lua/@types
# 扫描当前目录并生成注解文件到同目录
python ue_header_parser.py .
# 递归扫描整个项目
python ue_header_parser.py Source --recursive -o Content/Lua/@types
```
### 命令行参数
- `directory`: 要扫描的目录路径(必需)
- `-o, --output`: 输出目录路径(可选,默认为源文件同目录)
- `--recursive`: 递归扫描子目录(可选)
## 生成的注解格式
工具会根据UE头文件生成对应的emmy-lua注解
### 类注解示例
```lua
---@class UInventoryComponent : ULuaActorComponent
---@field Capacity integer
---@field InventoryList table<FInventoryGrid>
---@param ItemID integer
---@param Count integer
---@return boolean
function UInventoryComponent:IsCanContain(ItemID, Count) end
```
### 结构体注解示例
```lua
---@class FInventoryGrid
---@field ItemID integer
---@field CurrentCount integer
---@field MaxCount integer
---@field Priority integer
local FInventoryGrid = {}
```
### 枚举注解示例
```lua
---@enum EBusyRoleState
local EBusyRoleState = {
BonfireIdle = 0,
Searching = 1,
Picking = 2,
PickFinished = 3,
BackBonfire = 4
}
```
### 委托注解示例
```lua
---@class FOnInventoryChanged
---@field Call fun(ItemID: integer)
local FOnInventoryChanged = {}
```
## 类型映射
工具会自动将C++类型映射到Lua类型
| C++类型 | Lua类型 |
|---------|---------|
| int32, int64 | integer |
| float, double | number |
| bool | boolean |
| FString, FText, FName | string |
| void | nil |
| TArray, TMap, TSet | table |
| 其他类型 | any |
## 注意事项
1. 工具会跳过没有UE宏的普通头文件
2. 生成的注解文件会保存在`.d.lua`文件中
3. 如果输出目录不存在,工具会自动创建
4. 工具会处理编码问题但建议确保头文件使用UTF-8编码
5. 对于复杂的模板类型,工具会尝试解析内部类型
## 故障排除
如果遇到解析错误,请检查:
- 头文件语法是否正确
- UE宏格式是否符合标准
- 文件编码是否为UTF-8
- 是否有语法错误或缺失的分号
## 贡献
欢迎提交问题和改进建议!

402
Tools/ue_header_parser.py Normal file
View File

@ -0,0 +1,402 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
UE头文件解析工具
用于扫描UE头文件并生成slua插件的emmy-lua注解
"""
import os
import re
import argparse
from pathlib import Path
from typing import List, Dict, Set, Optional
class UEHeaderParser:
def __init__(self):
self.classes = []
self.structs = []
self.enums = []
self.delegates = []
def parse_header_file(self, file_path: str) -> Dict:
"""解析单个头文件"""
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
result = {
'classes': [],
'structs': [],
'enums': [],
'delegates': [],
'file_path': file_path
}
# 解析UCLASS
class_pattern = r'UCLASS\s*\([^)]*\)\s*\n\s*class\s+[A-Z_]+\s+(\w+)\s*:\s*public\s+(\w+)'
classes = re.findall(class_pattern, content)
for class_name, parent_class in classes:
result['classes'].append({
'name': class_name,
'parent': parent_class,
'functions': self._parse_functions(content, class_name),
'properties': self._parse_properties(content, class_name)
})
# 解析USTRUCT
struct_pattern = r'USTRUCT\s*\([^)]*\)\s*\n\s*struct\s+[A-Z_]+\s+(\w+)'
structs = re.findall(struct_pattern, content)
for struct_name in structs:
result['structs'].append({
'name': struct_name,
'properties': self._parse_struct_properties(content, struct_name)
})
# 解析USTRUCT (BlueprintType变体)
struct_pattern2 = r'USTRUCT\s*\([^)]*\)\s*\n\s*struct\s+F(\w+)'
structs2 = re.findall(struct_pattern2, content)
for struct_name in structs2:
result['structs'].append({
'name': f'F{struct_name}',
'properties': self._parse_struct_properties(content, f'F{struct_name}')
})
# 解析UENUM
enum_pattern = r'UENUM\s*\([^)]*\)\s*\n\s*enum\s+class\s+(\w+)'
enums = re.findall(enum_pattern, content)
for enum_name in enums:
result['enums'].append({
'name': enum_name,
'values': self._parse_enum_values(content, enum_name)
})
# 解析委托
delegate_pattern = r'DECLARE_DYNAMIC_(MULTICAST_)?DELEGATE(?:_\w+)?\s*\(\s*(\w+)\s*'
delegates = re.findall(delegate_pattern, content)
for is_multicast, delegate_name in delegates:
result['delegates'].append({
'name': delegate_name,
'is_multicast': bool(is_multicast),
'params': self._parse_delegate_params(content, delegate_name)
})
return result
def _parse_functions(self, content: str, class_name: str) -> List[Dict]:
"""解析UFUNCTION"""
functions = []
# 匹配UFUNCTION声明
func_pattern = r'UFUNCTION\s*\([^)]*\)\s*\n\s*(\w+)\s+(\w+)\s*\(([^)]*)\)'
matches = re.findall(func_pattern, content)
for return_type, func_name, params in matches:
# 解析参数
param_list = []
if params.strip():
for param in params.split(','):
param = param.strip()
if param:
# 简单的参数解析
parts = param.split()
if len(parts) >= 2:
param_type = parts[-2] if len(parts) > 2 else parts[0]
param_name = parts[-1]
param_list.append({
'type': param_type,
'name': param_name
})
functions.append({
'name': func_name,
'return_type': return_type,
'params': param_list
})
return functions
def _parse_properties(self, content: str, class_name: str) -> List[Dict]:
"""解析UPROPERTY"""
properties = []
# 匹配UPROPERTY声明
prop_pattern = r'UPROPERTY\s*\([^)]*\)\s*\n\s*(\w+(?:<[^>]*>)?)\s+(\w+);'
matches = re.findall(prop_pattern, content)
for prop_type, prop_name in matches:
properties.append({
'name': prop_name,
'type': prop_type
})
return properties
def _parse_struct_properties(self, content: str, struct_name: str) -> List[Dict]:
"""解析结构体属性"""
properties = []
# 在结构体定义范围内查找属性
struct_start = content.find(f'struct {struct_name}')
if struct_start == -1:
return properties
# 找到结构体结束位置
brace_count = 0
struct_content = ""
for i in range(struct_start, len(content)):
char = content[i]
if char == '{':
brace_count += 1
elif char == '}':
brace_count -= 1
if brace_count == 0:
struct_content = content[struct_start:i+1]
break
if not struct_content:
return properties
# 在结构体内容中查找UPROPERTY
prop_pattern = r'UPROPERTY\s*\([^)]*\)\s*\n\s*(\w+(?:<[^>]*>)?)\s+(\w+);'
matches = re.findall(prop_pattern, struct_content)
for prop_type, prop_name in matches:
properties.append({
'name': prop_name,
'type': prop_type
})
return properties
def _parse_enum_values(self, content: str, enum_name: str) -> List[Dict]:
"""解析枚举值"""
values = []
# 找到枚举定义
enum_start = content.find(f'enum class {enum_name}')
if enum_start == -1:
return values
# 找到枚举内容
brace_start = content.find('{', enum_start)
if brace_start == -1:
return values
brace_end = content.find('}', brace_start)
if brace_end == -1:
return values
enum_content = content[brace_start+1:brace_end]
# 解析枚举值
value_pattern = r'(\w+)\s*(?:=\s*(\d+))?'
matches = re.findall(value_pattern, enum_content)
current_value = 0
for name, explicit_value in matches:
if explicit_value:
current_value = int(explicit_value)
values.append({
'name': name,
'value': current_value
})
current_value += 1
return values
def _parse_delegate_params(self, content: str, delegate_name: str) -> List[Dict]:
"""解析委托参数"""
params = []
# 找到委托声明
delegate_pattern = f'DECLARE_DYNAMIC_(MULTICAST_)?DELEGATE(?:_\\w+)?\\s*\\(\\s*{delegate_name}'
match = re.search(delegate_pattern, content)
if not match:
return params
# 查找参数列表
param_start = content.find('(', match.end())
if param_start == -1:
return params
param_end = content.find(')', param_start)
if param_end == -1:
return params
param_content = content[param_start+1:param_end]
# 解析参数
param_parts = [p.strip() for p in param_content.split(',') if p.strip()]
for i, param in enumerate(param_parts):
parts = param.split()
if len(parts) >= 2:
param_type = ' '.join(parts[:-1])
param_name = parts[-1]
params.append({
'type': param_type,
'name': param_name
})
return params
def generate_emmy_lua_annotations(self, parsed_data: Dict) -> str:
"""生成emmy-lua注解"""
output = []
# 文件头注释
output.append(f'-- 自动生成的emmy-lua注解文件')
output.append(f'-- 源文件: {parsed_data["file_path"]}')
output.append('')
# 生成枚举注解
for enum in parsed_data['enums']:
output.append(f'---@enum {enum["name"]}')
output.append(f'local {enum["name"]} = {{')
for value in enum['values']:
output.append(f' {value["name"]} = {value["value"]},')
output.append('}')
output.append('')
# 生成结构体注解
for struct in parsed_data['structs']:
output.append(f'---@class {struct["name"]}')
for prop in struct['properties']:
lua_type = self._cpp_to_lua_type(prop['type'])
output.append(f'---@field {prop["name"]} {lua_type}')
output.append(f'local {struct["name"]} = {{}}')
output.append('')
# 生成类注解
for cls in parsed_data['classes']:
output.append(f'---@class {cls["name"]} : {cls["parent"]}')
# 添加属性
for prop in cls['properties']:
lua_type = self._cpp_to_lua_type(prop['type'])
output.append(f'---@field {prop["name"]} {lua_type}')
# 添加方法
for func in cls['functions']:
lua_return_type = self._cpp_to_lua_type(func['return_type'])
param_annotations = []
for param in func['params']:
lua_param_type = self._cpp_to_lua_type(param['type'])
param_annotations.append(f'---@param {param["name"]} {lua_param_type}')
if param_annotations:
output.extend(param_annotations)
output.append(f'---@return {lua_return_type}')
output.append(f'function {cls["name"]}:{func["name"]}({", ".join(p["name"] for p in func["params"])}) end')
output.append('')
output.append('')
# 生成委托注解
for delegate in parsed_data['delegates']:
output.append(f'---@class {delegate["name"]}')
param_types = []
for param in delegate['params']:
lua_type = self._cpp_to_lua_type(param['type'])
param_types.append(f'{param["name"]}: {lua_type}')
if param_types:
output.append(f'---@field Call fun({", ".join(param_types)})')
else:
output.append('---@field Call fun()')
output.append(f'local {delegate["name"]} = {{}}')
output.append('')
return '\n'.join(output)
def _cpp_to_lua_type(self, cpp_type: str) -> str:
"""将C++类型转换为Lua类型"""
type_mapping = {
'int32': 'integer',
'int64': 'integer',
'float': 'number',
'double': 'number',
'bool': 'boolean',
'FString': 'string',
'FText': 'string',
'FName': 'string',
'void': 'nil',
'TArray': 'table',
'TMap': 'table',
'TSet': 'table'
}
# 处理模板类型
if '<' in cpp_type and '>' in cpp_type:
base_type = cpp_type.split('<')[0]
inner_type = cpp_type.split('<')[1].split('>')[0]
lua_inner_type = self._cpp_to_lua_type(inner_type)
return f'{type_mapping.get(base_type, "any")}<{lua_inner_type}>'
return type_mapping.get(cpp_type, 'any')
def scan_directory(self, directory: str, output_dir: str = None):
"""扫描目录或单个文件并生成注解文件"""
header_files = []
if os.path.isfile(directory) and directory.endswith('.h'):
# 单个文件
header_files = [directory]
elif os.path.isdir(directory):
# 目录
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith('.h'):
header_files.append(os.path.join(root, file))
else:
print(f'错误: {directory} 不是有效的文件或目录')
return
print(f'找到 {len(header_files)} 个头文件')
for header_file in header_files:
try:
print(f'正在解析: {header_file}')
parsed_data = self.parse_header_file(header_file)
if any([parsed_data['classes'], parsed_data['structs'], parsed_data['enums'], parsed_data['delegates']]):
annotations = self.generate_emmy_lua_annotations(parsed_data)
# 确定输出文件路径
if output_dir:
if os.path.isfile(directory):
# 单个文件的情况
output_file = os.path.join(output_dir, os.path.basename(header_file).replace('.h', '.d.lua'))
else:
# 目录的情况
relative_path = os.path.relpath(header_file, directory)
output_file = os.path.join(output_dir, relative_path.replace('.h', '.d.lua'))
os.makedirs(os.path.dirname(output_file), exist_ok=True)
else:
output_file = header_file.replace('.h', '.d.lua')
with open(output_file, 'w', encoding='utf-8') as f:
f.write(annotations)
print(f'已生成: {output_file}')
except Exception as e:
print(f'解析文件 {header_file} 时出错: {e}')
def main():
parser = argparse.ArgumentParser(description='UE头文件解析工具 - 生成slua插件的emmy-lua注解')
parser.add_argument('directory', help='要扫描的目录路径')
parser.add_argument('-o', '--output', help='输出目录路径(默认为源文件同目录)')
parser.add_argument('--recursive', action='store_true', help='递归扫描子目录')
args = parser.parse_args()
if not os.path.exists(args.directory):
print(f'错误: 目录 {args.directory} 不存在')
return
parser = UEHeaderParser()
parser.scan_directory(args.directory, args.output)
if __name__ == '__main__':
main()

104
Tools/使用示例.md Normal file
View File

@ -0,0 +1,104 @@
# UE头文件解析工具使用示例
## 基本用法
### 扫描整个项目
```bash
python Tools/ue_header_parser.py Source/BusyRabbit/Public -o Content/Lua/@types
```
### 扫描特定目录
```bash
# 扫描Components目录
python Tools/ue_header_parser.py Source/BusyRabbit/Public/Components -o Content/Lua/@types
# 扫描Level目录
python Tools/ue_header_parser.py Source/BusyRabbit/Public/Level -o Content/Lua/@types
```
### 扫描单个文件
```bash
# 直接指定文件路径(需要先确保输出目录存在)
python Tools/ue_header_parser.py Source/BusyRabbit/Public/Components/InventoryComponent.h -o Content/Lua/@types
```
## 生成结果示例
### 输入头文件 (InventoryComponent.h)
```cpp
USTRUCT(BlueprintType)
struct FInventoryGrid {
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadOnly, DisplayName = "物品ID")
int32 ItemID;
UPROPERTY(BlueprintReadWrite, DisplayName = "当前的数量")
int32 CurrentCount;
UPROPERTY(BlueprintReadWrite, DisplayName = "最大堆叠限制")
int32 MaxCount;
UPROPERTY(BlueprintReadWrite, DisplayName = "优先级")
int32 Priority;
};
UCLASS()
class BUSYRABBIT_API UInventoryComponent : public ULuaActorComponent {
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
bool IsCanContain(int32 ItemID, int32 Count);
// ... 其他函数和属性
};
```
### 输出注解文件 (InventoryComponent.d.lua)
```lua
-- 自动生成的emmy-lua注解文件
-- 源文件: Source/BusyRabbit/Public/Components\InventoryComponent.h
---@class FInventoryGrid
---@field ItemID integer
---@field CurrentCount integer
---@field MaxCount integer
---@field Priority integer
local FInventoryGrid = {}
---@class UInventoryComponent : ULuaActorComponent
---@field Capacity integer
---@field InventoryList table<any>
---@param ItemID integer
---@param Count integer
---@return boolean
function UInventoryComponent:IsCanContain(ItemID, Count) end
-- ... 其他函数注解
```
## 集成到开发流程
### 1. 定期生成注解
建议在每次UE头文件更新后运行工具重新生成注解。
### 2. 版本控制
将生成的`.d.lua`文件添加到版本控制中,方便团队共享。
### 3. IDE配置
确保IDE如VSCode能够识别`Content/Lua/@types`目录中的注解文件。
## 注意事项
1. **类型映射**: 工具会自动将C++类型映射到Lua类型
2. **模板类型**: 支持`TArray<FInventoryGrid>`等模板类型的解析
3. **委托支持**: 自动生成委托类型的Call函数注解
4. **错误处理**: 工具会跳过无法解析的文件并继续处理其他文件
## 故障排除
如果遇到问题,请检查:
- 头文件语法是否正确
- UE宏格式是否符合标准
- 输出目录权限是否足够
- Python版本是否为3.6+