diff --git a/Package/embedded/Miscs/SkinDyeMaterial.tres b/Package/embedded/Miscs/SkinDyeMaterial.tres new file mode 100644 index 0000000..fb71dca --- /dev/null +++ b/Package/embedded/Miscs/SkinDyeMaterial.tres @@ -0,0 +1,9 @@ +[gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://cegp8r6cj3jqj"] + +[ext_resource type="Shader" path="res://embedded/Miscs/SkinDyeShader.gdshader" id="1_31kjt"] + +[resource] +shader = ExtResource("1_31kjt") +shader_parameter/old_palette = null +shader_parameter/new_palette = null +shader_parameter/glowing = null diff --git a/Package/embedded/Miscs/SkinDyeShader.gdshader b/Package/embedded/Miscs/SkinDyeShader.gdshader new file mode 100644 index 0000000..71b8305 --- /dev/null +++ b/Package/embedded/Miscs/SkinDyeShader.gdshader @@ -0,0 +1,50 @@ +shader_type canvas_item; + +uniform vec4 old_palette[32]; +uniform vec4 new_palette[32]; +uniform int glowing[32]; +const float pe = 0.004; + +struct DyeResult{ + vec4 color; + int index; +}; + +DyeResult dye(vec4 color){ + for(int i = 0; i<32; i++){ + if(distance(color, old_palette[i]) < pe){ + return DyeResult(new_palette[i], i); + } + } + return DyeResult(color, -1); +} + +vec4 rgb2hsv(vec4 inp){ + vec3 c = inp.rgb; + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec4(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x, inp.a); +} + +vec4 hsv2rgb(vec4 inp){ + vec3 c = inp.xyz; + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return vec4( c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y), inp.a); +} + +void fragment() { + DyeResult dy = dye(texture(TEXTURE, UV)); + if(dy.index != -1 && glowing[dy.index] == 1){ + vec4 hsv = rgb2hsv(dy.color); + hsv.z += sin(TIME*1.5)*0.1; + COLOR = hsv2rgb(hsv); + } + else{ + COLOR = dy.color; + } + +} diff --git a/Package/embedded/Patches/GlobalRegistry.p.cs b/Package/embedded/Patches/GlobalRegistry.p.cs index 0d6d66e..965c79f 100644 --- a/Package/embedded/Patches/GlobalRegistry.p.cs +++ b/Package/embedded/Patches/GlobalRegistry.p.cs @@ -8,6 +8,7 @@ using Polonium.Attributes; using Polonium; using Polonium.Interfaces; using System.Collections.Generic; +using Polonium.Agents; // ReSharper disable once CheckNamespace public static partial class GlobalRegistry @@ -28,21 +29,18 @@ public static partial class GlobalRegistry public static class Asset where T : Node { - private static readonly Queue Pool = new(); // ReSharper disable once StaticMemberInGenericType // ReSharper disable once UnusedAutoPropertyAccessor.Global - public static PackedScene Scene { get; set; } - private static T Instance => Scene.Instantiate(); - public static T Get() => Pool.Count > 0 ? Pool.Dequeue() : Instance; - - public static void Return(T obj) - { - if(Pool.Count < 10) - Pool.Enqueue(obj); - else - obj.QueueFree(); - } + public static PackedScene Scene => PoloniumRegistry.Asset.Scene; + public static T Get() => PoloniumRegistry.Asset.Get(); + public static void Return(T obj) => PoloniumRegistry.Asset.Return(obj); } + + public static partial class Action where TAction : AgentAction + { + public static string Name => PoloniumRegistry.Action.Name; + } + public static PoloniumRegistry PoloniumRegistry => PoloniumRegistry.Instance; public static bool Paused { get; set; } public static void Prepare() => PoloniumRegistry.Prepare(); diff --git a/Package/embedded/Patches/SkinPaletteRegister.p.cs b/Package/embedded/Patches/SkinPaletteRegister.p.cs new file mode 100644 index 0000000..4f4ba35 --- /dev/null +++ b/Package/embedded/Patches/SkinPaletteRegister.p.cs @@ -0,0 +1,151 @@ +using System.Reflection; +using Godot; +using Polonium; +using Polonium.Attributes; +using Polonium.SkinManagers.Generic; +using System; +using System.Collections.Generic; +using System.Linq; +using Skin = Polonium.SkinManagers.Skin; + +[AutoRegister] +// ReSharper disable once CheckNamespace +public static class SkinPaletteRegister +{ + public static void Register() + { + Type gr = typeof(PoloniumRegistry.SkinRegistry<>); + Assembly assembly = Assembly.GetExecutingAssembly(); + IEnumerable skinTypes = assembly + .GetTypes() + .Where(t => t.IsSubclassOf(typeof(Skin)) && !t.IsAbstract) + .Where(t => t.GetCustomAttributes(typeof(SkinInfo), true).Any()); + + foreach (Type skinType in skinTypes) + { + Type ist = gr.MakeGenericType(skinType); + string pth = skinType.GetCustomAttribute()!.Path; + int k = skinType.GetCustomAttribute()!.Roots; + + PropertyInfo paletteRoots = ist.GetProperty("PaletteRoots"); + PropertyInfo paletteMap = ist.GetProperty("PaletteMap"); + Color[] roots; + Dictionary map; + if (IsAutoPalette(skinType)) + (roots, map) = KMean(ExtractColors(pth), k); + else + (roots, map) = GetPalette(pth); + paletteRoots!.SetValue(null, roots); + paletteMap!.SetValue(null, map); + } + } + private static bool IsAutoPalette(Type type) + { + Type x = type; + while (x != typeof(object) && x != null) + { + if (x.IsGenericType && x.GetGenericTypeDefinition() == typeof(AutoPaletteSkin<>)) + return true; + x = x.BaseType; + } + + return false; + } + + private static HashSet ExtractColors(string path) + { + HashSet colors = new(); + CompressedTexture2D texture = ResourceLoader.Load(path); + Image image = texture.GetImage(); + for (int x = 0; x < image.GetWidth(); x++) + for (int y = 0; y < image.GetHeight(); y++) + { + Color px = image.GetPixel(x, y); + if (px.A8 > 0) + colors.Add(px); + } + + return colors; + } + + private static (Color[], Dictionary) GetPalette(string path) + { + //HashSet colors = ExtractColors(path); + + HashSet colors = new(); + Dictionary> map = new(); + + CompressedTexture2D texture = ResourceLoader.Load(path); + Image image = texture.GetImage(); + int height = image.GetHeight(); + int width = image.GetWidth(); + for (int y = height - 32; y < height; y++) + { + Color px0 = image.GetPixel(width-32, y); + if (px0.A8 == 0) + break; + colors.Add(px0); + map[px0] = new HashSet { px0 }; + for (int x = width - 32; x < width; x++) + { + Color px = image.GetPixel(x, y); + if (px.A8 == 0) + break; + map[px0].Add(px); + } + } + + return (colors.ToArray(), map.ToDictionary(p => p.Key, p => p.Value.ToArray())); + } + private static (Color[], Dictionary) KMean(HashSet colors, int k) + { + var colorPoints = colors.ToList(); + var random = new Random(); + Color[] centroids = colorPoints.OrderBy(x => random.Next()).Take(k).ToArray(); + + bool hasConverged = false; + Dictionary> clusters = new(); + + while (!hasConverged) + { + clusters = centroids.ToDictionary(c => c, c => new List()); + foreach (Color color in colorPoints) + { + Color nearestCentroid = centroids.OrderBy(c => c.DistanceTo(color)).First(); + clusters[nearestCentroid].Add(color); + } + hasConverged = true; + for (int i = 0; i < centroids.Length; i++) + { + if (clusters[centroids[i]].Count == 0) continue; + + Color newCentroid = clusters[centroids[i]].AverageColor(); + if (!newCentroid.Equals(centroids[i])) + { + centroids[i] = newCentroid; + hasConverged = false; + } + } + } + + Dictionary resultMap = clusters.ToDictionary( + kvp => kvp.Key, + kvp => kvp.Value.ToArray() + ); + + return (centroids, resultMap); + } + + private static Vector4 ToVector(this Color a) => new(a.R, a.G, a.B, a.A); + private static double DistanceTo(this Color a, Color b) => a.ToVector().DistanceTo(b.ToVector()); + private static Color ToColor(this Vector4 a) => new Color(a.X, a.Y, a.Z, a.W); + + private static Color AverageColor(this IEnumerable a) + { + Vector4[] aa = a.Select(x => x.ToVector()).ToArray(); + Vector4 res = Vector4.Zero; + foreach (Vector4 c in aa) + res += c; + return (res / aa.Length).ToColor(); + } +} \ No newline at end of file diff --git a/src/Agents/ActionSet.cs b/src/Agents/ActionSet.cs index 2efbbda..7fb5f2e 100644 --- a/src/Agents/ActionSet.cs +++ b/src/Agents/ActionSet.cs @@ -5,9 +5,9 @@ namespace Polonium.Agents; public abstract partial class ActionSet : Node { - [Signal] - public delegate void ActionSubmittedEventHandler(ActionBody action); - + + public HashSet Templates { get; set; } = new(); + public virtual void Learn(AgentAction.Template template) => Templates.Add(template); } diff --git a/src/Agents/Agent.cs b/src/Agents/Agent.cs index 471f87a..da84ea1 100644 --- a/src/Agents/Agent.cs +++ b/src/Agents/Agent.cs @@ -15,7 +15,6 @@ public abstract partial class Agent : Node { Knowledge = GetNode("Knowledge"); ActionSet = GetNode("ActionSet"); - ActionSet.ActionSubmitted += ExecuteAction; } public sealed override void _Ready() @@ -34,6 +33,14 @@ public abstract partial class Agent : Node header.SetSender(this); EmitSignalActionExecuted(header, actionBody); } - - + + public void Act(AgentAction.Parameter parameter) + where TAction : AgentAction + { + AgentAction.Template template = ActionSet.Templates + .FirstOrDefault(t => t.ActionName == PoloniumRegistry.Action.Name); + + } + + } diff --git a/src/Agents/AgentAction.cs b/src/Agents/AgentAction.cs new file mode 100644 index 0000000..b8f70e4 --- /dev/null +++ b/src/Agents/AgentAction.cs @@ -0,0 +1,60 @@ +using Godot; + +namespace Polonium.Agents; + +public abstract partial class AgentAction : Node +{ + public abstract class Parameter + { + } + + public virtual void SetParameter(Parameter p) + { + } + + public abstract void Execute(); + + public abstract void Finish(); + + public abstract void Reset(); + + public abstract class Template : PoloniumTemplate + { + public abstract string ActionName { get; } + public abstract Template Copy { get; } + } + + public class Template : Template + where TAction : AgentAction + { + public override string ActionName => PoloniumRegistry.Action.Name; + + public override TAction Get + { + get + { + TAction res = PoloniumRegistry.Asset.Get(); + res.Reset(); + Modify(res); + return res; + } + } + + public override void Modify(AgentAction obj) + { + if (obj is not TAction act) + return; + Modify(act); + } + + public void Return(TAction res) => PoloniumRegistry.Asset.Return(res); + + public override Template Copy => new Template { }; + + public virtual void Modify(TAction action) + { + } + } +} + + diff --git a/src/Agents/Knowledge.cs b/src/Agents/Knowledge.cs index 6a51d94..335d6f9 100644 --- a/src/Agents/Knowledge.cs +++ b/src/Agents/Knowledge.cs @@ -1,8 +1,12 @@ using Godot; +using Polonium.Resources; namespace Polonium.Agents; public abstract partial class Knowledge : Node { - -} \ No newline at end of file + [Signal] + public delegate void KnowledgeUpdatedEventHandler(KnowledgePatch update); + public abstract void UpdateKnowledge(KnowledgePatch update); +} + diff --git a/src/Agents/World.cs b/src/Agents/World.cs index e3fa191..53e6c01 100644 --- a/src/Agents/World.cs +++ b/src/Agents/World.cs @@ -5,7 +5,7 @@ namespace Polonium.Agents; public abstract partial class World : Node { - private WorldModel Model { get; set; } + protected WorldModel Model { get; set; } public HashSet Agents { get; } = new(); public Knowledge CommonKnowledge { get; set; } diff --git a/src/Agents/WorldModel.cs b/src/Agents/WorldModel.cs index b5a7794..d578347 100644 --- a/src/Agents/WorldModel.cs +++ b/src/Agents/WorldModel.cs @@ -11,7 +11,7 @@ public abstract partial class WorldModel : Node [Signal] public delegate void PrivateKnowledgeUpdatedEventHandler(SignalHeader header, KnowledgePatch update); - private Knowledge WorldKnowledge { get; set; } + protected Knowledge WorldKnowledge { get; set; } public override void _Ready() { diff --git a/src/Attributes/SkinInfo.cs b/src/Attributes/SkinInfo.cs new file mode 100644 index 0000000..2c6d239 --- /dev/null +++ b/src/Attributes/SkinInfo.cs @@ -0,0 +1,8 @@ +namespace Polonium.Attributes; +[AttributeUsage(AttributeTargets.Class)] +public class SkinInfo(string path, int roots = 1) : Attribute +{ + public string Path { get; set; } = path; + public int Roots { get; set; } = roots; + +} diff --git a/src/ItemManagers/Generic/SkinItem.cs b/src/ItemManagers/Generic/SkinItem.cs new file mode 100644 index 0000000..371f883 --- /dev/null +++ b/src/ItemManagers/Generic/SkinItem.cs @@ -0,0 +1,33 @@ +using Godot; +using Polonium.SkinManagers.Generic; + +namespace Polonium.ItemManagers.Generic; + +public abstract partial class SkinItem : SkinItem + where TItem : SkinItem + where TSkin : Skin +{ + public new TSkin Skin + { + get => base.Skin as TSkin; + set => base.Skin = value; + } + + public new abstract class Template : SkinItem.Template + { + public Dictionary DyeInfo { get; set; } = new(); + public override SkinItem Get => GenericGet(); + + public virtual void Return(TItem item) => GenericReturn(item); + + public override void Modify(SkinItem obj) + { + foreach (int key in DyeInfo.Keys) + { + (Color c, bool g) = DyeInfo[key]; + obj.Skin.Dye(key, c, g); + } + } + } + +} \ No newline at end of file diff --git a/src/ItemManagers/Item.cs b/src/ItemManagers/Item.cs new file mode 100644 index 0000000..e09b041 --- /dev/null +++ b/src/ItemManagers/Item.cs @@ -0,0 +1,36 @@ +using Godot; + +namespace Polonium.ItemManagers; + +public abstract partial class Item : Node +{ + public Texture2D ItemIcon { get; set; } + public abstract class Template : PoloniumTemplate + { + } + + public class Template : Template + where TItem : Item + { + public override Item Get + { + get + { + TItem res = PoloniumRegistry.Asset.Get(); + Modify(res); + return res; + } + } + + public override void Modify(Item obj) + { + if (obj is not TItem item) + return; + Modify(item); + } + + public virtual void Modify(TItem res) + { + } + } +} diff --git a/src/ItemManagers/SkinItem.cs b/src/ItemManagers/SkinItem.cs new file mode 100644 index 0000000..c0860b9 --- /dev/null +++ b/src/ItemManagers/SkinItem.cs @@ -0,0 +1,53 @@ +using Polonium.SkinManagers; + +namespace Polonium.ItemManagers; + +public abstract partial class SkinItem : Item +{ + public Skin Skin { get; set; } + + public virtual void Reset() + { + } + + public new abstract class Template : PoloniumTemplate + { + protected SkinItem Instance { get; set; } + + protected virtual TItem GenericGet() + where TItem : SkinItem + where TSkin : Skin + { + TItem res = PoloniumRegistry.Asset.Get(); + res.Reset(); + res.Skin = GetSkin(); + Modify(res); + return res; + } + + protected virtual void GenericReturn(TItem item) + where TItem : SkinItem + where TSkin : Skin + { + ReturnSkin(item.Skin as TSkin); + item.Skin = null; + PoloniumRegistry.Asset.Return(item); + } + + protected virtual TSkin GetSkin() + where TSkin : Skin + { + TSkin res = PoloniumRegistry.Asset.Get(); + res.Init(); + return res; + } + + protected virtual void ReturnSkin(TSkin skin) + where TSkin : Skin + { + PoloniumRegistry.Asset.Return(skin); + } + + } + +} diff --git a/src/PoloniumRegistry.cs b/src/PoloniumRegistry.cs index 04bbd43..0f7a386 100644 --- a/src/PoloniumRegistry.cs +++ b/src/PoloniumRegistry.cs @@ -32,6 +32,39 @@ public class PoloniumRegistry // ReSharper disable once CollectionNeverQueried.Global [RegistryPassThrough(true)] public HashSet TimeConsumers { get; } = new(); - + + public static class Action + where TAction : AgentAction + { + // ReSharper disable once StaticMemberInGenericType + public static string Name { get; set; } + } + + + public static class SkinRegistry + where TSkin : Polonium.SkinManagers.Skin + { + // ReSharper disable once StaticMemberInGenericType + public static Color[] PaletteRoots { get; set; } + // ReSharper disable once StaticMemberInGenericType + public static Dictionary PaletteMap { get; set; } + } + + public static class Asset + where TAsset : Node + { + private static readonly Queue Pool = new(); + public static PackedScene Scene { get; set; } + private static TAsset Instance => Scene.Instantiate(); + public static TAsset Get() => Pool.Count > 0 ? Pool.Dequeue() : Instance; + + public static void Return(TAsset asset) + { + if (Pool.Count < 10) + Pool.Enqueue(asset); + else + asset.QueueFree(); + } + } } diff --git a/src/PoloniumTemplate.cs b/src/PoloniumTemplate.cs new file mode 100644 index 0000000..c39adaa --- /dev/null +++ b/src/PoloniumTemplate.cs @@ -0,0 +1,7 @@ +namespace Polonium; + +public abstract class PoloniumTemplate +{ + public abstract TObj Get { get; } + public abstract void Modify(TObj obj); +} \ No newline at end of file diff --git a/src/Resources/SignalHeader.cs b/src/Resources/SignalHeader.cs index 468d298..9189a00 100644 --- a/src/Resources/SignalHeader.cs +++ b/src/Resources/SignalHeader.cs @@ -6,7 +6,7 @@ namespace Polonium.Resources; public abstract partial class SignalHeader : Resource { - public abstract Agent[] ResolveReceivers(); + public abstract IEnumerable ResolveReceivers(); public abstract Agent ResolveSender(); public abstract void SetSender(Agent agent); diff --git a/src/SkinManagers/Generic/AutoPaletteSkin.cs b/src/SkinManagers/Generic/AutoPaletteSkin.cs new file mode 100644 index 0000000..832a0d8 --- /dev/null +++ b/src/SkinManagers/Generic/AutoPaletteSkin.cs @@ -0,0 +1,8 @@ +namespace Polonium.SkinManagers.Generic; + +public abstract partial class AutoPaletteSkin : Skin + where TSkin : AutoPaletteSkin +{ + protected override int Columns => 1; + protected override int Rows => 1; +} \ No newline at end of file diff --git a/src/SkinManagers/Generic/Skin.cs b/src/SkinManagers/Generic/Skin.cs new file mode 100644 index 0000000..9ebd7c6 --- /dev/null +++ b/src/SkinManagers/Generic/Skin.cs @@ -0,0 +1,12 @@ + +using Godot; + +namespace Polonium.SkinManagers.Generic; + +public abstract partial class Skin : Skin + where TSkin : Skin +{ + protected override int RootCount => PaletteRoots.Length; + protected override Color[] PaletteRoots => PoloniumRegistry.SkinRegistry.PaletteRoots; + protected override Dictionary PaletteMap => PoloniumRegistry.SkinRegistry.PaletteMap; +} \ No newline at end of file diff --git a/src/SkinManagers/Skin.cs b/src/SkinManagers/Skin.cs new file mode 100644 index 0000000..1af2f2b --- /dev/null +++ b/src/SkinManagers/Skin.cs @@ -0,0 +1,112 @@ +using Godot; + +namespace Polonium.SkinManagers; + +public abstract partial class Skin : Sprite2D +{ + protected abstract int Columns { get; } + protected abstract int Rows { get; } + + protected virtual string ShaderMaterialPath => "res://embedded/Miscs/SkinDyeMaterial.tres"; + + protected ShaderMaterial ShaderMaterial + { + get => Material as ShaderMaterial; + set => Material = value; + } + + protected abstract Color[] PaletteRoots { get; } + + protected abstract Dictionary PaletteMap { get; } + protected Color[] DyeMap { get; set; } + protected int[] GlowingMap { get; set; } + protected abstract int RootCount { get; } + + private struct DyeInfo + { + public Color[] ColorFrom; + public Color[] ColorTo; + public int[] Glowing; + } + + + private DyeInfo FullDyePair + { + get + { + List colorFrom = new List(); + List colorTo = new List(); + List glowing = new List(); + for (int i = 0; i < PaletteRoots.Length; i++) + { + Color rootSource = PaletteRoots[i]; + Color rootTarget = DyeMap[i]; + int isGlowing = GlowingMap[i]; + foreach (Color c in PaletteMap[rootSource]) + { + colorFrom.Add(c); + float h = c.H + rootTarget.H - rootSource.H; + if (h > 0) + h -= 1; + if (h < 0) + h += 1; + float s = rootSource.S > 0 + ? Math.Max(0, Math.Min(1, c.S * (rootTarget.S / rootSource.S))) + : Math.Max(0, Math.Min(1, rootTarget.S + (c.S - rootSource.S))); + + float v = rootSource.V > 0 + ? Math.Max(0, Math.Min(1, c.V * (rootTarget.V / rootSource.V))) + : Math.Max(0, Math.Min(1, rootTarget.V + (c.V - rootSource.V))); + colorTo.Add(Color.FromHsv(h, s, v, rootTarget.A)); + glowing.Add(isGlowing); + } + + } + while (colorFrom.Count < 32) + { + colorFrom.Add(Color.Color8(1, 2, 3, 4)); + colorTo.Add(Color.Color8(1, 2, 3, 4)); + glowing.Add(0); + } + + return new DyeInfo(){ ColorFrom = colorFrom.ToArray(), ColorTo = colorTo.ToArray(), Glowing = glowing.ToArray() }; + } + } + + public virtual void Init() + { + Hframes = Columns; + Vframes = Rows; + if (DyeMap is null || DyeMap.Length != PaletteRoots.Length) + { + DyeMap = new Color[PaletteRoots.Length]; + GlowingMap = new int[PaletteRoots.Length]; + for (int i = 0; i < PaletteRoots.Length; i++) + { + DyeMap[i] = PaletteRoots[i]; + GlowingMap[i] = 0; + } + } + + ShaderMaterial = ResourceLoader + .Load(ShaderMaterialPath) + .Duplicate() as ShaderMaterial; + Rerender(); + } + + private void Rerender() + { + DyeInfo info = FullDyePair; + ShaderMaterial.SetShaderParameter("old_palette", info.ColorFrom); + ShaderMaterial.SetShaderParameter("new_palette", info.ColorTo); + ShaderMaterial.SetShaderParameter("glowing", info.Glowing); + } + + public void Dye(int rootIdx, Color target, bool glowing = false) + { + DyeMap[rootIdx] = target; + GlowingMap[rootIdx] = glowing ? 1 : 0; + Rerender(); + } + +} \ No newline at end of file diff --git a/src/SkinManagers/SkinPacker.cs b/src/SkinManagers/SkinPacker.cs new file mode 100644 index 0000000..583022b --- /dev/null +++ b/src/SkinManagers/SkinPacker.cs @@ -0,0 +1,43 @@ +using Godot; + +namespace Polonium.SkinManagers; + +public abstract partial class SkinPacker : Node2D +{ + public Node2D SkinCollector { get; set; } + protected Skin[] CollectedSkins => SkinCollector + .GetChildren() + .OfType() + .ToArray(); + + private Vector2I BackingFrameCoords { get; set; } + + [Export] + protected Vector2I FrameCoords + { + get => BackingFrameCoords; + set + { + BackingFrameCoords = value; + UpdateSkins(); + } + } + + private void UpdateSkins() + { + if (SkinCollector is not null) + foreach(Skin skin in CollectedSkins) + skin.FrameCoords = FrameCoords; + } + + public AnimationPlayer SyncPlayer { get; set; } + + public override void _Ready() + { + base._Ready(); + SyncPlayer = GetNode("AnimationPlayer"); + SkinCollector = GetNode("SkinCollector"); + } + + +} \ No newline at end of file