.
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
using Godot;
|
||||
using GodotHostTest.GodotDI;
|
||||
|
||||
namespace GodotHostTest.Game;
|
||||
|
||||
public partial class Root : Node2D
|
||||
{
|
||||
[Inject] private readonly ISceneInstantiator _sceneInstantiator = null!;
|
||||
[Export] private PackedScene _levelScene = null!;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
AddChild(_sceneInstantiator.Instantiate(_levelScene));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://bvjvojer872c8
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
[gd_scene format=3 uid="uid://xfd48ep5qi1k"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dk578xnlxjwrr" path="res://Game/LevelServiceSource.cs" id="1_5saw1"]
|
||||
[ext_resource type="PackedScene" uid="uid://3v8kq1rfwhmr" path="res://Game/target.tscn" id="1_y7rhs"]
|
||||
[ext_resource type="Script" uid="uid://cxfrtglku2gfa" path="res://Game/TargetSpawner.cs" id="2_5saw1"]
|
||||
[ext_resource type="Script" uid="uid://bgrjeadxhgmvi" path="res://Game/Score.cs" id="3_spj1u"]
|
||||
[ext_resource type="Texture2D" uid="uid://y1ros3y7hag2" path="res://icon.svg" id="4_esqs7"]
|
||||
[ext_resource type="Script" uid="uid://ce7gnp1lcrrdv" path="res://Game/Turret.cs" id="5_himod"]
|
||||
[ext_resource type="PackedScene" uid="uid://brbufibn7hi3o" path="res://Game/projectile.tscn" id="6_sin03"]
|
||||
|
||||
[sub_resource type="Animation" id="Animation_bbjqm"]
|
||||
length = 0.001
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("Level/Score:offset_transform_position")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 0,
|
||||
"values": [Vector2(0, 0)]
|
||||
}
|
||||
tracks/1/type = "value"
|
||||
tracks/1/imported = false
|
||||
tracks/1/enabled = true
|
||||
tracks/1/path = NodePath("Level/Score:offset_transform_rotation")
|
||||
tracks/1/interp = 1
|
||||
tracks/1/loop_wrap = true
|
||||
tracks/1/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 0,
|
||||
"values": [0.0]
|
||||
}
|
||||
|
||||
[sub_resource type="Animation" id="Animation_4ty2a"]
|
||||
resource_name = "wiggle"
|
||||
length = 0.3
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("Level/Score:offset_transform_position")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"times": PackedFloat32Array(0, 0.049999997, 0.099999994, 0.15, 0.2, 0.24999999),
|
||||
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1),
|
||||
"update": 0,
|
||||
"values": [Vector2(0, 0), Vector2(10, -10), Vector2(-10, -10), Vector2(10, 10), Vector2(-10, 10), Vector2(0, 0)]
|
||||
}
|
||||
tracks/1/type = "value"
|
||||
tracks/1/imported = false
|
||||
tracks/1/enabled = true
|
||||
tracks/1/path = NodePath("Level/Score:offset_transform_rotation")
|
||||
tracks/1/interp = 1
|
||||
tracks/1/loop_wrap = true
|
||||
tracks/1/keys = {
|
||||
"times": PackedFloat32Array(0, 0.049999997, 0.099999994, 0.15, 0.2, 0.24999999),
|
||||
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1),
|
||||
"update": 0,
|
||||
"values": [0.0, 0.08726646, -0.08726646, 0.08726646, -0.08726646, 0.0]
|
||||
}
|
||||
|
||||
[sub_resource type="AnimationLibrary" id="AnimationLibrary_rckqj"]
|
||||
_data = {
|
||||
&"RESET": SubResource("Animation_bbjqm"),
|
||||
&"wiggle": SubResource("Animation_4ty2a")
|
||||
}
|
||||
|
||||
[node name="Level" type="Node2D" unique_id=1331632726]
|
||||
|
||||
[node name="RootScope" type="Node" parent="." unique_id=1267156771 node_paths=PackedStringArray("_targetSpawner", "InjectedNodes")]
|
||||
script = ExtResource("1_5saw1")
|
||||
_targetSpawner = NodePath("../Spawner")
|
||||
_serviceScope = "GodotHostTest.Game.LevelScope, godot-host-test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
|
||||
InjectedNodes = [NodePath("../Target"), NodePath("../Target2"), NodePath("../Target3"), NodePath("../Target4"), NodePath("../Target5"), NodePath("../Target6"), NodePath("../Score"), NodePath("../Turret")]
|
||||
|
||||
[node name="Target" parent="." unique_id=2101558052 instance=ExtResource("1_y7rhs")]
|
||||
position = Vector2(198, 143)
|
||||
TimeOutDurationInSeconds = 2.0
|
||||
|
||||
[node name="Target2" parent="." unique_id=1009901052 instance=ExtResource("1_y7rhs")]
|
||||
position = Vector2(669, 64)
|
||||
TimeOutDurationInSeconds = 2.0
|
||||
|
||||
[node name="Target3" parent="." unique_id=1433382188 instance=ExtResource("1_y7rhs")]
|
||||
position = Vector2(1060, 263)
|
||||
TimeOutDurationInSeconds = 2.0
|
||||
|
||||
[node name="Target4" parent="." unique_id=1553596281 instance=ExtResource("1_y7rhs")]
|
||||
position = Vector2(961, 620)
|
||||
TimeOutDurationInSeconds = 2.0
|
||||
|
||||
[node name="Target5" parent="." unique_id=46923989 instance=ExtResource("1_y7rhs")]
|
||||
position = Vector2(469, 591)
|
||||
TimeOutDurationInSeconds = 2.0
|
||||
|
||||
[node name="Target6" parent="." unique_id=2092179937 instance=ExtResource("1_y7rhs")]
|
||||
position = Vector2(53, 417)
|
||||
TimeOutDurationInSeconds = 2.0
|
||||
|
||||
[node name="Spawner" type="Node2D" parent="." unique_id=251404460 node_paths=PackedStringArray("Timer")]
|
||||
script = ExtResource("2_5saw1")
|
||||
TimeBetweenSpawns = 1.5
|
||||
Timer = NodePath("Timer")
|
||||
|
||||
[node name="Timer" type="Timer" parent="Spawner" unique_id=1066078427]
|
||||
|
||||
[node name="Score" type="ColorRect" parent="." unique_id=1267746809 node_paths=PackedStringArray("_animationPlayer", "_label")]
|
||||
offset_right = 200.0
|
||||
offset_bottom = 40.0
|
||||
offset_transform_enabled = true
|
||||
color = Color(1, 1, 1, 0.2784314)
|
||||
script = ExtResource("3_spj1u")
|
||||
_animationPlayer = NodePath("../AnimationPlayer")
|
||||
_label = NodePath("Label")
|
||||
|
||||
[node name="Label" type="Label" parent="Score" unique_id=558063655]
|
||||
layout_mode = 1
|
||||
anchors_preset = -1
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 10.0
|
||||
offset_right = -10.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_colors/font_color = Color(0, 0, 0, 1)
|
||||
text = "Score: 0"
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="." unique_id=1215951318]
|
||||
root_node = NodePath("../..")
|
||||
libraries/ = SubResource("AnimationLibrary_rckqj")
|
||||
|
||||
[node name="Turret" type="Sprite2D" parent="." unique_id=152356196]
|
||||
modulate = Color(1.92523e-07, 0.88004553, 0.8802661, 1)
|
||||
position = Vector2(539, 307)
|
||||
texture = ExtResource("4_esqs7")
|
||||
script = ExtResource("5_himod")
|
||||
_projectilePackedScene = ExtResource("6_sin03")
|
||||
|
||||
[connection signal="timeout" from="Spawner/Timer" to="Spawner" method="OnTimer"]
|
||||
@@ -0,0 +1 @@
|
||||
uid://bahq074tnev8g
|
||||
@@ -0,0 +1,170 @@
|
||||
#if TOOLS
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
namespace GodotHostTest.GodotDI.Editor;
|
||||
|
||||
[Tool]
|
||||
public partial class Plugin : EditorPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Amount of debounce before updating the injections
|
||||
/// </summary>
|
||||
private const double DebounceDelay = 0.1;
|
||||
|
||||
private const BindingFlags InjectBindingFlags =
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
||||
|
||||
private ScopeInspectorPlugin? _plugin;
|
||||
|
||||
private SceneTreeTimer? _sceneTreeTimer;
|
||||
|
||||
private bool _treeChangesRegistered;
|
||||
|
||||
public override void _EnterTree()
|
||||
{
|
||||
GD.Print("Initializing Godot DI");
|
||||
|
||||
// add the inspector plugin to provide a better interface for the "scope" property
|
||||
_plugin = new ScopeInspectorPlugin();
|
||||
AddInspectorPlugin(_plugin);
|
||||
|
||||
// update injected nodes when the scene changes
|
||||
SceneChanged += OnSceneChanged;
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
GD.Print("Deinitializing Godot DI");
|
||||
|
||||
SceneChanged -= OnSceneChanged;
|
||||
|
||||
UnregisterTreeChanges();
|
||||
|
||||
RemoveInspectorPlugin(_plugin);
|
||||
_plugin?.Dispose();
|
||||
_plugin = null;
|
||||
}
|
||||
|
||||
private void OnTreeChanged()
|
||||
{
|
||||
var root = EditorInterface.Singleton.GetEditedSceneRoot();
|
||||
OnSceneChanged(root);
|
||||
}
|
||||
|
||||
private void OnSceneChanged(Node? sceneRoot)
|
||||
{
|
||||
// register to tree changes, in case the registration wasn't possible before
|
||||
RegisterTreeChanges();
|
||||
|
||||
// don't do anything if there's no scene root
|
||||
if (sceneRoot == null) return;
|
||||
|
||||
// apply debounce
|
||||
if (_sceneTreeTimer != null)
|
||||
{
|
||||
_sceneTreeTimer.SetTimeLeft(DebounceDelay);
|
||||
return;
|
||||
}
|
||||
|
||||
_sceneTreeTimer =
|
||||
EditorInterface.Singleton.GetEditedSceneRoot().GetTree().CreateTimer(DebounceDelay);
|
||||
_sceneTreeTimer.Timeout += () => ActualOnSceneChanged(sceneRoot);
|
||||
}
|
||||
|
||||
private void ActualOnSceneChanged(Node sceneRoot)
|
||||
{
|
||||
// mark debounce as completed
|
||||
_sceneTreeTimer = null;
|
||||
|
||||
var sceneNodes = sceneRoot.FindChildren("*");
|
||||
GD.Print($"Updating injection for root {sceneRoot.Name}");
|
||||
|
||||
// look for the service source
|
||||
var serviceSource = sceneNodes.Where(child => child.IsServiceSource())
|
||||
.Single(out var failureReason);
|
||||
switch (failureReason)
|
||||
{
|
||||
case EnumerableExtensions.SingleFailureReason.LessThanOne:
|
||||
return;
|
||||
case EnumerableExtensions.SingleFailureReason.MoreThanOne:
|
||||
GD.PushWarning("Found more than one service source owned by the scene.");
|
||||
return;
|
||||
default:
|
||||
{
|
||||
// find all nodes that need injection
|
||||
var nodesNeedingInjection = sceneNodes.Append(sceneRoot).Where(node =>
|
||||
{
|
||||
var type = node.GetCSharpScriptType();
|
||||
return type != null && (
|
||||
type.GetFields(InjectBindingFlags).Any(field =>
|
||||
field.GetCustomAttributes(typeof(InjectAttribute), true).Length > 0) ||
|
||||
type.GetMethods(InjectBindingFlags).Any(field =>
|
||||
field.GetCustomAttributes(typeof(InjectAttribute), true).Length > 0)
|
||||
);
|
||||
}).ToList();
|
||||
|
||||
// add all nodes needed to the scope
|
||||
var injectedNodes = serviceSource!.Get(ServiceSource.PropertyName.InjectedNodes).AsGodotArray();
|
||||
Array? newArray = null;
|
||||
foreach (var node in nodesNeedingInjection
|
||||
.Where(node => !injectedNodes.Contains(node)))
|
||||
{
|
||||
newArray ??= new Array(injectedNodes);
|
||||
newArray.Add(node);
|
||||
GD.Print($"Added node {node.Name} to injection.");
|
||||
}
|
||||
|
||||
// remove all nodes that are no longer needing injection
|
||||
foreach (var node in injectedNodes
|
||||
.Select(n => (Node?)n.AsGodotObject())
|
||||
.Where(node => !nodesNeedingInjection.Contains(node)))
|
||||
{
|
||||
if (node == null) continue;
|
||||
newArray ??= new Array(injectedNodes);
|
||||
newArray.Remove(node);
|
||||
GD.Print($"Removed node {node?.Name} from injection.");
|
||||
}
|
||||
|
||||
// apply changes to the scene if necessary
|
||||
if (newArray != null)
|
||||
{
|
||||
GD.Print("Apply changes.");
|
||||
var undoRedo = GetUndoRedo();
|
||||
undoRedo.CreateAction("Update injected nodes");
|
||||
undoRedo.AddDoProperty(serviceSource, ServiceSource.PropertyName.InjectedNodes, newArray);
|
||||
undoRedo.AddUndoProperty(serviceSource, ServiceSource.PropertyName.InjectedNodes, injectedNodes);
|
||||
undoRedo.CommitAction();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attach to the TreeChanged event of the SceneTree if it's not already registered and if there's a scene tree to
|
||||
/// attach to from the currently edited scene.
|
||||
/// </summary>
|
||||
private void RegisterTreeChanges()
|
||||
{
|
||||
if (_treeChangesRegistered) return;
|
||||
var sceneRoot = EditorInterface.Singleton.GetEditedSceneRoot();
|
||||
if (sceneRoot == null) return;
|
||||
var tree = sceneRoot.GetTree();
|
||||
tree.TreeChanged += OnTreeChanged;
|
||||
_treeChangesRegistered = true;
|
||||
}
|
||||
|
||||
private void UnregisterTreeChanges()
|
||||
{
|
||||
var sceneRoot = EditorInterface.Singleton.GetEditedSceneRoot();
|
||||
if (!_treeChangesRegistered || sceneRoot == null) return;
|
||||
var tree = sceneRoot.GetTree();
|
||||
tree.TreeChanged -= OnTreeChanged;
|
||||
SceneChanged -= OnSceneChanged;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,30 @@
|
||||
#if TOOLS
|
||||
using Godot;
|
||||
|
||||
namespace GodotHostTest.GodotDI.Editor;
|
||||
|
||||
// it's inside ServiceSource so that it can safely access nameof(_serviceScope)
|
||||
public partial class ScopeInspectorPlugin : EditorInspectorPlugin
|
||||
{
|
||||
public override bool _CanHandle(GodotObject @object)
|
||||
{
|
||||
var result = @object.IsServiceSource();
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool _ParseProperty(GodotObject @object, Variant.Type type, string name, PropertyHint hintType,
|
||||
string hintString,
|
||||
PropertyUsageFlags usageFlags, bool wide)
|
||||
{
|
||||
// We handle properties of type integer.
|
||||
if (name != nameof(ServiceSource.PropertyName._serviceScope)) return false;
|
||||
// Create an instance of the custom property editor and register
|
||||
// it to a specific property path.
|
||||
AddPropertyEditor(name, new ScopePickerEditor());
|
||||
// Inform the editor to remove the default property editor for
|
||||
// this property type.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1 @@
|
||||
uid://ca5k0qqats56p
|
||||
@@ -1,26 +0,0 @@
|
||||
#if TOOLS
|
||||
using Godot;
|
||||
|
||||
namespace GodotHostTest.GodotDI;
|
||||
|
||||
[Tool]
|
||||
public partial class GodotDI : EditorPlugin
|
||||
{
|
||||
private ServiceSource.ScopeInspectorPlugin? _plugin;
|
||||
|
||||
public override void _EnterTree()
|
||||
{
|
||||
GD.Print("Initializing Godot DI");
|
||||
_plugin = new ServiceSource.ScopeInspectorPlugin();
|
||||
AddInspectorPlugin(_plugin);
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
GD.Print("Deinitializing Godot DI");
|
||||
RemoveInspectorPlugin(_plugin);
|
||||
_plugin?.Dispose();
|
||||
_plugin = null;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,36 +0,0 @@
|
||||
#if TOOLS
|
||||
using Godot;
|
||||
using GodotHostTest.addons.godot_di;
|
||||
|
||||
namespace GodotHostTest.GodotDI;
|
||||
|
||||
public partial class ServiceSource
|
||||
{
|
||||
// it's inside ServiceSource so that it can safely access nameof(_serviceScope)
|
||||
public partial class ScopeInspectorPlugin : EditorInspectorPlugin
|
||||
{
|
||||
public override bool _CanHandle(GodotObject @object)
|
||||
{
|
||||
var result = @object.GetScript().AsGodotObject() is CSharpScript script &&
|
||||
GodotScriptPathCache.TryGetTypeFromPath(script.ResourcePath, out var type) &&
|
||||
type.IsAssignableTo(typeof(ServiceSource));
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool _ParseProperty(GodotObject @object, Variant.Type type, string name, PropertyHint hintType,
|
||||
string hintString,
|
||||
PropertyUsageFlags usageFlags, bool wide)
|
||||
{
|
||||
// We handle properties of type integer.
|
||||
if (name != nameof(_serviceScope)) return false;
|
||||
// Create an instance of the custom property editor and register
|
||||
// it to a specific property path.
|
||||
AddPropertyEditor(name, new ScopePickerEditor());
|
||||
// Inform the editor to remove the default property editor for
|
||||
// this property type.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user