[Godot] Making Plugin or Add On 플러그인, 애드온 만들기 3
Godot 2026. 9. 3. 22:17 |반응형
오브젝트 페인터 플러그인을 만들어 보자.
기본적인 내용은 아래 링크를 참고한다.
2026.09.02 - [Godot] - [Godot] Making Plugin or Add On 플러그인, 애드온 만들기 1

플러그인 UI로 사용할 씬은 위와 같이 구성한다.

myplugin.cs의 내용은 아래와 같이 작성한다.
#if TOOLS
using Godot;
[Tool]
public partial class myplugin : EditorPlugin
{
private EditorDock _dock; // 플러그인 UI를 담을 도킹 컨테이너
private PackedScene _selectedScene; // 도장처럼 찍을 씬을 기억할 변수
private EditorResourcePicker _resourcePicker; // 도장처럼 찍을 씬을 선택할 수 있는 리소스 선택기
private Button _button; // 종료 버튼
public override void _EnterTree()
{
// 플러그인 UI를 담은 씬을 로드하고 인스턴스화한다.
PackedScene packedScene = GD.Load<PackedScene>("res://test.tscn");
Control control = packedScene.Instantiate<Control>();
_button = control.GetNode<Button>("VBoxContainer/Button");
_button.Pressed += OnEndButtonPressed;
_resourcePicker = control.GetNode<EditorResourcePicker>("VBoxContainer/EditorResourcePicker");
_resourcePicker.BaseType = "PackedScene"; // 선택 가능한 리소스 타입을 PackedScene으로 제한
_resourcePicker.ResourceChanged += OnResourceChanged;
_dock = new EditorDock();
_dock.Name = "오브젝트 페인터";
_dock.DefaultSlot = EditorDock.DockSlot.RightUl;
_dock.AddChild(control);
AddDock(_dock);
}
public override void _ExitTree()
{
_button.Pressed -= OnEndButtonPressed;
_resourcePicker.ResourceChanged -= OnResourceChanged;
if (_dock != null)
{
RemoveDock(_dock);
_dock.QueueFree();
}
}
private void OnEndButtonPressed()
{
// 플러그인 비활성화
EditorInterface.Singleton.SetPluginEnabled("myplugin", false);
GD.Print($"{_dock.Name} 플러그인이 비활성화되었습니다.");
}
private void OnResourceChanged(Resource resource)
{
// 리소스 선택기가 PackedScene을 선택하면 _selectedScene에 저장한다.
if (resource is PackedScene packedScene)
{
_selectedScene = packedScene;
GD.Print($"선택된 씬: {_selectedScene.ResourcePath}\n화면을 클릭해 배치하세요!");
}
else
{
_selectedScene = null;
GD.Print("선택된 씬이 없습니다. 페인트 모드를 종료합니다.");
}
}
public override bool _Handles(GodotObject @object)
{
// 현재 선택된 노드가 Node2D 계열일 때만 이 플러그인이 마우스 입력을 처리.
return @object is Node2D;
}
// 에디터 뷰포트 마우스 클릭 가로채기
public override bool _ForwardCanvasGuiInput(InputEvent @event)
{
// 선택된 씬이 없으면 엔진의 원래 마우스 동작(선택, 이동 등)을 방해하지 않고 종료한다.
if (_selectedScene == null) return false;
// 마우스 클릭 이벤트인지 확인하고, 왼쪽 버튼 클릭이면 오브젝트를 배치하고, 오른쪽 버튼 클릭이면 페인트 모드를 취소한다.
if (@event is InputEventMouseButton mouseEvent)
{
// 왼쪽 버튼을 '눌렀을 때' (클릭 순간)
if (mouseEvent.ButtonIndex == MouseButton.Left && mouseEvent.Pressed)
{
// 실제 화면 뷰포트의 줌/이동이 반영된 마우스 월드 좌표를 계산.
Transform2D canvasTransform = EditorInterface.Singleton.GetEditorViewport2D().GlobalCanvasTransform;
Vector2 worldPos = canvasTransform.AffineInverse() * mouseEvent.Position;
// 오브젝트 생성 함수 호출.
SpawnObject(worldPos);
// true를 반환하면 이 클릭이 처리됐으니 더 이상 다른 플러그인이나 에디터 기본 동작이 이 클릭을
// 처리하지 않도록 막는다.
return true;
}
// 오른쪽 버튼을 누르면 페인트 모드를 취소.
else if (mouseEvent.ButtonIndex == MouseButton.Right && mouseEvent.Pressed)
{
_selectedScene = null;
GD.Print("페인트 모드 취소됨.");
return true;
}
}
return false;
}
// 씬 생성 및 배치
private void SpawnObject(Vector2 position)
{
// 현재 에디터에서 열려있는 최상위 씬 루트를 가져온다. (없으면 종료)
Node sceneRoot = EditorInterface.Singleton.GetEditedSceneRoot();
if (sceneRoot == null) return;
// 씬 루트에서 "Heart"라는 이름의 노드(Node)를 찾아서 부모로 지정한다. (없으면 새로 만들어 지정한다)
Node targetParent = sceneRoot.FindChild("Heart", true, false);
if (targetParent == null)
{
targetParent = new Node();
targetParent.Name = "Heart";
sceneRoot.AddChild(targetParent);
targetParent.Owner = sceneRoot;
GD.Print("'Heart 노드가 없어서 새로 생성했습니다.");
}
// 씬을 인스턴스화한다. (Node2D 계열이어야 함)
Node2D newObject = _selectedScene.Instantiate<Node2D>(PackedScene.GenEditState.Instance);
// Node2D newObject = _selectedScene.Instantiate<Node2D>() 으로 인스턴스화 하면 안된다.
// PackedScene.GenEditState.Instance 옵션을 주어야 시그널 연결, 에디터 전용 노드, 에디터 전용 스크립트 등
// 에디터에서만 필요한 정보가 유지된 상태로 인스턴스화 된다.
/********************************************************************************************************
// 이 주석의 코드는 UndoRedoManager를 사용하지 않고 바로 씬 트리에 붙이는 방식이다.
// 이렇게 하면 에디터에서 Ctrl + Z로 되돌리기(Undo)가 불가능하고, 씬 변경 사항이 인식되지 않아 씬 이름 탭에
// 별표(*)가 뜨지 않는다. (즉, 씬이 변경되었는데도 저장하지 않아도 된다고 에디터가 판단함)
// 씬 트리에 추가하고 위치를 지정한다.
targetParent.AddChild(newObject);
newObject.GlobalPosition = position;
// 중요!!
// 생성된 노드의 Owner를 반드시 루트로 설정해야 한다.
newObject.Owner = sceneRoot;
// 스크립트(코드)로 노드를 생성해서 AddChild()로 씬 트리에 붙이더라도, Owner 속성을 씬의 최상위
// 루트 노드(Root)로 지정해주지 않으면 에디터에서 Ctrl + S로 씬을 저장할 때 해당 노드들이 파일에
// 저장되지 않고 사라져버린다. (Owner가 null이면 씬에 속하지 않는 노드로 간주됨)
//
// 부모가 Heart 노드라고 해서 Owner를 Heart로 지정하면 안 된다. Owner는 반드시 씬의 최상위 루트 노드이어야 한다.
// Owner는 진짜 부모 노드와는 별개로, 씬 파일에 저장될 때 어떤 노드가 씬에 속하는지 여부를 결정하는 속성이다.
GD.Print($"오브젝트가 {position}에 배치되었습니다.");
**********************************************************************************************************/
// 아래 코드는 UndoRedoManager를 사용하여 씬 트리에 노드를 추가하는 방식이다.
// 위 주석 부분과 동일한 동작을 하지만, UndoRedoManager를 사용하면 Ctrl + Z로 되돌리기(Undo)가 가능하고,
// 씬 변경 사항이 인식되어 씬 이름 탭에 별표(*)가 뜨게 된다. (즉, 씬이 변경되었음을 에디터가 인식함)
// 에디터의 실행 취소/다시 실행 매니저를 가져온다. (UndoRedoManager)
EditorUndoRedoManager undoRedo = GetUndoRedo();
// 액션의 이름을 정하고 기록을 시작한다. (에디터 기록에 "오브젝트 배치"라고 남음)
undoRedo.CreateAction("오브젝트 배치");
// --- 실행할 때 (Do) ---
// AddChild, 위치 지정, Owner 설정을 순서대로 예약한다.
undoRedo.AddDoMethod(targetParent, Node.MethodName.AddChild, newObject);
undoRedo.AddDoProperty(newObject, Node2D.PropertyName.GlobalPosition, position);
undoRedo.AddDoProperty(newObject, Node.PropertyName.Owner, sceneRoot);
// Undo로 삭제되었을 때 메모리에서 완전히 날아가지 않도록 참조를 유지해 준다.
undoRedo.AddDoReference(newObject);
// --- 취소할 때 (Undo) ---
// Ctrl+Z를 누르면 씬 트리에서 이 노드를 떼어낸다.
undoRedo.AddUndoMethod(targetParent, Node.MethodName.RemoveChild, newObject);
// 액션 실행!
// 이 함수가 호출되는 순간 예약된 Do 작업들이 실행되며, 씬에 별표(*)가 생긴다.
undoRedo.CommitAction();
GD.Print($"오브젝트가 {position}에 배치되었습니다.");
}
}
#endif

Project Settings - Plugin - MyPlugin을 Enable 한다.

화면 우측 패널에 플러그인이 추가된다. 종료 버튼 위의 EditorResourcePicker 오른쪽, 아래 화살표를 클릭하고 Quick Load...를 클릭한다.

페인트로 사용할 씬을 선택한다. 여기서는 미리 만들어둔 heart.tscn을 선택했다.

화면 원하는 곳을 클릭하면 하트가 생성된다.
마우스 우클릭을 하면 페인트 모드가 종료되고 플러그인의 종료 버튼을 클릭하면 플러그인이 종료된다.
반응형
'Godot' 카테고리의 다른 글
| [Godot] Making Plugin or Add On 플러그인, 애드온 만들기 2 (0) | 2026.09.03 |
|---|---|
| [Godot] Making Plugin or Add On 플러그인, 애드온 만들기 1 (0) | 2026.09.02 |
| [Godot] Lemmings or Worms Style Terrain 레밍즈, 웜즈 스타일 지형 만들기 (0) | 2026.08.30 |
| [Godot] Old CRT Monitor Shader 오래된 CRT 모니터 셰이더 (0) | 2024.03.03 |
| [Godot] 2D Fog Shader 안개 셰이더 (0) | 2024.03.03 |
