From 9f1604f57c72698418edbfc2c1747248c5f9b03d Mon Sep 17 00:00:00 2001 From: "j.foucher" Date: Fri, 10 Apr 2026 20:17:57 +0200 Subject: [PATCH] Add Phase 3: object spawn system with catalogue UI Spawn System: - SpawnableComponent: marker component for Blueprint actors (name, category, thumbnail) - SpawnCatalog DataAsset: lists all spawnable Blueprints with Factory for easy creation - SpawnManager: loads catalog, spawns actors in front of camera, ensures Movable mobility - Reads metadata from Blueprint component templates (no temp spawn needed) Catalogue UI: - Left panel with scrollable list grouped by category - Thumbnail cards (80x80) with name below, click to spawn - "Capture All Thumbnails" button on DataAsset: auto-generates from UE thumbnails Undo/Redo improvements: - SpawnAction: undo hides spawned actor, redo shows it - Delete confirmation dialog (Yes/No) before soft-delete - Undo/redo auto-deselects hidden actors and refreshes gizmo - Proper cleanup of hidden actors when actions pruned from stack Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/memory/project_ps_editor.md | 34 +++- .claude/memory/reference_build.md | 4 + Unreal/Content/test.umap | Bin 48048 -> 48048 bytes .../PS_Editor/Content/Cubeezrzea.uasset | Bin 0 -> 29846 bytes .../Content/NewPS_Editor_SpawnCatalog.uasset | Bin 0 -> 1998 bytes .../Content/PS_Editor_PlayerController.uasset | Bin 20011 -> 20728 bytes .../Plugins/PS_Editor/Content/Sphere.uasset | Bin 0 -> 30589 bytes .../Content/T_Cubeezrzea_Thumb.uasset | Bin 0 -> 62808 bytes .../PS_Editor/GameMode/PS_Editor_Pawn.cpp | 17 +- .../GameMode/PS_Editor_PlayerController.cpp | 38 +++- .../GameMode/PS_Editor_PlayerController.h | 12 ++ .../Source/PS_Editor/PS_Editor.Build.cs | 7 + .../Selection/PS_Editor_UndoManager.cpp | 28 ++- .../Selection/PS_Editor_UndoManager.h | 46 ++++- .../Spawn/PS_Editor_SpawnCatalog.cpp | 113 +++++++++++ .../PS_Editor/Spawn/PS_Editor_SpawnCatalog.h | 45 +++++ .../Spawn/PS_Editor_SpawnCatalogFactory.cpp | 27 +++ .../Spawn/PS_Editor_SpawnCatalogFactory.h | 22 +++ .../Spawn/PS_Editor_SpawnManager.cpp | 170 ++++++++++++++++ .../PS_Editor/Spawn/PS_Editor_SpawnManager.h | 69 +++++++ .../Spawn/PS_Editor_SpawnableComponent.cpp | 6 + .../Spawn/PS_Editor_SpawnableComponent.h | 30 +++ ...Editor_SpawnableComponentCustomization.cpp | 139 +++++++++++++ ...S_Editor_SpawnableComponentCustomization.h | 26 +++ .../Source/PS_Editor/UI/PS_Editor_HUD.cpp | 1 + .../UI/Widgets/PS_Editor_MainWidget.cpp | 182 ++++++++++++++++++ .../UI/Widgets/PS_Editor_MainWidget.h | 13 +- 27 files changed, 1009 insertions(+), 20 deletions(-) create mode 100644 Unreal/Plugins/PS_Editor/Content/Cubeezrzea.uasset create mode 100644 Unreal/Plugins/PS_Editor/Content/NewPS_Editor_SpawnCatalog.uasset create mode 100644 Unreal/Plugins/PS_Editor/Content/Sphere.uasset create mode 100644 Unreal/Plugins/PS_Editor/Content/T_Cubeezrzea_Thumb.uasset create mode 100644 Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalog.cpp create mode 100644 Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalog.h create mode 100644 Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalogFactory.cpp create mode 100644 Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalogFactory.h create mode 100644 Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.cpp create mode 100644 Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.h create mode 100644 Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnableComponent.cpp create mode 100644 Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnableComponent.h create mode 100644 Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnableComponentCustomization.cpp create mode 100644 Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnableComponentCustomization.h diff --git a/.claude/memory/project_ps_editor.md b/.claude/memory/project_ps_editor.md index 2de4f48..eddcbbd 100644 --- a/.claude/memory/project_ps_editor.md +++ b/.claude/memory/project_ps_editor.md @@ -8,11 +8,35 @@ PS_Editor is a runtime editor plugin for Unreal Engine 5.5 that will be integrat **Why:** Enable runtime scene editing in packaged builds - place objects, configure properties, define scenarios, manipulate splines, modify lighting, and save/load scenes as JSON. -**How to apply:** All development is incremental, phase by phase. Phase 1 = GameMode + Camera (completed). Future phases: object spawning/transform, JSON serialization, splines, lighting, AI characters. - Key decisions: -- UI: UMG driven by C++ (not Blueprints) -- Gizmos: InteractiveToolsFramework (UE5 built-in) -- Serialization: JSON via Json/JsonUtilities modules +- UI: UMG driven by C++ (Slate) +- Gizmos: Custom actor with BasicShapes + unlit material (InteractiveToolsFramework is editor-only) +- Serialization: JSON via Json/JsonUtilities modules (future) - Naming: PS_Editor_ prefix on all files/classes - Plugin path: Unreal/Plugins/PS_Editor/ +- AZERTY keyboard layout: arrows + A/Q for movement, E/R/T for modes + +## Completed Phases + +### Phase 1 - GameMode + Camera +- PS_Editor_GameMode, PS_Editor_Pawn, PS_Editor_PlayerController, PS_Editor_HUD +- Camera: Fly (RMB), Pan (LMB drag), Orbit (Alt+LMB / MMB) +- Movement: arrows + A (up) / Q (down) + +### Phase 2 - Selection + Transform + Undo +- SelectionManager: raycast click-to-select, Ctrl+click multi-select, Movable actors only +- Gizmo: custom actor with colored arrows (R/G/B), cones, SDPG_Foreground, constant screen size +- Translate/Rotate/Scale via gizmo drag, hotkeys E/R/T +- Snap to ground (End key), soft-delete (Delete key) +- Undo/Redo system: custom UndoManager, Ctrl+Z / Ctrl+Y, 50 action stack +- Properties panel: editable Location/Rotation/Scale fields +- Toolbar: mode buttons + snap ground +- Material: M_PS_Editor_Gizmo (Unlit, Vector Parameter "Color" → Emissive, Translucent, Disable Depth Test) + +## Next: Phase 3 - Object Spawning +- Catalog/list of objects to place +- Drag & drop or click to spawn +- Duplication of existing objects + +## Next: Phase 4 - JSON Serialization +- Save/load scenes as JSON diff --git a/.claude/memory/reference_build.md b/.claude/memory/reference_build.md index ccf3e45..f522f1d 100644 --- a/.claude/memory/reference_build.md +++ b/.claude/memory/reference_build.md @@ -12,3 +12,7 @@ Build command (Editor target, Development): ``` **How to apply:** Use this command to compile after code changes. Timeout should be set to 600000ms (10 min) for safety. The target name is `PS_ProserveEditorEditor` (Editor suffix) for editor builds. + +**When to use CLI vs Live Coding:** +- Live Coding (Ctrl+Alt+F11): .cpp only changes (no new UCLASS, USTRUCT, UENUM, UPROPERTY in headers) +- Full CLI build (UE must be closed): new files, header changes with new reflected types, Build.cs changes diff --git a/Unreal/Content/test.umap b/Unreal/Content/test.umap index e87f29c383b0624bf8d84c97aaf6891dcaf6176c..33c7b5b8fc801652d495c8536ce3b1def3b09276 100644 GIT binary patch delta 316 zcmdn+ooNFQtzi_%n!a?!|DzWi8u~@%rLr;p+q{SIQ>?UUl|@8pN^-H6o1>*qW`#+l zduE!iQ@*KxX4K@9@v8Muc{493sJw@BngvAOH7_M3&-r}~1_p-7iRCJrb4!iH zCFlH!dmgi4qQjRR8y_|Bc_|@*P%XJ$UNFs$DHe{7ZssXYnIR>)fsU?F@ysBY zT1O9aM@MJV()0>1<4^;rv{z;@OupPB&C$^*)i^LR+dL}U(Fv$xa!EovyMdmush+9P zW~M}A5s}#w3ev={m#DUKXgsmo)M?4cz`!s$v0P^uCZ`S|- diff --git a/Unreal/Plugins/PS_Editor/Content/Cubeezrzea.uasset b/Unreal/Plugins/PS_Editor/Content/Cubeezrzea.uasset new file mode 100644 index 0000000000000000000000000000000000000000..5b3fc780d2db6b69101bdda25cc5523fc2904e53 GIT binary patch literal 29846 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 VEC=YIj`uaY^Ii;=ueUMC{{@dPl#c)a literal 0 HcmV?d00001 diff --git a/Unreal/Plugins/PS_Editor/Content/PS_Editor_PlayerController.uasset b/Unreal/Plugins/PS_Editor/Content/PS_Editor_PlayerController.uasset index 7f4dc85d97b82057f10d7365ef472ca63ad0a1d5..8459e9fa09cf93cb5f6f31174379755518b4935c 100644 GIT binary patch delta 4617 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 H?GOA3OOev_ diff --git a/Unreal/Plugins/PS_Editor/Content/Sphere.uasset b/Unreal/Plugins/PS_Editor/Content/Sphere.uasset new file mode 100644 index 0000000000000000000000000000000000000000..904db766e19c0fa0f7b2902be4deac770475f9ef GIT binary patch literal 30589 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 literal 0 HcmV?d00001 diff --git a/Unreal/Plugins/PS_Editor/Content/T_Cubeezrzea_Thumb.uasset b/Unreal/Plugins/PS_Editor/Content/T_Cubeezrzea_Thumb.uasset new file mode 100644 index 0000000000000000000000000000000000000000..5530dd670d8b79846cc545e66a31099623fca53e GIT binary patch literal 62808 zcma%i1z1&Gv;QHbL%JId-6cqOch><9orjWA1nKVXPU!~eZt0X(QbANo{v6-e_w&2= zyZ3*GXS4U3nKf%>)~w%bc+Tk!V#@jP@bF*;2LPbJ0svQ!cPK$`8y@?N3^IiuJm;{o zkJedeupfD^7yNB2#Z?bZ)Dc)TKEemw$e5C8zG$45SCX>JY) z4sjkq0d5{nK0YoPK5-siDFJRyDJgCN3Fs8SFJ-8ko`?Xzhh9*k zhoc*_%^FuN;JpVMx_9~3W3S+?F9GFC=o8>(mT(fGYeVfots*@BzyspQ?O~rjj+mLX zrFoe(zz$%L8<>@sjrW%v063Tu`5)wulYWufNrMmcVe!$27>vJozkfew`vICB#z#iN zA2Qs>F}tP(#MZ@~UD6o>)>bvQ16#NQe#otSmt z?O^6nnT1jU|7YNwR&DPmT#u?%WatAmZsqIrf zN~E>GFWfyKU`=Nahz0n+>&1LzUd8)wdVd@7D#T!gCgty;AbMmBv^1bm0wmRLY@qur zZwYX3yS0KQOWw(ykLMrnY4O*Tp-Fk<{=+MN&!<54$WsD&gCW1dl6SIlHdT5|(W8$0 zS~iboa9)!$w0SP z+15oC?4${Hhibb4zEjiZL+7i29RHQyzzZY=Xgn&;j|K1*ZV(*_I`D9Ooa+Y2VLDxZ zEGy_()4|pP>;~Xqn`ru20V554tqRz08LvTupVF}o~iryNFVSjN_7Rg34iS% zKu55y1?q*SJH*4{Uzz0qWZmIEvj2AAzl1MpxW@zCq2KQPDh7ZY!>f4G|H}RMtpU*5 z5D!6@(^8hzG*xkawCf0Z%+4g`t6OM3wKOGVOtqz@q?NS90S+g+lhBlDY08*N$ZCiS z01(TMi2nb$wULq#4vkmR0p#WeaQAu<4=r+Kusi72>DmiuOoZ;t-x&nB$%f%UPvPI= zfAq3hioiIJ{QrQAJp!#6zdZZvFmOo$e%_1CKI;A=roZoZk5Iz=_cPJo_djpH^==>a z;Qx<$?Ei}%%b&*ok9vPYJK#_Mem?s1+Z6Oj{`L*ZgTf$`pb+^#(Qlhj`J<_SZ2i`U zRv(n0Nr%#Z)nWaM4R~yL0Exe!YX8qQ{#N;Q`>(eCltHTiO85W8C$_)XzuHU( zG~h>i|HpcNasKuD&-MSe^t8efdUE{RHq_4lP+-vQ_}y*(>L_e~pS8zYgW@YR z7I8~UC`ju8pw+A;ZmJ6bx%?^6|2M&ZYBW#>4UGZNlPB&8vULEN+d9}jHYVtf%c^Pr z+U-9u{vY^5g)S&*Dh0Lzc{sQOphGEJN3atVq1=A0%Eim~;_(9ff_qyBcQE9amX^#v zlm2J~P|%dp)8hP95s%$Y+`+{LBxwV3asoR5peI=drqNyP( z@vrO_P=X#!C_yJc3j#{eXXy2te(5m$ zJ%a86lzxNcU!%v!phy9w-=G4u@MlWseo6nk^%y80s{9+#{+tAD@lg8H9q9O19;4L_ zg??WEK!E6P{QN%~nD}5Iy7k>i^a$Hj6l&milYt&&DE*lnXi-B6>dBwp{L%sb68)<@ z|IESv#mKMBL#_Tc1O*i+{b>mLcKw_FnedzO+jS`RLg~*O{IA7$yHoK|RiRor)Lwbb za6?f2cQb{)DE+2iJg9rn7dt3H5A1LH6(8*1Bj}4El>Q8w<}cQN&K&*U!r%V?J$$qW zp+Ns?babm4hFs|Qq;%qC?!6|T`lnco%$V>m4e6O0h-e6J|GoG3KK?bD`mH#CO=w^i z>X7o|1wQ!8BGmi;Y~e5YZ@quB*y3Sfezew=2L}fSJ$`>}`|kt+4#5A(pxl!&fLs4g z$Xl*2RgZt`QAHsF4oCtVS%`^=9|#EvAH+02q7X$Z0FYp5k>r%pdG_8UWIWgN-HLORiOhKa0<5 zK~2phb4rPbD@COM2U7>L#)HpV$qcWtR`21&w$|(pwvRL<>m`h)111?P>!ld;!Pa0&Hn;>G*=A z?Xj)w8zS;;ivWn#_<0iOk_hm{?nrQIl!4mSHmOLsfhj!$MM{=wNL+LYIgs5CIrcuDI!|S8UC#R5S#;DNyB!`xn*LB=>$LG=>-c(POU6~G}K4JoZcN4>q=+PdS> zHS)b5auD(ykLmodr@N1C-7RFD-R0$cz(BvGkzHul85cZQs{5Y#VCJSTpVaT<_4=!= zjqB5F(Fx@x)AWtQQ~_RnOl$CV+i-Rgj+J#o^Et(}pbekWQWW>Zn`pddio{(L9|Mz? z%hPdh%dG}|cywm{kZ8GqAR3uM0e92_VA#ScdPFp^I1g4qt+$cMybuvwi>lC3i-e+9 zETIL^mMTPA4nUWv6w@juMxZNe3^VIUz(Y!It9Dyy_eX7C;Byl&M^gah;~F4oRcfsf zDbTF_Au5*O05%#~9i zxfQ_#V6NpStfxVx8ZyZrxAqxBb|;yli@lPMT8_ z4$_k)GUWFD67D=9E9$0P({&NyXTG%JeQ4-)(De2>U1^R>pTP6Dt8#IA*_DSG=ZgmB zx8qkNEo2RXLMAUDCRaDymo*Jjo0-Fh4D<~KO)s@P@bU34=QEgHZJ(otVu++XIe*9g z>TZMVx_5HvFk<7-kSlrA#}fRta~E~+GbU$xig|?l05q*xe}-7ZrZCW ztLnJhnyO28Z;v46@dz!8A7&|vI(%ODk} zQnqG1x-x4(bgPk4lPKM zkyw-q!$Jy#F;? zxKLBv><-nTbWURVif&HY`@`>>k=Qas(noZ;t&{{??*84bPTD!Rj5C=2jO<&S@ek)K zsj@%6pRfB*XJ7wBH?`^K)W|akdr1BA`Y<202)W5AR=gwfL`{6KIhg@}!$HbM8b%CP zVWCdXIFNe(1+e$b(%3$2;iXIf5mFWOJ z5KD!&WOzg)5QXK5JaQBQuT&PNCS9Yln0OcxWxxms*OG`1h!j>f_r5?!u@a#Xsa>Xx ztvXOA6$w~HX@^|aoMb?Kq?ZiK@Du^NcCA1SCbgizVqQkQoZdl9hu9&^T%S@q+D5)I ztj55lOkFxoys1|kN1caU1`n?Z3x%o-K^-H~j4_a>k&Pe&H$WNaS{VQzM_FtZUauk^ z39Fnxa1A@d)-G8I^9df1c}PE`Z)75}^Zr?8bZD%|6Y%QTCp8pk z2iLOOd*1LUM6KUp#S=xIAO(yhK(DV(nQxg}kPr znLup9S(tDkbVb@1J;1X zs;7(oLSJn}>l*+{;4(UbjW|qxK0>xQ{CFKo7%L?_0;9epqKmw`IQHO>zBDd847_wS zsX7YdOk;&eBr7En5~V@WQ-Hi8P(6ZDg9lY4SS=-mHQA{{j;^9OARr0f0f3H@m(Rw> z!CQqmS!lk5pA3Vlu^?Td@re>vv3^z;L0dbjj0!_sr-Nll%{7HD5SJQNE=i{mag0}a zgO7)AP&`Fvo(0}fr#H$Vk9rv?TuvH5%vLkgK@YDm!xXTK&#&ZQP8@+*)vU6?8ZlU; zr6z7nh(Ve}ZxD!2`G#=+wHyL$6>I?N(^;K@2wZbz+_}O$6fyYTqe72VG4LvDfiy1a zB8eO}?|98iJ97heaBy?ESWrP@(dX^CNjO~3>58N`7OZ$tum&?_bFgLQ_JU+;YGMYS zgDJd@P10s;`1kR*2JJj^%djQQ*Rs3aXR7``WmBGiA5In)S+SkFqd-E(%^~#kyZXGv zFVMDnIBwS^n|HOsuuG9v@#E==bWJz0>;3#Ar-O(i$5mJ#Jz` z`sC_}+|$5=0+U2})_M?(Ne~rsm?`$P5g3+*LYrJn$7)yo@LgRg5t4LVeg5#XAe~-c z;b>{=;WKUC5SpAi)`*OJJ|m9QNMpS;t=lU@$9L5oncPGz%`0`8qO?Tvv;gV?sj$cQ z@UjG0cmNCuQF=OdJYk?+p!!A!48L*={01%n_K9k)-%YXe5o4mQk>LHOFa1fNk-^gU zku4XsU(?N&a1(90uU%%Q%eYcxG)wv}mzlOTj0M)5LP%c4mSY>MTKgYUO|2Dg{TS4l zZ7cnuN^(JJE#$UmR8z#$Ht}_ZqD9ha=(tkkFz|sb3v+UM;-YS%%(aqqN}*z(hov!0E*bvhdJYfOI;5ssAnC4n=?}wlIiimcfkds9$is%THhu&oFy54Z+dm0tFovIl-G$>B^|*KwbA4$p8fmgEAu7qB*cEOp1Ed}SJ!)A(v}=zR)syiR zb~3N~;Sa(>4XtA~ocrY_irVpB-NJm;byhzc^7_q_&u{4UcDQas+V-9ZCTpZ6_p4@B zzv6GJ6P|qe5$7rG`*Dnm9zyejT29)mgPuvMLHd#%tt#KS(Ce!AnTF3pwR0o)&rKF7 za#(I33WLdMb{EMzZbKWo$EedsMiwpYS2b1lKGNchM>ijqUIXj7wk#MKQx8ffRU9GF z#y84>mcG2GLh;oj>hAPKeJ31bl6HJe#*m7oI~J-d5n7Kl8(Y^m4QQ%`y%oH&Gqw6U6szK z1%2cC5w6o=VQ^RK%9MR#hK#ev_;UJrwX;bk&!6gs|eP zlSGNO=+vK|x9nT0@4Jk~gNcQ_Wx8|P^q9aKoIiO!FeH#`QVq$!#~VNrUvG>4;POFjR~VhF;BzqR%I0rdQF9F3|&HkFY-ew z!}A*4yqvY9^SfUI$s4^K45NY;_zzR6W8+PR)&uEQ@aXJHDzURPXjoEijVP!FoyM%x zl@#vLP^mCWZ6R;dz|Sm%OAT8ueT){U2nvfc$0Jly4sl$UqG1V0C*1THbU0Pf75H

JA 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 a!(omdHXcn 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 if (!SM || !SM->IsAnythingSelected()) return; + // Confirmation dialog + const int32 Count = SM->GetSelectedActors().Num(); + const FText Message = FText::Format( + NSLOCTEXT("PS_Editor", "DeleteConfirm", "Delete {0} selected object(s)?"), + FText::AsNumber(Count)); + + EAppReturnType::Type Result = FMessageDialog::Open(EAppMsgType::YesNo, Message); + if (Result != EAppReturnType::Yes) + { + return; + } + // Soft-delete: hide actors instead of destroying (enables undo) TSharedPtr Action = MakeShared(); for (const TWeakObjectPtr& Weak : SM->GetSelectedActors()) @@ -584,14 +597,14 @@ void APS_Editor_Pawn::HandleDelete(const FInputActionValue& Value) } SM->ClearSelection(); - Action->Execute(); // Hide the actors + Action->Execute(); if (UPS_Editor_UndoManager* UM = EditorPC->GetUndoManager()) { UM->RecordAction(Action); } - UE_LOG(LogTemp, Log, TEXT("PS_Editor: Soft-deleted %d actor(s)"), Action->Actors.Num()); + UE_LOG(LogTemp, Log, TEXT("PS_Editor: Soft-deleted %d actor(s)"), Count); } void APS_Editor_Pawn::HandleUndo(const FInputActionValue& Value) diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.cpp index 3beed29..d3196e6 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.cpp @@ -1,6 +1,7 @@ #include "PS_Editor_PlayerController.h" #include "PS_Editor_SelectionManager.h" #include "PS_Editor_UndoManager.h" +#include "PS_Editor_SpawnManager.h" #include "PS_Editor_Gizmo.h" #include "PS_Editor_Pawn.h" @@ -22,14 +23,43 @@ void APS_Editor_PlayerController::BeginPlay() UndoManager = NewObject(this); - // After undo/redo, refresh the gizmo position + // Load spawn catalog and create spawn manager + SpawnManager = NewObject(this); + UPS_Editor_SpawnCatalog* LoadedCatalog = SpawnCatalogAsset.LoadSynchronous(); + SpawnManager->Initialize(this, LoadedCatalog); + + // After undo/redo, deselect hidden actors and refresh gizmo UndoManager->OnUndoRedo.AddWeakLambda(this, [this]() { - if (SelectionManager && SelectionManager->IsAnythingSelected()) + if (SelectionManager) { - if (APS_Editor_Gizmo* Gizmo = SelectionManager->GetGizmo()) + // Deselect any actors that are now hidden (undone spawn or redone delete) + TArray> ToDeselect; + for (const TWeakObjectPtr& Weak : SelectionManager->GetSelectedActors()) { - Gizmo->SetActorLocation(SelectionManager->GetSelectionPivot()); + if (AActor* Actor = Weak.Get()) + { + if (Actor->IsHidden()) + { + ToDeselect.Add(Actor); + } + } + } + for (const TWeakObjectPtr& Weak : ToDeselect) + { + if (AActor* Actor = Weak.Get()) + { + SelectionManager->DeselectActor(Actor); + } + } + + // Refresh gizmo position + if (SelectionManager->IsAnythingSelected()) + { + if (APS_Editor_Gizmo* Gizmo = SelectionManager->GetGizmo()) + { + Gizmo->SetActorLocation(SelectionManager->GetSelectionPivot()); + } } } }); diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.h index 4c45df5..37385e7 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.h @@ -6,6 +6,8 @@ class UPS_Editor_SelectionManager; class UPS_Editor_UndoManager; +class UPS_Editor_SpawnManager; +class UPS_Editor_SpawnCatalog; /** * Player controller for PS_Editor runtime editing. @@ -25,6 +27,13 @@ public: UFUNCTION(BlueprintCallable, Category = "PS Editor") UPS_Editor_UndoManager* GetUndoManager() const { return UndoManager; } + UFUNCTION(BlueprintCallable, Category = "PS Editor") + UPS_Editor_SpawnManager* GetSpawnManager() const { return SpawnManager; } + + /** The spawn catalog DataAsset to load. Set this in the Blueprint or World Settings. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS Editor") + TSoftObjectPtr SpawnCatalogAsset; + protected: virtual void BeginPlay() override; virtual void OnPossess(APawn* InPawn) override; @@ -36,5 +45,8 @@ private: UPROPERTY() TObjectPtr UndoManager; + UPROPERTY() + TObjectPtr SpawnManager; + void HandleEditorClick(FVector2D ScreenPos, bool bAdditive); }; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/PS_Editor.Build.cs b/Unreal/Plugins/PS_Editor/Source/PS_Editor/PS_Editor.Build.cs index 9af7921..4b34e74 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/PS_Editor.Build.cs +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/PS_Editor.Build.cs @@ -15,6 +15,7 @@ public class PS_Editor : ModuleRules PublicIncludePaths.Add(Path.Combine(SourceDir, "UI", "Widgets")); PublicIncludePaths.Add(Path.Combine(SourceDir, "Selection")); PublicIncludePaths.Add(Path.Combine(SourceDir, "Gizmo")); + PublicIncludePaths.Add(Path.Combine(SourceDir, "Spawn")); PublicDependencyModuleNames.AddRange(new string[] { @@ -31,5 +32,11 @@ public class PS_Editor : ModuleRules "Slate", "SlateCore" }); + + // Editor-only: Factory for SpawnCatalog DataAsset creation + if (Target.bBuildEditor) + { + PrivateDependencyModuleNames.Add("UnrealEd"); + } } } diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_UndoManager.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_UndoManager.cpp index 87de4a6..6135a02 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_UndoManager.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_UndoManager.cpp @@ -89,16 +89,30 @@ void UPS_Editor_UndoManager::PruneHistory() void UPS_Editor_UndoManager::CleanupDeleteAction(TSharedPtr Action) { - // If a delete action is being pruned from history, truly destroy the hidden actors - if (!Action.IsValid() || Action->ActionType != EPS_Editor_ActionType::Delete) return; - FPS_Editor_DeleteAction* DeleteAction = static_cast(Action.Get()); - for (const TWeakObjectPtr& Weak : DeleteAction->Actors) + if (!Action.IsValid()) return; + + // Truly destroy hidden actors when their action is pruned from history + TArray>* ActorsToClean = nullptr; + + if (Action->ActionType == EPS_Editor_ActionType::Delete) { - if (AActor* Actor = Weak.Get()) + ActorsToClean = &static_cast(Action.Get())->Actors; + } + else if (Action->ActionType == EPS_Editor_ActionType::Spawn) + { + ActorsToClean = &static_cast(Action.Get())->Actors; + } + + if (ActorsToClean) + { + for (const TWeakObjectPtr& Weak : *ActorsToClean) { - if (Actor->IsHidden()) + if (AActor* Actor = Weak.Get()) { - Actor->Destroy(); + if (Actor->IsHidden()) + { + Actor->Destroy(); + } } } } diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_UndoManager.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_UndoManager.h index e6c42be..03b0848 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_UndoManager.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_UndoManager.h @@ -7,7 +7,8 @@ enum class EPS_Editor_ActionType : uint8 { Transform, - Delete + Delete, + Spawn }; /** @@ -110,6 +111,49 @@ struct FPS_Editor_DeleteAction : public FPS_Editor_Action } }; +/** + * Records a spawn action. Undo hides the spawned actor, redo shows it again. + */ +struct FPS_Editor_SpawnAction : public FPS_Editor_Action +{ + FPS_Editor_SpawnAction() : FPS_Editor_Action(EPS_Editor_ActionType::Spawn) {} + + TArray> Actors; + + virtual void Execute() override + { + // Redo: show the spawned actors + for (const TWeakObjectPtr& Weak : Actors) + { + if (AActor* Actor = Weak.Get()) + { + Actor->SetActorHiddenInGame(false); + Actor->SetActorEnableCollision(true); + Actor->SetActorTickEnabled(true); + } + } + } + + virtual void Undo() override + { + // Undo spawn: hide the actors + for (const TWeakObjectPtr& Weak : Actors) + { + if (AActor* Actor = Weak.Get()) + { + Actor->SetActorHiddenInGame(true); + Actor->SetActorEnableCollision(false); + Actor->SetActorTickEnabled(false); + } + } + } + + virtual FString GetDescription() const override + { + return FString::Printf(TEXT("Spawn %d actor(s)"), Actors.Num()); + } +}; + /** * Runtime undo/redo manager for PS_Editor. * Maintains a linear action stack with a cursor. diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalog.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalog.cpp new file mode 100644 index 0000000..a4dac6a --- /dev/null +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalog.cpp @@ -0,0 +1,113 @@ +#include "PS_Editor_SpawnCatalog.h" + +#if WITH_EDITOR +#include "PS_Editor_SpawnableComponent.h" +#include "Engine/Blueprint.h" +#include "Engine/Texture2D.h" +#include "ThumbnailRendering/ThumbnailManager.h" +#include "ObjectTools.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "UObject/SavePackage.h" +#include "Misc/PackageName.h" + +void UPS_Editor_SpawnCatalog::CaptureAllThumbnails() +{ + int32 Captured = 0; + + for (FPS_Editor_SpawnEntry& Entry : Entries) + { + UClass* LoadedClass = Entry.ActorClass.LoadSynchronous(); + if (!LoadedClass) + { + UE_LOG(LogTemp, Warning, TEXT("PS_Editor: Failed to load class for catalog entry")); + continue; + } + + // Find the SpawnableComponent template + UPS_Editor_SpawnableComponent* SpawnComp = nullptr; + TArray ClassSubObjects; + GetObjectsWithOuter(LoadedClass, ClassSubObjects, true); + for (UObject* Sub : ClassSubObjects) + { + SpawnComp = Cast(Sub); + if (SpawnComp) break; + } + + if (!SpawnComp) + { + UE_LOG(LogTemp, Warning, TEXT("PS_Editor: No SpawnableComponent on %s, skipping"), *LoadedClass->GetName()); + continue; + } + + // Skip if already has a custom thumbnail + if (SpawnComp->Thumbnail) + { + UE_LOG(LogTemp, Log, TEXT("PS_Editor: %s already has a thumbnail, skipping"), *LoadedClass->GetName()); + continue; + } + + // Find the Blueprint + UBlueprint* Blueprint = Cast(LoadedClass->ClassGeneratedBy); + if (!Blueprint) + { + UE_LOG(LogTemp, Warning, TEXT("PS_Editor: %s is not a Blueprint, skipping"), *LoadedClass->GetName()); + continue; + } + + // Generate thumbnail + FObjectThumbnail* ObjThumb = ThumbnailTools::GenerateThumbnailForObjectToSaveToDisk(Blueprint); + if (!ObjThumb || ObjThumb->GetImageWidth() <= 0 || ObjThumb->GetImageHeight() <= 0) + { + UE_LOG(LogTemp, Warning, TEXT("PS_Editor: Failed to generate thumbnail for %s"), *Blueprint->GetName()); + continue; + } + + const int32 Width = ObjThumb->GetImageWidth(); + const int32 Height = ObjThumb->GetImageHeight(); + const TArray& PixelData = ObjThumb->GetUncompressedImageData(); + + if (PixelData.Num() != Width * Height * 4) + { + continue; + } + + // Save texture next to the Blueprint + FString BlueprintPath = Blueprint->GetOutermost()->GetName(); + FString BasePath = FPaths::GetPath(BlueprintPath); + FString AssetName = FString::Printf(TEXT("T_%s_Thumb"), *Blueprint->GetName()); + FString FullPath = FString::Printf(TEXT("%s/%s"), *BasePath, *AssetName); + + UPackage* Package = CreatePackage(*FullPath); + if (!Package) continue; + + UTexture2D* Texture = NewObject(Package, *AssetName, RF_Public | RF_Standalone); + Texture->Source.Init(Width, Height, 1, 1, TSF_BGRA8); + + uint8* MipData = Texture->Source.LockMip(0); + FMemory::Memcpy(MipData, PixelData.GetData(), PixelData.Num()); + Texture->Source.UnlockMip(0); + + Texture->SRGB = true; + Texture->CompressionSettings = TC_Default; + Texture->MipGenSettings = TMGS_NoMipmaps; + Texture->PostEditChange(); + Texture->UpdateResource(); + + FString PackageFilename = FPackageName::LongPackageNameToFilename(FullPath, FPackageName::GetAssetPackageExtension()); + FSavePackageArgs SaveArgs; + SaveArgs.TopLevelFlags = RF_Public | RF_Standalone; + UPackage::SavePackage(Package, Texture, *PackageFilename, SaveArgs); + FAssetRegistryModule::AssetCreated(Texture); + + // Assign to the component and mark Blueprint dirty + SpawnComp->Thumbnail = Texture; + Blueprint->MarkPackageDirty(); + + Captured++; + UE_LOG(LogTemp, Log, TEXT("PS_Editor: Captured thumbnail for %s -> %s"), *Blueprint->GetName(), *FullPath); + } + + UE_LOG(LogTemp, Log, TEXT("PS_Editor: Captured %d thumbnails"), Captured); +} + +#endif diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalog.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalog.h new file mode 100644 index 0000000..c546003 --- /dev/null +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalog.h @@ -0,0 +1,45 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Engine/DataAsset.h" +#include "Engine/AssetManager.h" +#include "PS_Editor_SpawnCatalog.generated.h" + +/** + * A single entry in the spawn catalogue. + */ +USTRUCT(BlueprintType) +struct FPS_Editor_SpawnEntry +{ + GENERATED_BODY() + + /** The Blueprint class to spawn. Must have a PS_Editor_SpawnableComponent for name/category. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS Editor") + TSoftClassPtr ActorClass; +}; + +/** + * Data asset listing all Blueprints available for spawning in the PS_Editor runtime catalogue. + * + * Usage: + * 1. Create a DataAsset of type PS_Editor_SpawnCatalog in Content Browser + * 2. Add entries pointing to your prepared Blueprints + * 3. Click "Capture All Thumbnails" to auto-generate thumbnails + * 4. Reference this DataAsset in the PlayerController's SpawnCatalogAsset property + */ +UCLASS(BlueprintType) +class PS_EDITOR_API UPS_Editor_SpawnCatalog : public UPrimaryDataAsset +{ + GENERATED_BODY() + +public: + /** List of spawnable actors. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS Editor") + TArray Entries; + +#if WITH_EDITOR + /** Captures UE-generated thumbnails for all Blueprints in the catalog and assigns them to their SpawnableComponents. */ + UFUNCTION(CallInEditor, Category = "PS Editor") + void CaptureAllThumbnails(); +#endif +}; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalogFactory.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalogFactory.cpp new file mode 100644 index 0000000..7e0759c --- /dev/null +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalogFactory.cpp @@ -0,0 +1,27 @@ +#if WITH_EDITOR + +#include "PS_Editor_SpawnCatalogFactory.h" +#include "PS_Editor_SpawnCatalog.h" + +#define LOCTEXT_NAMESPACE "PS_Editor" + +UPS_Editor_SpawnCatalogFactory::UPS_Editor_SpawnCatalogFactory() +{ + SupportedClass = UPS_Editor_SpawnCatalog::StaticClass(); + bCreateNew = true; + bEditAfterNew = true; +} + +UObject* UPS_Editor_SpawnCatalogFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + return NewObject(InParent, InClass, InName, Flags); +} + +FText UPS_Editor_SpawnCatalogFactory::GetDisplayName() const +{ + return LOCTEXT("SpawnCatalogDisplayName", "PS Editor Spawn Catalog"); +} + +#undef LOCTEXT_NAMESPACE + +#endif diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalogFactory.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalogFactory.h new file mode 100644 index 0000000..1074972 --- /dev/null +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalogFactory.h @@ -0,0 +1,22 @@ +#pragma once + +#if WITH_EDITOR + +#include "CoreMinimal.h" +#include "Factories/Factory.h" +#include "PS_Editor_SpawnCatalogFactory.generated.h" + +UCLASS(HideCategories = Object) +class UPS_Editor_SpawnCatalogFactory : public UFactory +{ + GENERATED_BODY() + +public: + UPS_Editor_SpawnCatalogFactory(); + + virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; + virtual bool ShouldShowInNewMenu() const override { return true; } + virtual FText GetDisplayName() const override; +}; + +#endif diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.cpp new file mode 100644 index 0000000..67c6673 --- /dev/null +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.cpp @@ -0,0 +1,170 @@ +#include "PS_Editor_SpawnManager.h" +#include "PS_Editor_SpawnableComponent.h" +#include "PS_Editor_UndoManager.h" +#include "PS_Editor_PlayerController.h" +#include "GameFramework/PlayerController.h" +#include "Engine/World.h" + +void UPS_Editor_SpawnManager::Initialize(APlayerController* InOwnerPC, UPS_Editor_SpawnCatalog* InCatalog) +{ + OwnerPC = InOwnerPC; + Catalog = InCatalog; + + if (Catalog) + { + ResolveCatalog(); + UE_LOG(LogTemp, Log, TEXT("PS_Editor: SpawnCatalog loaded with %d entries"), ResolvedEntries.Num()); + } + else + { + UE_LOG(LogTemp, Warning, TEXT("PS_Editor: No SpawnCatalog assigned. Spawn catalogue will be empty.")); + } +} + +void UPS_Editor_SpawnManager::ResolveCatalog() +{ + ResolvedEntries.Empty(); + + if (!Catalog) + { + return; + } + + for (const FPS_Editor_SpawnEntry& Entry : Catalog->Entries) + { + // Synchronously load the class + UClass* LoadedClass = Entry.ActorClass.LoadSynchronous(); + if (!LoadedClass) + { + UE_LOG(LogTemp, Warning, TEXT("PS_Editor: Failed to load class for catalog entry")); + continue; + } + + FPS_Editor_ResolvedEntry Resolved; + Resolved.ActorClass = LoadedClass; + + // Find the SpawnableComponent template in the Blueprint class hierarchy. + // Blueprint-added components are stored as subobjects of the UBlueprintGeneratedClass. + UPS_Editor_SpawnableComponent* SpawnComp = nullptr; + + // Search BPGC subobjects (Blueprint-added components) + TArray ClassSubObjects; + GetObjectsWithOuter(LoadedClass, ClassSubObjects, true); + for (UObject* Sub : ClassSubObjects) + { + SpawnComp = Cast(Sub); + if (SpawnComp) + { + break; + } + } + + // Fallback: check CDO subobjects (C++ constructor components) + if (!SpawnComp) + { + AActor* CDO = LoadedClass->GetDefaultObject(); + if (CDO) + { + SpawnComp = CDO->FindComponentByClass(); + } + } + + if (SpawnComp && !SpawnComp->DisplayName.IsEmpty()) + { + Resolved.DisplayName = SpawnComp->DisplayName; + } + else + { + Resolved.DisplayName = LoadedClass->GetName(); + Resolved.DisplayName.RemoveFromEnd(TEXT("_C")); + } + + Resolved.Category = SpawnComp ? SpawnComp->Category : TEXT("Default"); + Resolved.Thumbnail = SpawnComp ? SpawnComp->Thumbnail : nullptr; + + ResolvedEntries.Add(Resolved); + } +} + +AActor* UPS_Editor_SpawnManager::SpawnFromCatalog(int32 Index) +{ + return SpawnFromCatalogAtLocation(Index, ComputeSpawnLocation()); +} + +AActor* UPS_Editor_SpawnManager::SpawnFromCatalogAtLocation(int32 Index, FVector WorldLocation) +{ + if (!ResolvedEntries.IsValidIndex(Index)) + { + UE_LOG(LogTemp, Warning, TEXT("PS_Editor: Invalid catalog index %d"), Index); + return nullptr; + } + + APlayerController* PC = OwnerPC.Get(); + if (!PC || !PC->GetWorld()) + { + return nullptr; + } + + const FPS_Editor_ResolvedEntry& Entry = ResolvedEntries[Index]; + if (!Entry.ActorClass) + { + return nullptr; + } + + FActorSpawnParameters SpawnParams; + SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn; + + AActor* SpawnedActor = PC->GetWorld()->SpawnActor(Entry.ActorClass, WorldLocation, FRotator::ZeroRotator, SpawnParams); + if (!SpawnedActor) + { + UE_LOG(LogTemp, Warning, TEXT("PS_Editor: Failed to spawn %s"), *Entry.DisplayName); + return nullptr; + } + + // Ensure the spawned actor is Movable (so it's selectable) + if (USceneComponent* Root = SpawnedActor->GetRootComponent()) + { + Root->SetMobility(EComponentMobility::Movable); + } + + // Record undo action (undo = hide the spawned actor) + if (APS_Editor_PlayerController* EditorPC = Cast(PC)) + { + if (UPS_Editor_UndoManager* UM = EditorPC->GetUndoManager()) + { + TSharedPtr Action = MakeShared(); + Action->Actors.Add(SpawnedActor); + UM->RecordAction(Action); + } + } + + UE_LOG(LogTemp, Log, TEXT("PS_Editor: Spawned %s at %s"), *Entry.DisplayName, *WorldLocation.ToString()); + return SpawnedActor; +} + +FVector UPS_Editor_SpawnManager::ComputeSpawnLocation() const +{ + APlayerController* PC = OwnerPC.Get(); + if (!PC) + { + return FVector::ZeroVector; + } + + FVector CameraLocation; + FRotator CameraRotation; + PC->GetPlayerViewPoint(CameraLocation, CameraRotation); + + // Spawn 500 units in front of the camera + return CameraLocation + CameraRotation.Vector() * 500.0f; +} + +TArray UPS_Editor_SpawnManager::GetCategories() const +{ + TArray Categories; + for (const FPS_Editor_ResolvedEntry& Entry : ResolvedEntries) + { + Categories.AddUnique(Entry.Category); + } + Categories.Sort(); + return Categories; +} diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.h new file mode 100644 index 0000000..2dc9ed8 --- /dev/null +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.h @@ -0,0 +1,69 @@ +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "PS_Editor_SpawnCatalog.h" +#include "PS_Editor_SpawnManager.generated.h" + +class APlayerController; +class UPS_Editor_UndoManager; + +/** + * Resolved catalog entry with display info ready for UI. + */ +USTRUCT() +struct FPS_Editor_ResolvedEntry +{ + GENERATED_BODY() + + UPROPERTY() + TSubclassOf ActorClass; + + FString DisplayName; + FString Category; + + UPROPERTY() + TObjectPtr Thumbnail; +}; + +/** + * Manages spawning of prepared Blueprint actors at runtime. + * Loads a SpawnCatalog DataAsset and provides spawn functions. + */ +UCLASS() +class PS_EDITOR_API UPS_Editor_SpawnManager : public UObject +{ + GENERATED_BODY() + +public: + /** Initialize with owning controller and load catalog. */ + void Initialize(APlayerController* InOwnerPC, UPS_Editor_SpawnCatalog* InCatalog); + + /** Spawn the actor at the given catalog index, positioned in front of the camera. Returns the spawned actor. */ + AActor* SpawnFromCatalog(int32 Index); + + /** Spawn the actor at a specific world location. */ + AActor* SpawnFromCatalogAtLocation(int32 Index, FVector WorldLocation); + + /** Get the resolved catalog entries for UI display. */ + const TArray& GetResolvedEntries() const { return ResolvedEntries; } + + /** Get unique categories for filtering. */ + TArray GetCategories() const; + +private: + UPROPERTY() + TWeakObjectPtr OwnerPC; + + UPROPERTY() + TObjectPtr Catalog; + + UPROPERTY() + TArray ResolvedEntries; + + /** Resolve all catalog entries (load classes, read SpawnableComponent metadata). */ + void ResolveCatalog(); + + /** Compute spawn location in front of the camera. */ + FVector ComputeSpawnLocation() const; +}; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnableComponent.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnableComponent.cpp new file mode 100644 index 0000000..333a101 --- /dev/null +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnableComponent.cpp @@ -0,0 +1,6 @@ +#include "PS_Editor_SpawnableComponent.h" + +UPS_Editor_SpawnableComponent::UPS_Editor_SpawnableComponent() +{ + PrimaryComponentTick.bCanEverTick = false; +} diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnableComponent.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnableComponent.h new file mode 100644 index 0000000..c27f71b --- /dev/null +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnableComponent.h @@ -0,0 +1,30 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "PS_Editor_SpawnableComponent.generated.h" + +/** + * Marker component for Blueprints that can be spawned via the PS_Editor catalogue. + * Add this component to any Blueprint to make it appear in the runtime spawn catalogue. + */ +UCLASS(ClassGroup = (PSEditor), meta = (BlueprintSpawnableComponent, DisplayName = "PS Editor Spawnable")) +class PS_EDITOR_API UPS_Editor_SpawnableComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UPS_Editor_SpawnableComponent(); + + /** Display name shown in the spawn catalogue UI. If empty, uses the actor's class name. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawnable") + FString DisplayName; + + /** Category for filtering in the catalogue (e.g. "Characters", "Props", "Vehicles"). */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawnable") + FString Category = TEXT("Default"); + + /** Optional thumbnail texture shown in the catalogue. Use "Capture All Thumbnails" on the SpawnCatalog DataAsset to auto-generate. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawnable") + TObjectPtr Thumbnail; +}; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnableComponentCustomization.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnableComponentCustomization.cpp new file mode 100644 index 0000000..5206c38 --- /dev/null +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnableComponentCustomization.cpp @@ -0,0 +1,139 @@ +#if WITH_EDITOR + +#include "PS_Editor_SpawnableComponentCustomization.h" +#include "PS_Editor_SpawnableComponent.h" +#include "DetailLayoutBuilder.h" +#include "DetailCategoryBuilder.h" +#include "DetailWidgetRow.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Text/STextBlock.h" +#include "Engine/Blueprint.h" +#include "Engine/Texture2D.h" +#include "ThumbnailRendering/ThumbnailManager.h" +#include "ObjectTools.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "UObject/SavePackage.h" +#include "Misc/PackageName.h" +#include "HAL/FileManager.h" + +TSharedRef FPS_Editor_SpawnableComponentCustomization::MakeInstance() +{ + return MakeShareable(new FPS_Editor_SpawnableComponentCustomization); +} + +void FPS_Editor_SpawnableComponentCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) +{ + TArray> Objects; + DetailBuilder.GetObjectsBeingCustomized(Objects); + if (Objects.Num() > 0) + { + EditedComponent = Objects[0]; + } + + IDetailCategoryBuilder& Category = DetailBuilder.EditCategory("PS Editor|Spawnable"); + Category.AddCustomRow(FText::FromString(TEXT("Capture Thumbnail"))) + .NameContent() + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Auto Thumbnail"))) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(150.0f) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .OnClicked(FOnClicked::CreateRaw(this, &FPS_Editor_SpawnableComponentCustomization::OnCaptureThumbnailClicked)) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Capture Thumbnail"))) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ]; +} + +FReply FPS_Editor_SpawnableComponentCustomization::OnCaptureThumbnailClicked() +{ + UPS_Editor_SpawnableComponent* Comp = Cast(EditedComponent.Get()); + if (!Comp) + { + return FReply::Handled(); + } + + // Find the owning Blueprint + AActor* OwnerActor = Comp->GetOwner(); + if (!OwnerActor) + { + return FReply::Handled(); + } + + UClass* ActorClass = OwnerActor->GetClass(); + UBlueprint* Blueprint = Cast(ActorClass->ClassGeneratedBy); + if (!Blueprint) + { + UE_LOG(LogTemp, Warning, TEXT("PS_Editor: Cannot capture thumbnail - not a Blueprint actor")); + return FReply::Handled(); + } + + // Generate thumbnail + FObjectThumbnail* ObjThumb = ThumbnailTools::GenerateThumbnailForObjectToSaveToDisk(Blueprint); + if (!ObjThumb || ObjThumb->GetImageWidth() <= 0 || ObjThumb->GetImageHeight() <= 0) + { + UE_LOG(LogTemp, Warning, TEXT("PS_Editor: Failed to generate thumbnail for %s"), *Blueprint->GetName()); + return FReply::Handled(); + } + + const int32 Width = ObjThumb->GetImageWidth(); + const int32 Height = ObjThumb->GetImageHeight(); + const TArray& PixelData = ObjThumb->GetUncompressedImageData(); + + if (PixelData.Num() != Width * Height * 4) + { + UE_LOG(LogTemp, Warning, TEXT("PS_Editor: Invalid thumbnail pixel data")); + return FReply::Handled(); + } + + // Save as a persistent texture asset next to the Blueprint + FString BlueprintPath = Blueprint->GetOutermost()->GetName(); + FString BasePath = FPaths::GetPath(BlueprintPath); + FString AssetName = FString::Printf(TEXT("T_%s_Thumb"), *Blueprint->GetName()); + FString FullPath = FString::Printf(TEXT("%s/%s"), *BasePath, *AssetName); + + UPackage* Package = CreatePackage(*FullPath); + if (!Package) + { + UE_LOG(LogTemp, Warning, TEXT("PS_Editor: Failed to create package for thumbnail")); + return FReply::Handled(); + } + + UTexture2D* Texture = NewObject(Package, *AssetName, RF_Public | RF_Standalone); + Texture->Source.Init(Width, Height, 1, 1, TSF_BGRA8); + + uint8* MipData = Texture->Source.LockMip(0); + FMemory::Memcpy(MipData, PixelData.GetData(), PixelData.Num()); + Texture->Source.UnlockMip(0); + + Texture->SRGB = true; + Texture->CompressionSettings = TC_Default; + Texture->MipGenSettings = TMGS_NoMipmaps; + Texture->PostEditChange(); + Texture->UpdateResource(); + + // Save the package + FString PackageFilename = FPackageName::LongPackageNameToFilename(FullPath, FPackageName::GetAssetPackageExtension()); + FSavePackageArgs SaveArgs; + SaveArgs.TopLevelFlags = RF_Public | RF_Standalone; + UPackage::SavePackage(Package, Texture, *PackageFilename, SaveArgs); + + FAssetRegistryModule::AssetCreated(Texture); + + // Assign to the component + Comp->Thumbnail = Texture; + Comp->MarkPackageDirty(); + + UE_LOG(LogTemp, Log, TEXT("PS_Editor: Thumbnail captured and saved to %s"), *FullPath); + + return FReply::Handled(); +} + +#endif diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnableComponentCustomization.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnableComponentCustomization.h new file mode 100644 index 0000000..8d0a739 --- /dev/null +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnableComponentCustomization.h @@ -0,0 +1,26 @@ +#pragma once + +#if WITH_EDITOR + +#include "CoreMinimal.h" +#include "IDetailCustomization.h" + +/** + * Detail panel customization for UPS_Editor_SpawnableComponent. + * Adds a "Capture Thumbnail" button that grabs the Blueprint's auto-generated + * thumbnail and saves it as a persistent UTexture2D asset. + */ +class FPS_Editor_SpawnableComponentCustomization : public IDetailCustomization +{ +public: + static TSharedRef MakeInstance(); + + virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; + +private: + FReply OnCaptureThumbnailClicked(); + + TWeakObjectPtr EditedComponent; +}; + +#endif diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/PS_Editor_HUD.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/PS_Editor_HUD.cpp index 14d19d5..5bc7129 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/PS_Editor_HUD.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/PS_Editor_HUD.cpp @@ -17,6 +17,7 @@ void APS_Editor_HUD::BeginPlay() if (APS_Editor_PlayerController* EditorPC = Cast(PC)) { MainWidget->SetSelectionManager(EditorPC->GetSelectionManager()); + MainWidget->SetSpawnManager(EditorPC->GetSpawnManager()); } MainWidget->AddToViewport(0); diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp index a003408..5222842 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp @@ -1,5 +1,6 @@ #include "PS_Editor_MainWidget.h" #include "PS_Editor_SelectionManager.h" +#include "PS_Editor_SpawnManager.h" #include "PS_Editor_UndoManager.h" #include "PS_Editor_PlayerController.h" #include "Widgets/SBoxPanel.h" @@ -7,6 +8,8 @@ #include "Widgets/Layout/SBorder.h" #include "Widgets/Layout/SScrollBox.h" #include "Widgets/Input/SButton.h" +#include "Widgets/Images/SImage.h" +#include "Engine/Texture2D.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/SOverlay.h" @@ -15,6 +18,11 @@ void UPS_Editor_MainWidget::SetSelectionManager(UPS_Editor_SelectionManager* InS SelectionManager = InSelectionManager; } +void UPS_Editor_MainWidget::SetSpawnManager(UPS_Editor_SpawnManager* InSpawnManager) +{ + SpawnManager = InSpawnManager; +} + TSharedRef UPS_Editor_MainWidget::RebuildWidget() { return SNew(SOverlay) @@ -63,6 +71,14 @@ TSharedRef UPS_Editor_MainWidget::RebuildWidget() BuildToolbar() ] ] + // Left panel: Spawn Catalogue + + SOverlay::Slot() + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .Padding(16.0f, 80.0f, 0.0f, 60.0f) + [ + BuildSpawnCatalog() + ] // Right panel: Properties + SOverlay::Slot() .HAlign(HAlign_Right) @@ -242,9 +258,175 @@ TSharedRef UPS_Editor_MainWidget::BuildPropertiesPanel() ]; } +TSharedRef UPS_Editor_MainWidget::BuildSpawnCatalog() +{ + TSharedRef CatalogList = SNew(SVerticalBox); + + // Header + CatalogList->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 4.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Spawn Catalogue"))) + .Font(FCoreStyle::GetDefaultFontStyle("Bold", 11)) + .ColorAndOpacity(FLinearColor(0.8f, 0.8f, 0.8f)) + ]; + + // Note: entries will be populated in NativeConstruct when SpawnManager is set. + // For now, show a placeholder. The actual list is built dynamically. + CatalogList->AddSlot() + .AutoHeight() + [ + SAssignNew(SpawnListContainer, SVerticalBox) + ]; + + return SNew(SBorder) + .BorderBackgroundColor(FLinearColor(0.05f, 0.05f, 0.05f, 0.85f)) + .Padding(FMargin(10.0f, 8.0f)) + [ + SNew(SBox) + .MinDesiredWidth(180.0f) + .MaxDesiredHeight(400.0f) + [ + SNew(SScrollBox) + + SScrollBox::Slot() + [ + CatalogList + ] + ] + ]; +} + void UPS_Editor_MainWidget::NativeConstruct() { Super::NativeConstruct(); + PopulateSpawnCatalog(); +} + +void UPS_Editor_MainWidget::PopulateSpawnCatalog() +{ + if (!SpawnListContainer.IsValid()) + { + return; + } + + SpawnListContainer->ClearChildren(); + + UPS_Editor_SpawnManager* SM = SpawnManager.Get(); + if (!SM) + { + SpawnListContainer->AddSlot() + .AutoHeight() + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("No catalog loaded"))) + .Font(FCoreStyle::GetDefaultFontStyle("Italic", 9)) + .ColorAndOpacity(FLinearColor(0.5f, 0.5f, 0.5f)) + ]; + return; + } + + const TArray& Entries = SM->GetResolvedEntries(); + if (Entries.Num() == 0) + { + SpawnListContainer->AddSlot() + .AutoHeight() + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Catalog is empty"))) + .Font(FCoreStyle::GetDefaultFontStyle("Italic", 9)) + .ColorAndOpacity(FLinearColor(0.5f, 0.5f, 0.5f)) + ]; + return; + } + + // Group by category + FString CurrentCategory; + for (int32 i = 0; i < Entries.Num(); ++i) + { + const FPS_Editor_ResolvedEntry& Entry = Entries[i]; + + // Category header + if (Entry.Category != CurrentCategory) + { + CurrentCategory = Entry.Category; + SpawnListContainer->AddSlot() + .AutoHeight() + .Padding(0.0f, 6.0f, 0.0f, 2.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(CurrentCategory)) + .Font(FCoreStyle::GetDefaultFontStyle("Bold", 9)) + .ColorAndOpacity(FLinearColor(0.6f, 0.6f, 0.6f)) + ]; + } + + // Spawn card: thumbnail on top, name below + const int32 EntryIndex = i; + const float CardSize = 80.0f; + + TSharedRef CardContent = SNew(SVerticalBox); + + // Thumbnail + if (Entry.Thumbnail) + { + TSharedPtr Brush = MakeShared(); + Brush->SetResourceObject(Entry.Thumbnail); + Brush->ImageSize = FVector2D(CardSize, CardSize); + Brush->DrawAs = ESlateBrushDrawType::Image; + Brush->Tiling = ESlateBrushTileType::NoTile; + CachedBrushes.Add(Brush); + + TWeakPtr WeakBrush = Brush; + CardContent->AddSlot() + .AutoHeight() + .HAlign(HAlign_Center) + [ + SNew(SImage) + .Image_Lambda([WeakBrush]() -> const FSlateBrush* + { + return WeakBrush.IsValid() ? WeakBrush.Pin().Get() : nullptr; + }) + .DesiredSizeOverride(FVector2D(CardSize, CardSize)) + ]; + } + + // Name below thumbnail + CardContent->AddSlot() + .AutoHeight() + .HAlign(HAlign_Center) + .Padding(0.0f, 4.0f, 0.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(Entry.DisplayName)) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 9)) + .Justification(ETextJustify::Center) + .ColorAndOpacity(FLinearColor::White) + ]; + + SpawnListContainer->AddSlot() + .AutoHeight() + .Padding(4.0f, 4.0f) + [ + SNew(SButton) + .OnClicked_Lambda([this, EntryIndex]() -> FReply + { + if (UPS_Editor_SpawnManager* Mgr = SpawnManager.Get()) + { + Mgr->SpawnFromCatalog(EntryIndex); + } + return FReply::Handled(); + }) + [ + SNew(SBox) + .MinDesiredWidth(CardSize + 8.0f) + [ + CardContent + ] + ] + ]; + } } void UPS_Editor_MainWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.h index d9a4f73..b737501 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.h @@ -6,6 +6,7 @@ #include "PS_Editor_MainWidget.generated.h" class UPS_Editor_SelectionManager; +class UPS_Editor_SpawnManager; /** * Main editor widget for PS_Editor. @@ -18,8 +19,9 @@ class PS_EDITOR_API UPS_Editor_MainWidget : public UUserWidget GENERATED_BODY() public: - /** Set the selection manager reference. Must be called after construction. */ + /** Set manager references. Must be called after construction. */ void SetSelectionManager(UPS_Editor_SelectionManager* InSelectionManager); + void SetSpawnManager(UPS_Editor_SpawnManager* InSpawnManager); protected: virtual TSharedRef RebuildWidget() override; @@ -30,10 +32,17 @@ private: UPROPERTY() TWeakObjectPtr SelectionManager; + UPROPERTY() + TWeakObjectPtr SpawnManager; + // ---- Slate widget refs for dynamic updates ---- TSharedPtr ModeText; TSharedPtr SelectionInfoText; + // Spawn catalog + TSharedPtr SpawnListContainer; + TArray> CachedBrushes; + // Transform fields TSharedPtr LocX, LocY, LocZ; TSharedPtr RotX, RotY, RotZ; @@ -42,6 +51,8 @@ private: // ---- Helpers ---- TSharedRef BuildToolbar(); TSharedRef BuildPropertiesPanel(); + TSharedRef BuildSpawnCatalog(); + void PopulateSpawnCatalog(); void RefreshPropertiesFromSelection(); void ApplyTransformFromUI(); FString FloatToString(float Value) const;