From 4bb809b8e3a9c8e7caf94fe10dfc91490aed45f2 Mon Sep 17 00:00:00 2001 From: wyatt Date: Wed, 24 Sep 2025 13:57:17 +0800 Subject: [PATCH] =?UTF-8?q?=E8=81=94=E6=9C=BA=E7=89=88=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=EF=BC=8C=E5=85=88=E5=A4=87=E4=BB=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GameMode/BP_BusyLevelGameMode.uasset | Bin 21355 -> 21502 bytes .../Private/BusyGamePlayLibrary.cpp | 8 +- .../Gas/BusyAbilitySystemComponent.cpp | 5 - .../Private/Level/Actor/BusyPawnBase.cpp | 12 +- .../Components/BusyAbilitySystemComponent.cpp | 6 + .../Actor/Components/BusyPawnMovement.cpp | 14 +- .../Private/Level/BusyLevelItem.cpp | 2 - .../Private/Level/LevelPlayerController.cpp | 42 +- .../Private/Level/LevelPlayerState.cpp | 87 ++-- .../Private/Level/PaperTerrainMapActor.cpp | 275 ------------ Source/BusyRabbit/Private/Role/BusyRole.cpp | 5 +- .../BusyRabbit/Public/BusyGamePlayLibrary.h | 2 +- .../Public/Gas/BusyAbilitySystemComponent.h | 17 - .../Public/Level/Actor/BusyPawnBase.h | 12 +- .../Components/BusyAbilitySystemComponent.h | 17 + .../Level/Actor/Components/BusyPawnMovement.h | 3 - .../Public/Level/LevelPlayerController.h | 11 +- .../Public/Level/LevelPlayerState.h | 32 +- .../Public/Level/PaperTerrainMapActor.h | 97 ----- Tools/README.md | 125 ++++++ Tools/ue_header_parser.py | 402 ++++++++++++++++++ Tools/使用示例.md | 104 +++++ 22 files changed, 824 insertions(+), 454 deletions(-) delete mode 100644 Source/BusyRabbit/Private/Gas/BusyAbilitySystemComponent.cpp create mode 100644 Source/BusyRabbit/Private/Level/Actor/Components/BusyAbilitySystemComponent.cpp delete mode 100644 Source/BusyRabbit/Private/Level/PaperTerrainMapActor.cpp delete mode 100644 Source/BusyRabbit/Public/Gas/BusyAbilitySystemComponent.h create mode 100644 Source/BusyRabbit/Public/Level/Actor/Components/BusyAbilitySystemComponent.h delete mode 100644 Source/BusyRabbit/Public/Level/PaperTerrainMapActor.h create mode 100644 Tools/README.md create mode 100644 Tools/ue_header_parser.py create mode 100644 Tools/使用示例.md diff --git a/Content/Blueprint/Level/GameMode/BP_BusyLevelGameMode.uasset b/Content/Blueprint/Level/GameMode/BP_BusyLevelGameMode.uasset index ee2dc7774b2b141c916edbb6b245974e0ec46723..8459331e2d7a965c0e354477fdf1f14469f2bbb5 100644 GIT binary patch delta 4148 zcmc&%X>3$g6uz&E&<&<#-s?i0E=noIu1>`+(3$DpSvqY2wN>lb4lvNgw$qxrWF(@A zQ6uEq_{U(3QDaOL(r8RbR9yZ@RFL?~ACO3*h@eEVA_?`J`_3I+XSz&?i6@HKd$B{JG-=Q|ZAscdUK~`Kj=m zJ(`Mh7N`{UgB%mTHMRiQ{lh`dR`* zey=aMhh3{W?Vc@Mbs|wQ@k^;C@XSuLgnYn4`3=T3pqtE28Wk1@Me;5IZ%}tF;Ni?s z{JLTnfwz}zW4s7nNDzlsCJNqg47^_<@o0>^M_5Q_E;B-Q0Rr~N5D<9}#mMtX9_1I! zh|-9}0ZAl{YBAnKjJ#k3Pvl=Lpf`eu%}CxaFyoO$cn^|KV#wRQ2oK`v^B*G+6&?%} zyRO7jCN}c!CwwK|1IzLZ^_NLj3Mfy+N<3KwBX15WVVD;)snkjVeUT!=HRW|#_>ZP~ z?O}qGKz~p6?>1hbS$I!+y|#;7m7r(H{?ZO`5kHZhTXu+CR}5Kkmix%R8uSNb_c}zk zx(us!uK_+v_S!}PzY8!PR*ZtZcgE}Zr#ifTomE8`CxY;<1Rd7+skV*$`E^zePq6HK z2n~=YoFeu$fX8)@2vD4;H`3ftjv~I#Y}NJJ5{ZLETSkz1$2WeDdLYJYQc)Oxl`Pr@JBzamK z{D{pQx<`i?O+X|y2Eoz@Cq}y?Rx(DD+r=2Uo&uoC#RosQEujYADkyecv5lnfB5jNy zUr|_`0zTi7X{kjMSFw=jMoJg|wms8=V;2)~Ekfx<3imXqwQfQ;lSUr7Wf_z3y75dV(>UcyF`s8_swRglNDG#|+6dtmK?)9CF1=VB z;ma3(#Mbevwhf_h&p!<(70XY@3oOQ}#Z!NzCLdr|fm=pC3?(HT3>>2W7DBou19TSJ z;_JabvaLy;6OSh?NRwLs)$`#S<^Dfx`+F1CB$&24x2`60La z();Q~C(iN>XWjBUCR;|rf#PPCH)|(<-CLTGM&rkeCpbRoXI1>H_fQfN@zCV(VRu$e zP9zCWiw?i$&Qdh~k-H`{pGf!yaroiiH}8;dn$_{+o=wV5UgfC;-mu4!A$gc_dthK+ zm~z3>n1Nq{DBh3m`;+I8l6wpls)+?rQZuy}^u|L4SFUb*qJU5*l^I$ycMDk2%aCoH`2D*O(KR jL^!cV^bxW0M;4NA#hxI>Xd(cD#$p_Qy5umg>#6t`SP}S_ delta 3951 zcmbtWU2IfE6rNkVw%gy`viG)?{sES@*lmH9LVwt8cWF!iwzLbVMTJ@l6#8eaCMt0y zpos|y(4!B=sL?2f1dT#?Km(>4;)BM7h!4J?4-$e%4T(JXAbQT+Gs~sBx2@^P-aTjL z`{vA?GiT=ByB}*ePin`1bDZBOL^bS8seo5%lc7*^WK0Mlya__!Ph3lbjNVe{(^A{s za|vN!S7o{ozvx0}XqR9|B|aVaxPQKKb;PB&oxVI?Q&{wc@8S-B>Wz252^@8KkAJi+ z`4sWf@R@*5wXpXbj#wYYrbYfmq2)Y=ef1XX-tu-QY&d%%VSgetB;}nQPWsNFWx|H^ zRgp~gl8UB%#1QdiYlBW zUS_Pk$4%ONyol&q7%{p{^G&Qy#C$w63lCrGcl6bR=neX!Ldrg2Wq?@Zb^?rXUF3lSrTthg&546T);*)#wwv%QxC3-0Z0~ z#w6doTuL2gc1DM`_{v>@F z!`2}oIONYRAcwToqAd>PQKXbR+ZkB}J&m4xwK7|maM$nD7wD}*d8^<#VzRDEk??ew z_T;2nV_1y~%!4b9F3kt;`#na?aN0BF+;skvTjgqgNnXFfB1aLDa}U2TaP zUE5XgYo`}_TS~QF$ttz5a&WxKsL&Uf*ACmfwkpT0ZS&f|gev$9_i0gCUVM_Zxw1UH zxg0Yzv)Mkwt3MVBcF9plM~=LW?Z*wgivmOB;7smhgz z1*7t&QxXFZk?txTIzqMCh15hz!6z`dcW89WaQ_Qp1H2t7)535mRHx}v-GMAcqp7zJ z4IGFvUhWQN(O8n`x^(!l``9uyhF-qGvC(I7$pe!^W1~gkfr+8<$*7A@w!Ai1fF~Rj zMiV%Dy%85pgA#L?Rl-de|M5#i3g5)imNzuLT(a#ziew3cJ*-I;(_gZHbG<9{?PQ}^ zi^O~nI>WBuS`!p8*PeRs-M7-s%$6%dnGet() : nullptr; } -UWorld* UBusyGamePlayLibrary::K2_GetWorld(UObject* obj){ - return Cast(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){ - // 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 FloatData){ - // Աд DataTexture->GetPlatformData()->Mips[0]; FTexture2DMipMap& Mip = DataTexture->GetPlatformData()->Mips[0]; void* Data = Mip.BulkData.Lock(LOCK_READ_WRITE); - // ݸƵ FMemory::Memcpy(Data, FloatData.GetData(), FloatData.Num() * sizeof(float)); - // Mip.BulkData.Unlock(); DataTexture->UpdateResource(); } diff --git a/Source/BusyRabbit/Private/Gas/BusyAbilitySystemComponent.cpp b/Source/BusyRabbit/Private/Gas/BusyAbilitySystemComponent.cpp deleted file mode 100644 index d3ce2bb..0000000 --- a/Source/BusyRabbit/Private/Gas/BusyAbilitySystemComponent.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - - -#include "Gas/BusyAbilitySystemComponent.h" - diff --git a/Source/BusyRabbit/Private/Level/Actor/BusyPawnBase.cpp b/Source/BusyRabbit/Private/Level/Actor/BusyPawnBase.cpp index f614b8d..0298b1d 100644 --- a/Source/BusyRabbit/Private/Level/Actor/BusyPawnBase.cpp +++ b/Source/BusyRabbit/Private/Level/Actor/BusyPawnBase.cpp @@ -7,21 +7,30 @@ ABusyPawnBase::ABusyPawnBase() { + RootScene = CreateDefaultSubobject(TEXT("RootScene")); SpineRoot = CreateDefaultSubobject(TEXT("SpineRoot")); SphereComponent = CreateDefaultSubobject(TEXT("SphereComponent")); SpineRenderComponent = CreateDefaultSubobject(TEXT("SpineRenderComponent")); SpineAnimationComponent = CreateDefaultSubobject(TEXT("SpineAnimationComponent")); + AbilitySystemComponent = CreateDefaultSubobject(TEXT("AbilitySystemComponent")); + MovementComponent = CreateDefaultSubobject(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; } + diff --git a/Source/BusyRabbit/Private/Level/Actor/Components/BusyAbilitySystemComponent.cpp b/Source/BusyRabbit/Private/Level/Actor/Components/BusyAbilitySystemComponent.cpp new file mode 100644 index 0000000..557f1be --- /dev/null +++ b/Source/BusyRabbit/Private/Level/Actor/Components/BusyAbilitySystemComponent.cpp @@ -0,0 +1,6 @@ +#include "Level/Actor/Components/BusyAbilitySystemComponent.h" + +FString UBusyAbilitySystemComponent::GetLuaFilePath_Implementation() const +{ + return LuaFilePath; +} diff --git a/Source/BusyRabbit/Private/Level/Actor/Components/BusyPawnMovement.cpp b/Source/BusyRabbit/Private/Level/Actor/Components/BusyPawnMovement.cpp index a8a90b3..6a935e1 100644 --- a/Source/BusyRabbit/Private/Level/Actor/Components/BusyPawnMovement.cpp +++ b/Source/BusyRabbit/Private/Level/Actor/Components/BusyPawnMovement.cpp @@ -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(Owner); if (!Owner || !Movable) return; @@ -61,8 +65,12 @@ void UBusyPawnMovement::TickComponent(float DeltaTime, ELevelTick TickType, } if (!NewLocation.Equals(CurrentLocation)) { - Owner->SetActorLocation(NewLocation, true); + Owner->SetActorLocation(NewLocation, true); + Owner->ForceNetUpdate(); } - Movable->Execute_UpdateMoveDirection(Owner, GetMoveDirection()); -} + + + // Movable->Execute_UpdateMoveDirection(Owner, GetMoveDirection()); +} +#pragma optimize("",on) diff --git a/Source/BusyRabbit/Private/Level/BusyLevelItem.cpp b/Source/BusyRabbit/Private/Level/BusyLevelItem.cpp index 3968292..350c38e 100644 --- a/Source/BusyRabbit/Private/Level/BusyLevelItem.cpp +++ b/Source/BusyRabbit/Private/Level/BusyLevelItem.cpp @@ -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(TEXT("PickBar")); LevelItemAttribute = CreateDefaultSubobject("LevelItemAttribute"); - AbilityComponent = CreateDefaultSubobject("RoleAbility"); InitSprite(); InitCapsule(); diff --git a/Source/BusyRabbit/Private/Level/LevelPlayerController.cpp b/Source/BusyRabbit/Private/Level/LevelPlayerController.cpp index 5eb92c3..1ea8c05 100644 --- a/Source/BusyRabbit/Private/Level/LevelPlayerController.cpp +++ b/Source/BusyRabbit/Private/Level/LevelPlayerController.cpp @@ -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); // 注册输入 - const auto Subsystem = ULocalPlayer::GetSubsystem(GetLocalPlayer()); - if (InputMapping && Subsystem) + if (!HasAuthority()) { - Subsystem->AddMappingContext(InputMapping, 0); + const auto Subsystem = ULocalPlayer::GetSubsystem(GetLocalPlayer()); + if (InputMapping && Subsystem) + { + Subsystem->AddMappingContext(InputMapping, 0); + } + + + if (UEnhancedInputComponent* EnhancedInput = CastChecked(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& Results) const { } -void ALevelPlayerController::GetControlledRole() const +ABusyPlayerRole* ALevelPlayerController::GetControlledRole() const { + if (const ALevelPlayerState* PS = GetPlayerState()) + { + 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); + } +} diff --git a/Source/BusyRabbit/Private/Level/LevelPlayerState.cpp b/Source/BusyRabbit/Private/Level/LevelPlayerState.cpp index 75117b3..009c3e1 100644 --- a/Source/BusyRabbit/Private/Level/LevelPlayerState.cpp +++ b/Source/BusyRabbit/Private/Level/LevelPlayerState.cpp @@ -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(GetPlayerController()); - if (!World || !PC) - { - UE_LOG(LogLevelPlayerState, Error, TEXT("ALevelPlayerController::BeginPlay() failed!")); - return; - } - - FVector2D SpawnLocation = GetSpawnLocation(); - for (auto PawnClass : RoleClasses) - { - FTransform SpawnTransform; - FActorSpawnParameters Params; - SpawnTransform.SetLocation(FVector(SpawnLocation, 50)); - if (auto *NewRole = Cast(World->SpawnActor(PawnClass, nullptr, Params))) - { - Roles.Add(NewRole); - } - } - - if (Roles.IsValidIndex(0)) - { - PC->SwitchControlledRole(Roles[0]); - } } +void ALevelPlayerState::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ALevelPlayerState, RoleRoster); + DOREPLIFETIME(ALevelPlayerState, ControlledRoleIndex); +} + +AActor* ALevelPlayerState::CreateRoleRoster(class APlayerController* PlayerController) +{ + 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( + 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& Roster) +{ + if (HasAuthority()) + { + RoleRoster = Roster; + } +} \ No newline at end of file diff --git a/Source/BusyRabbit/Private/Level/PaperTerrainMapActor.cpp b/Source/BusyRabbit/Private/Level/PaperTerrainMapActor.cpp deleted file mode 100644 index f938381..0000000 --- a/Source/BusyRabbit/Private/Level/PaperTerrainMapActor.cpp +++ /dev/null @@ -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(TEXT("RootComponent")); - - // 创建TileMap组件 - TileMapComponent = CreateDefaultSubobject(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(this); - NewTileMap->MapWidth = MapWidth; - NewTileMap->MapHeight = MapHeight; - NewTileMap->TileWidth = TileSize; - NewTileMap->TileHeight = TileSize; - - // 添加一个空图层 - UPaperTileLayer* NewLayer = NewObject(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(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(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(this, GeneratorClass.Get()); - UTerrainGeneratorBlueprintLibrary::SetupExampleTerrainConfig(TerrainGenerator); - } - else - { - UE_LOG(LogTemp, Warning, TEXT("APaperTerrainMapActor::APaperTerrainMapActor, GeneratorClass is nullptr")); - } -} - -void APaperTerrainMapActor::ApplyTerrainToTileMap(const TArray& 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); - } -} diff --git a/Source/BusyRabbit/Private/Role/BusyRole.cpp b/Source/BusyRabbit/Private/Role/BusyRole.cpp index 8dee33b..d5348fc 100644 --- a/Source/BusyRabbit/Private/Role/BusyRole.cpp +++ b/Source/BusyRabbit/Private/Role/BusyRole.cpp @@ -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; - // û + // ���û������� if (RoleAttribute) { RoleAttribute->InitHealth(Row->Health); RoleAttribute->InitHunger(Row->Hunger); @@ -79,7 +78,7 @@ void ABusyRole::SetRole(const FName& Name){ RoleAttribute->RegisterCustomAttribute(); } - // ü + // ���ü��� for (UClass* AbilityClass : Row->DefaultAbilities) { if (AbilityClass) { RoleAbility->GiveAbility(FGameplayAbilitySpec(AbilityClass)); diff --git a/Source/BusyRabbit/Public/BusyGamePlayLibrary.h b/Source/BusyRabbit/Public/BusyGamePlayLibrary.h index ccab6e8..b003a29 100644 --- a/Source/BusyRabbit/Public/BusyGamePlayLibrary.h +++ b/Source/BusyRabbit/Public/BusyGamePlayLibrary.h @@ -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); diff --git a/Source/BusyRabbit/Public/Gas/BusyAbilitySystemComponent.h b/Source/BusyRabbit/Public/Gas/BusyAbilitySystemComponent.h deleted file mode 100644 index 3eed078..0000000 --- a/Source/BusyRabbit/Public/Gas/BusyAbilitySystemComponent.h +++ /dev/null @@ -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() - -}; diff --git a/Source/BusyRabbit/Public/Level/Actor/BusyPawnBase.h b/Source/BusyRabbit/Public/Level/Actor/BusyPawnBase.h index d04d6fc..836f6be 100644 --- a/Source/BusyRabbit/Public/Level/Actor/BusyPawnBase.h +++ b/Source/BusyRabbit/Public/Level/Actor/BusyPawnBase.h @@ -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() @@ -22,7 +24,7 @@ public: virtual void UpdateMoveDirection_Implementation(const FVector2D& InDirection) override; virtual float GetSpeed_Implementation()const override; - + protected: UPROPERTY(EditDefaultsOnly) TObjectPtr RootScene; //场景根组件 @@ -44,7 +46,13 @@ protected: TObjectPtr SpineAnimationComponent; /*-------------------------------------------------------------------*/ - + /*-----------------------------GAS相关--------------------------------*/ + UPROPERTY(EditAnywhere, BlueprintReadOnly) + TObjectPtr AbilitySystemComponent; + + /*-------------------------------------------------------------------*/ + +public: /*-------------------------------移动组件------------------------------*/ UPROPERTY(EditAnywhere, BlueprintReadOnly) TObjectPtr MovementComponent; diff --git a/Source/BusyRabbit/Public/Level/Actor/Components/BusyAbilitySystemComponent.h b/Source/BusyRabbit/Public/Level/Actor/Components/BusyAbilitySystemComponent.h new file mode 100644 index 0000000..6d0cb43 --- /dev/null +++ b/Source/BusyRabbit/Public/Level/Actor/Components/BusyAbilitySystemComponent.h @@ -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; +}; diff --git a/Source/BusyRabbit/Public/Level/Actor/Components/BusyPawnMovement.h b/Source/BusyRabbit/Public/Level/Actor/Components/BusyPawnMovement.h index cfa8f1d..fb79272 100644 --- a/Source/BusyRabbit/Public/Level/Actor/Components/BusyPawnMovement.h +++ b/Source/BusyRabbit/Public/Level/Actor/Components/BusyPawnMovement.h @@ -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) diff --git a/Source/BusyRabbit/Public/Level/LevelPlayerController.h b/Source/BusyRabbit/Public/Level/LevelPlayerController.h index b1f4f75..2d59187 100644 --- a/Source/BusyRabbit/Public/Level/LevelPlayerController.h +++ b/Source/BusyRabbit/Public/Level/LevelPlayerController.h @@ -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& 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 TouchAction; + + UFUNCTION() + void OnTouch(const FInputActionValue& Value); }; diff --git a/Source/BusyRabbit/Public/Level/LevelPlayerState.h b/Source/BusyRabbit/Public/Level/LevelPlayerState.h index 835edb6..b6362b3 100644 --- a/Source/BusyRabbit/Public/Level/LevelPlayerState.h +++ b/Source/BusyRabbit/Public/Level/LevelPlayerState.h @@ -14,27 +14,47 @@ class ALevelPlayerState : public ALuaPlayerState GENERATED_BODY() public: virtual void BeginPlay() override; + + virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; +public: // 给蓝图初始化的接口 + UFUNCTION(BlueprintCallable) + AActor* CreateRoleRoster(class APlayerController* PlayerController); + - -public: +public: // 给蓝图的Get接口 UFUNCTION(BlueprintCallable) ABusyPlayerRole* GetControlledRole() const; +public: // 给蓝图的Set接口 + UFUNCTION(BlueprintCallable) + void SetRoleRoster(const TArray& Roster); + + +public: + + +public: + + + protected: virtual FVector2D GetSpawnLocation()const; public: - UPROPERTY(EditAnywhere) + UPROPERTY(EditAnywhere, BlueprintReadOnly) TArray> RoleClasses; protected: - UPROPERTY() + UPROPERTY(Replicated, BlueprintReadOnly) int ControlledRoleIndex = -1; - UPROPERTY() - TArray Roles; + UPROPERTY(Replicated, BlueprintReadOnly) + TArray RoleRoster; }; + + + diff --git a/Source/BusyRabbit/Public/Level/PaperTerrainMapActor.h b/Source/BusyRabbit/Public/Level/PaperTerrainMapActor.h deleted file mode 100644 index b855c79..0000000 --- a/Source/BusyRabbit/Public/Level/PaperTerrainMapActor.h +++ /dev/null @@ -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 GeneratorClass; - - // 地形生成器实例 - UPROPERTY() - TObjectPtr TerrainGenerator; - - // 生成的地形数据 - TArray 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 *TerrainTileSetConfigs; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings", DisplayName="瓦片设置") - TObjectPtr TileSetDataAsset; - - -protected: - // Paper2D瓦片地图组件 - UPROPERTY(EditAnywhere, BlueprintReadWrite) - TObjectPtr 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& TerrainData)const; - - void DrawTile(const int32 X, const int32 Y, FGameplayTag TerrainTags[4])const; - -}; diff --git a/Tools/README.md b/Tools/README.md new file mode 100644 index 0000000..cfd8df0 --- /dev/null +++ b/Tools/README.md @@ -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 + +---@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 +- 是否有语法错误或缺失的分号 + +## 贡献 + +欢迎提交问题和改进建议! diff --git a/Tools/ue_header_parser.py b/Tools/ue_header_parser.py new file mode 100644 index 0000000..d51c74d --- /dev/null +++ b/Tools/ue_header_parser.py @@ -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() diff --git a/Tools/使用示例.md b/Tools/使用示例.md new file mode 100644 index 0000000..657a5a1 --- /dev/null +++ b/Tools/使用示例.md @@ -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 +---@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`等模板类型的解析 +3. **委托支持**: 自动生成委托类型的Call函数注解 +4. **错误处理**: 工具会跳过无法解析的文件并继续处理其他文件 + +## 故障排除 + +如果遇到问题,请检查: +- 头文件语法是否正确 +- UE宏格式是否符合标准 +- 输出目录权限是否足够 +- Python版本是否为3.6+