feat: more plugin work and intra-scope communication
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
using GodotHostTest.Interfaces;
|
||||
|
||||
namespace GodotHostTest.Game;
|
||||
|
||||
public class CurrentLevel : ICurrentLevel
|
||||
{
|
||||
public int LevelNumber { get; private set; } = 1;
|
||||
|
||||
public void NextLevel()
|
||||
{
|
||||
LevelNumber++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Godot;
|
||||
using GodotHostTest.GodotDI;
|
||||
using GodotHostTest.Interfaces;
|
||||
using OwofGames.GodotLume;
|
||||
|
||||
namespace GodotHostTest.Game;
|
||||
|
||||
public partial class GameServiceSource : RootServiceSource
|
||||
{
|
||||
[Export] private LevelsData _levelsData = null!;
|
||||
|
||||
protected override void Configure(Builder builder)
|
||||
{
|
||||
builder
|
||||
.AddSingleton<ICurrentLevel, CurrentLevel>()
|
||||
.AddInstance<ILevelDataProvider, LevelsData>(_levelsData)
|
||||
.AddValueProvider<ILevel, RootScope, LevelScope>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using Godot;
|
||||
using GodotHostTest.Interfaces;
|
||||
|
||||
namespace GodotHostTest.Game;
|
||||
|
||||
[GlobalClass]
|
||||
public partial class LevelData : Resource, ILevel
|
||||
{
|
||||
[Export] public double TimeBetweenSpawns;
|
||||
|
||||
TimeSpan ILevel.TimeBetweenSpawns => TimeSpan.FromSeconds(TimeBetweenSpawns);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
uid://d18acsodp1s3i
|
||||
@@ -1,18 +1,16 @@
|
||||
using Godot;
|
||||
using GodotHostTest.GodotDI;
|
||||
using GodotHostTest.Interfaces;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace GodotHostTest.Game;
|
||||
|
||||
public partial class LevelServiceSource : RootServiceSource
|
||||
public partial class LevelServiceSource : ServiceSource
|
||||
{
|
||||
[Export] private TargetSpawner _targetSpawner = null!;
|
||||
|
||||
protected override void Configure(IServiceCollection serviceCollection)
|
||||
{
|
||||
serviceCollection
|
||||
.AddSingleton<ITargetCollector>(_targetSpawner)
|
||||
.AddSingleton<ITargetSpawner>(_targetSpawner);
|
||||
}
|
||||
// protected override void ConfigureServices(IServiceCollection serviceCollection)
|
||||
// {
|
||||
// serviceCollection
|
||||
// .AddSingleton<ITargetCollector>(_targetSpawner)
|
||||
// .AddSingleton<ITargetSpawner>(_targetSpawner);
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Godot;
|
||||
using GodotHostTest.Interfaces;
|
||||
|
||||
namespace GodotHostTest.Game;
|
||||
|
||||
[GlobalClass]
|
||||
public partial class LevelsData : Resource, ILevelDataProvider
|
||||
{
|
||||
[Export] private LevelData[] _levelDataEntries = [];
|
||||
|
||||
public ILevel GetLevelData(int levelNumber)
|
||||
{
|
||||
var entry = _levelDataEntries[levelNumber];
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
+6
-1
@@ -1,15 +1,20 @@
|
||||
using Godot;
|
||||
using GodotHostTest.GodotDI;
|
||||
using GodotHostTest.Interfaces;
|
||||
|
||||
namespace GodotHostTest.Game;
|
||||
|
||||
public partial class Root : Node2D
|
||||
{
|
||||
[Inject] private readonly ICurrentLevel _currentLevel = null!;
|
||||
[Inject] private readonly ILevelDataProvider _levelDataProvider = null!;
|
||||
[Inject] private readonly ISceneInstantiator _sceneInstantiator = null!;
|
||||
[Export] private PackedScene _levelScene = null!;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
AddChild(_sceneInstantiator.Instantiate(_levelScene));
|
||||
var levelData = _levelDataProvider.GetLevelData(_currentLevel.LevelNumber);
|
||||
AddChild(_sceneInstantiator.Instantiate(_levelScene,
|
||||
(IValueSetter<ILevel> levelSetter) => { levelSetter.Set(levelData); }));
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -71,7 +71,7 @@ _data = {
|
||||
|
||||
[node name="Level" type="Node2D" unique_id=1331632726]
|
||||
|
||||
[node name="RootScope" type="Node" parent="." unique_id=1267156771 node_paths=PackedStringArray("_targetSpawner", "InjectedNodes")]
|
||||
[node name="LevelScope" 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"
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace GodotHostTest.Interfaces;
|
||||
|
||||
public interface ICurrentLevel
|
||||
{
|
||||
public int LevelNumber { get; }
|
||||
public void NextLevel();
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace GodotHostTest.Interfaces;
|
||||
|
||||
public interface ILevel
|
||||
{
|
||||
public TimeSpan TimeBetweenSpawns { get; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace GodotHostTest.Interfaces;
|
||||
|
||||
public interface ILevelDataProvider
|
||||
{
|
||||
public ILevel GetLevelData(int levelNumber);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using OwofGames.GodotLume;
|
||||
|
||||
namespace GodotHostTest.GodotDI;
|
||||
|
||||
public static class BuilderExtensions
|
||||
{
|
||||
public static Builder AddValueProvider<TValueType, TSourceScope, TDestinationScope>(this Builder builder)
|
||||
where TSourceScope : RootScope
|
||||
where TDestinationScope : TSourceScope
|
||||
{
|
||||
var valueProvider = new ValueProvider<TValueType>();
|
||||
return builder
|
||||
.AddScoped<IValueSetter<TValueType>, IValueSetter<TValueType>, TSourceScope>(Builder.NoDependencies,
|
||||
_ => valueProvider.Setter)
|
||||
.AddScoped<IValueGetter<TValueType>, IValueGetter<TValueType>, TDestinationScope>(Builder.NoDependencies,
|
||||
_ => valueProvider.Getter);
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,7 @@ public partial class Plugin : EditorPlugin
|
||||
// mark debounce as completed
|
||||
_sceneTreeTimer = null;
|
||||
|
||||
|
||||
// check that the instance is still valid after debouncing (could have been disposed)
|
||||
if (!IsInstanceValid(sceneRoot)) return;
|
||||
|
||||
@@ -96,6 +97,7 @@ public partial class Plugin : EditorPlugin
|
||||
return;
|
||||
default:
|
||||
{
|
||||
if (!IsInstanceValid(serviceSource)) return;
|
||||
// find all nodes that need injection
|
||||
var nodesNeedingInjection = sceneNodes.Where(node =>
|
||||
{
|
||||
@@ -109,7 +111,7 @@ public partial class Plugin : EditorPlugin
|
||||
}).ToList();
|
||||
|
||||
// add all nodes needed to the scope
|
||||
var injectedNodes = serviceSource!.Get(ServiceSource.PropertyName.InjectedNodes).AsGodotArray();
|
||||
var injectedNodes = serviceSource.Get(ServiceSource.PropertyName.InjectedNodes).AsGodotArray();
|
||||
Array? newArray = null;
|
||||
foreach (var node in nodesNeedingInjection
|
||||
.Where(node => !injectedNodes.Contains(node)))
|
||||
@@ -137,7 +139,8 @@ public partial class Plugin : EditorPlugin
|
||||
var undoRedo = GetUndoRedo();
|
||||
undoRedo.CreateAction("Update injected nodes");
|
||||
undoRedo.AddDoProperty(serviceSource, ServiceSource.PropertyName.InjectedNodes, newArray);
|
||||
undoRedo.AddUndoProperty(serviceSource, ServiceSource.PropertyName.InjectedNodes, injectedNodes);
|
||||
undoRedo.AddUndoProperty(serviceSource, ServiceSource.PropertyName.InjectedNodes,
|
||||
new Array(injectedNodes));
|
||||
undoRedo.CommitAction();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Godot;
|
||||
|
||||
namespace GodotHostTest.GodotDI;
|
||||
@@ -5,16 +6,18 @@ namespace GodotHostTest.GodotDI;
|
||||
public interface ISceneInstantiator
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiate a packed scene, just like <see cref="PackedScene.Instantiate"/>, but also looks for a scope between the top level children and triggers the dependency injection mechanism when found.
|
||||
/// Instantiate a packed scene, just like <see cref="PackedScene.Instantiate"/>, but also looks for a scope between the top level children of the instantiated scene and triggers the dependency injection mechanism when found.
|
||||
/// </summary>
|
||||
/// <param name="packedScene">The packed scene to instantiate.</param>
|
||||
/// <param name="onScopeCreated">An optional callback invoked once the scope has been created, but before instantiating the scene.</param>
|
||||
/// <returns>The created node, with its dependencies satisfied.</returns>
|
||||
Node Instantiate(PackedScene packedScene);
|
||||
Node Instantiate(PackedScene packedScene, Delegate? onScopeCreated = null);
|
||||
|
||||
/// <summary>
|
||||
/// Instantiate a packed scene, just like <see cref="PackedScene.Instantiate{T}"/>, but also looks for a scope between the top level children and triggers the dependency injection mechanism when found.
|
||||
/// </summary>
|
||||
/// <param name="packedScene">The packed scene to instantiate.</param>
|
||||
/// <param name="onScopeCreated">An optional callback invoked once the scope has been created, but before instantiating the scene.</param>
|
||||
/// <returns>The created node, with its dependencies satisfied.</returns>
|
||||
T Instantiate<T>(PackedScene packedScene) where T : Node;
|
||||
T Instantiate<T>(PackedScene packedScene, Delegate? onScopeCreated = null) where T : Node;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace GodotHostTest.GodotDI;
|
||||
|
||||
public interface IValueGetter<out T>
|
||||
{
|
||||
public T Get();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace GodotHostTest.GodotDI;
|
||||
|
||||
public interface IValueSetter<in T>
|
||||
{
|
||||
public void Set(T value);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using Godot;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OwofGames.GodotHost;
|
||||
using OwofGames.GodotLume;
|
||||
using OwofGames.GodotLume.Microsoft.DependencyInjection;
|
||||
|
||||
namespace GodotHostTest.GodotDI;
|
||||
@@ -15,6 +16,8 @@ public abstract partial class RootServiceSource : ServiceSource, ISceneInstantia
|
||||
{
|
||||
private Host? _host;
|
||||
|
||||
private ILogger<RootServiceSource>? _logger;
|
||||
|
||||
private Host Host
|
||||
{
|
||||
get => _host ??
|
||||
@@ -23,57 +26,59 @@ public abstract partial class RootServiceSource : ServiceSource, ISceneInstantia
|
||||
set => _host = value;
|
||||
}
|
||||
|
||||
private ILogger<RootServiceSource>? _logger;
|
||||
|
||||
private ILogger<RootServiceSource> Logger => _logger ??= Host.GetLogger<RootServiceSource>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Node Instantiate(PackedScene packedScene, Delegate? onScopeCreated = null)
|
||||
{
|
||||
var node = packedScene.Instantiate();
|
||||
var scope = node.GetChildren().OfType<ServiceSource>().SingleOrDefault();
|
||||
if (scope == null)
|
||||
Logger.LogDebug(
|
||||
"Instantiated the scene {PackedSceneName} through ISceneInstantiator.Instantiate, but no scope found.",
|
||||
packedScene.GetName());
|
||||
else
|
||||
scope.ResolveInjectedNodes(Host.ServiceProvider, onScopeCreated);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T Instantiate<T>(PackedScene packedScene, Delegate? onScopeCreated = null)
|
||||
where T : Node
|
||||
{
|
||||
return (T)Instantiate(packedScene, onScopeCreated);
|
||||
}
|
||||
|
||||
public override void _EnterTree()
|
||||
{
|
||||
// create the host
|
||||
Host = new HostBuilder()
|
||||
.SetServiceCollectionBuilder(InnerConfigure)
|
||||
.SetServiceProviderFactory(new LumeServiceProviderFactory())
|
||||
.SetServiceProviderFactory(new LumeServiceProviderFactory(), Configure)
|
||||
.Build();
|
||||
|
||||
// resolve nodes
|
||||
ResolveInjectedNodes(Host.ServiceProvider);
|
||||
ResolveInjectedNodes(Host.ServiceProvider, null);
|
||||
}
|
||||
|
||||
private void InnerConfigure(IServiceCollection serviceCollection)
|
||||
{
|
||||
serviceCollection.AddSingleton<ISceneInstantiator>(this);
|
||||
Configure(serviceCollection);
|
||||
ConfigureServices(serviceCollection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure the service collection by adding the registrations your game needs.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder to enrich with your services.</param>
|
||||
protected abstract void Configure(Builder builder);
|
||||
|
||||
/// <summary>
|
||||
/// Configure the service collection by adding the registrations your game needs.
|
||||
/// </summary>
|
||||
/// <param name="serviceCollection">The service collection to enrich.</param>
|
||||
protected abstract void Configure(IServiceCollection serviceCollection);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Node Instantiate(PackedScene packedScene)
|
||||
protected virtual void ConfigureServices(IServiceCollection serviceCollection)
|
||||
{
|
||||
var node = packedScene.Instantiate();
|
||||
var scope = node.GetChildren().OfType<ServiceSource>().SingleOrDefault();
|
||||
if (scope == null)
|
||||
{
|
||||
Logger.LogDebug(
|
||||
"Instantiated the scene {PackedSceneName} through ISceneInstantiator.Instantiate, but no scope found.",
|
||||
packedScene.GetName());
|
||||
}
|
||||
else
|
||||
{
|
||||
scope.ResolveInjectedNodes(Host.ServiceProvider);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T Instantiate<T>(PackedScene packedScene)
|
||||
where T : Node
|
||||
{
|
||||
return (T)Instantiate(packedScene);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Godot;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OwofGames.GodotLume;
|
||||
|
||||
namespace GodotHostTest.GodotDI;
|
||||
@@ -13,18 +15,33 @@ public partial class ServiceSource : Node
|
||||
[Export] private string _serviceScope = RootScopeQualifiedName;
|
||||
[Export] protected Node?[] InjectedNodes = [];
|
||||
|
||||
internal void ResolveInjectedNodes(IServiceProvider serviceProvider)
|
||||
internal void ResolveInjectedNodes(IServiceProvider serviceProvider, Delegate? onScopeCreated)
|
||||
{
|
||||
var logger = serviceProvider.GetRequiredService<ILogger<ServiceSource>>();
|
||||
|
||||
// if this service source must also act as a scope, create the scope and use its service provider
|
||||
if (_serviceScope != RootScopeQualifiedName)
|
||||
{
|
||||
// create the scoped provider
|
||||
var serviceScope = ScopeTypesAndNames.GetType(_serviceScope);
|
||||
var scopedProvider = serviceProvider
|
||||
.GetRequiredService<IProvider>()
|
||||
.GetScopedProvider(serviceScope);
|
||||
// convert it also to an IServiceProvider
|
||||
var serviceProviderCreator = serviceProvider.GetRequiredService<Func<IProvider, IServiceProvider>>();
|
||||
var scopedProvider = serviceProvider.GetRequiredService<IProvider>()
|
||||
.GetScopedProvider(ScopeTypesAndNames.GetType(_serviceScope));
|
||||
serviceProvider = serviceProviderCreator(scopedProvider);
|
||||
// serviceProvider = serviceProvider
|
||||
// .CreateScope()
|
||||
// .ServiceProvider;
|
||||
// invoke onScopeCreated, if present
|
||||
// TODO: replace with a source code generation by turning this method private partial with Action<IProvider> argument, and every invocation site calls the necessary provider.Get<...> to build the argument list
|
||||
if (onScopeCreated != null)
|
||||
{
|
||||
var parameters = onScopeCreated.Method.GetParameters()
|
||||
.Select(parameterInfo => scopedProvider.Get(parameterInfo.ParameterType));
|
||||
onScopeCreated.DynamicInvoke(parameters);
|
||||
}
|
||||
}
|
||||
else if (onScopeCreated != null)
|
||||
{
|
||||
logger.LogWarning("onScopeCreated passed during instantiation, but no scoped provider was created.");
|
||||
}
|
||||
|
||||
// resolve the injected nodes against the chosen service provider
|
||||
@@ -32,6 +49,7 @@ public partial class ServiceSource : Node
|
||||
foreach (var node in InjectedNodes)
|
||||
{
|
||||
if (node == null) continue;
|
||||
logger.LogTrace("Injecting {Node}.", node.Name);
|
||||
var nodeType = node.GetType();
|
||||
foreach (var field in nodeType
|
||||
.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
|
||||
namespace GodotHostTest.GodotDI;
|
||||
|
||||
/// <summary>
|
||||
/// An object that allows communication through scopes, by providing a couple getter/setter that can be injected in
|
||||
/// different scopes.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type that's been passed through scopes.</typeparam>
|
||||
/// <seealso cref="BuilderExtensions.AddValueProvider" />
|
||||
public class ValueProvider<T>
|
||||
{
|
||||
private bool _isSet;
|
||||
private T? _value;
|
||||
|
||||
public ValueProvider()
|
||||
{
|
||||
Setter = new SetterImplementation(this);
|
||||
}
|
||||
|
||||
public IValueSetter<T> Setter { get; }
|
||||
|
||||
public IValueGetter<T> Getter => !_isSet
|
||||
? throw new InvalidOperationException($"Value of type {typeof(T)} has not been set yet.")
|
||||
: new GetterImplementation(_value!);
|
||||
|
||||
private class SetterImplementation(ValueProvider<T> valueProvider) : IValueSetter<T>
|
||||
{
|
||||
public void Set(T value)
|
||||
{
|
||||
if (valueProvider._isSet)
|
||||
throw new InvalidOperationException($"A value (of type {typeof(T)}) cannot be set more than once.");
|
||||
|
||||
valueProvider._value = value;
|
||||
valueProvider._isSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
private class GetterImplementation(T value) : IValueGetter<T>
|
||||
{
|
||||
public T Get()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
<s:Boolean x:Key="/Default/AddReferences/RecentPaths/=_002Fhome_002Fredglow_002Fsources_002FOwofGames_002EGodot_002FOwofGames_002EGodotLume_002EMicrosoft_002EDependencyInjection_002Fbin_002FDebug_002Fnet8_002E0_002FOwofGames_002EGodotLume_002EMicrosoft_002EDependencyInjection_002Edll/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/AddReferences/RecentPaths/=_002Fhome_002Fredglow_002Fsources_002FOwofGames_002EGodot_002FOwofGames_002EGodotLume_002Fbin_002FDebug_002Fnet8_002E0_002FOwofGames_002EGodotLume_002Edll/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnumerable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2026_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F71c28e8d0d254cb69b7b78690e099f6183800_003Fa8_003F2e82b19d_003FEnumerable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2026_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6cc9b871557b441baa469ce23b255f41dbb200_003F71_003F4ef26ee8_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2026_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F71c28e8d0d254cb69b7b78690e099f6183800_003F72_003F2da75270_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/Environment/Hierarchy/Build/BuildTool/DotNetCliExePath/@EntryValue">/usr/lib/dotnet/dotnet</s:String>
|
||||
<s:String x:Key="/Default/Environment/Hierarchy/Build/BuildTool/CustomBuildToolPath/@EntryValue">/usr/lib/dotnet/sdk/10.0.109/MSBuild.dll</s:String>
|
||||
|
||||
+7
-2
@@ -2,9 +2,14 @@
|
||||
|
||||
[ext_resource type="Script" uid="uid://bvjvojer872c8" path="res://Game/Root.cs" id="1_iqet6"]
|
||||
[ext_resource type="PackedScene" uid="uid://xfd48ep5qi1k" path="res://Game/level.tscn" id="3_bbjqm"]
|
||||
[ext_resource type="Script" uid="uid://bpes6mnv21f2v" path="res://Game/GameServiceSource.cs" id="3_iqet6"]
|
||||
[ext_resource type="Resource" uid="uid://c4ym7vrpgk4e6" path="res://Game/levels_data.tres" id="4_t2jh2"]
|
||||
|
||||
[node name="Node2D" type="Node2D" unique_id=920849082]
|
||||
[node name="Root" type="Node2D" unique_id=920849082]
|
||||
script = ExtResource("1_iqet6")
|
||||
_levelScene = ExtResource("3_bbjqm")
|
||||
|
||||
[node name="Level" parent="." unique_id=1331632726 instance=ExtResource("3_bbjqm")]
|
||||
[node name="Scope" type="Node" parent="." unique_id=597932533 node_paths=PackedStringArray("InjectedNodes")]
|
||||
script = ExtResource("3_iqet6")
|
||||
_levelsData = ExtResource("4_t2jh2")
|
||||
InjectedNodes = [NodePath("..")]
|
||||
|
||||
Reference in New Issue
Block a user