diff --git a/DungeonShooting_Godot/prefab/bullet/explode/Explode0001.tscn b/DungeonShooting_Godot/prefab/bullet/explode/Explode0001.tscn index c25913b..8980f34 100644 --- a/DungeonShooting_Godot/prefab/bullet/explode/Explode0001.tscn +++ b/DungeonShooting_Godot/prefab/bullet/explode/Explode0001.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=25 format=3 uid="uid://cw3c600m8rv6t"] +[gd_scene load_steps=26 format=3 uid="uid://cw3c600m8rv6t"] [ext_resource type="Texture2D" uid="uid://h7hkgbwj1li" path="res://resource/sprite/effects/common/Smoke.png" id="1_ctx3v"] [ext_resource type="Script" path="res://src/game/activity/bullet/explode/Explode.cs" id="1_qn5pu"] @@ -94,13 +94,17 @@ color_ramp = SubResource("GradientTexture1D_ftewy") anim_offset_max = 1.0 +[sub_resource type="CircleShape2D" id="CircleShape2D_isein"] +resource_local_to_scene = true + [sub_resource type="Animation" id="Animation_j6a2s"] resource_name = "play" +length = 4.0 step = 0.05 tracks/0/type = "value" tracks/0/imported = false tracks/0/enabled = true -tracks/0/path = NodePath("Sprite2D:visible") +tracks/0/path = NodePath("Circle:visible") tracks/0/interp = 1 tracks/0/loop_wrap = true tracks/0/keys = { @@ -112,7 +116,7 @@ tracks/1/type = "value" tracks/1/imported = false tracks/1/enabled = true -tracks/1/path = NodePath("Sprite2D:frame") +tracks/1/path = NodePath("Circle:frame") tracks/1/interp = 1 tracks/1/loop_wrap = true tracks/1/keys = { @@ -124,7 +128,7 @@ tracks/2/type = "value" tracks/2/imported = false tracks/2/enabled = true -tracks/2/path = NodePath("Sprite2D2:visible") +tracks/2/path = NodePath("Line:visible") tracks/2/interp = 1 tracks/2/loop_wrap = true tracks/2/keys = { @@ -136,7 +140,7 @@ tracks/3/type = "value" tracks/3/imported = false tracks/3/enabled = true -tracks/3/path = NodePath("Sprite2D2:frame") +tracks/3/path = NodePath("Line:frame") tracks/3/interp = 1 tracks/3/loop_wrap = true tracks/3/keys = { @@ -148,7 +152,7 @@ tracks/4/type = "value" tracks/4/imported = false tracks/4/enabled = true -tracks/4/path = NodePath("GPUParticles2D:emitting") +tracks/4/path = NodePath("ParticlesSmoke2:emitting") tracks/4/interp = 1 tracks/4/loop_wrap = true tracks/4/keys = { @@ -160,7 +164,7 @@ tracks/5/type = "value" tracks/5/imported = false tracks/5/enabled = true -tracks/5/path = NodePath("GPUParticles2D2:emitting") +tracks/5/path = NodePath("ParticlesFire:emitting") tracks/5/interp = 1 tracks/5/loop_wrap = true tracks/5/keys = { @@ -172,7 +176,7 @@ tracks/6/type = "value" tracks/6/imported = false tracks/6/enabled = true -tracks/6/path = NodePath("GPUParticles2D3:emitting") +tracks/6/path = NodePath("ParticlesSmoke:emitting") tracks/6/interp = 1 tracks/6/loop_wrap = true tracks/6/keys = { @@ -181,13 +185,25 @@ "update": 1, "values": [true] } +tracks/7/type = "value" +tracks/7/imported = false +tracks/7/enabled = true +tracks/7/path = NodePath("CollisionShape2D:disabled") +tracks/7/interp = 1 +tracks/7/loop_wrap = true +tracks/7/keys = { +"times": PackedFloat32Array(0, 0.2), +"transitions": PackedFloat32Array(1, 1), +"update": 1, +"values": [false, true] +} [sub_resource type="Animation" id="Animation_20asn"] length = 0.001 tracks/0/type = "value" tracks/0/imported = false tracks/0/enabled = true -tracks/0/path = NodePath("Sprite2D:visible") +tracks/0/path = NodePath("Circle:visible") tracks/0/interp = 1 tracks/0/loop_wrap = true tracks/0/keys = { @@ -199,7 +215,7 @@ tracks/1/type = "value" tracks/1/imported = false tracks/1/enabled = true -tracks/1/path = NodePath("Sprite2D:frame") +tracks/1/path = NodePath("Circle:frame") tracks/1/interp = 1 tracks/1/loop_wrap = true tracks/1/keys = { @@ -211,7 +227,7 @@ tracks/2/type = "value" tracks/2/imported = false tracks/2/enabled = true -tracks/2/path = NodePath("Sprite2D2:visible") +tracks/2/path = NodePath("Line:visible") tracks/2/interp = 1 tracks/2/loop_wrap = true tracks/2/keys = { @@ -223,7 +239,7 @@ tracks/3/type = "value" tracks/3/imported = false tracks/3/enabled = true -tracks/3/path = NodePath("Sprite2D2:frame") +tracks/3/path = NodePath("Line:frame") tracks/3/interp = 1 tracks/3/loop_wrap = true tracks/3/keys = { @@ -235,7 +251,7 @@ tracks/4/type = "value" tracks/4/imported = false tracks/4/enabled = true -tracks/4/path = NodePath("GPUParticles2D:emitting") +tracks/4/path = NodePath("ParticlesSmoke2:emitting") tracks/4/interp = 1 tracks/4/loop_wrap = true tracks/4/keys = { @@ -247,7 +263,7 @@ tracks/5/type = "value" tracks/5/imported = false tracks/5/enabled = true -tracks/5/path = NodePath("GPUParticles2D2:emitting") +tracks/5/path = NodePath("ParticlesFire:emitting") tracks/5/interp = 1 tracks/5/loop_wrap = true tracks/5/keys = { @@ -259,7 +275,7 @@ tracks/6/type = "value" tracks/6/imported = false tracks/6/enabled = true -tracks/6/path = NodePath("GPUParticles2D3:emitting") +tracks/6/path = NodePath("ParticlesSmoke:emitting") tracks/6/interp = 1 tracks/6/loop_wrap = true tracks/6/keys = { @@ -268,6 +284,18 @@ "update": 1, "values": [false] } +tracks/7/type = "value" +tracks/7/imported = false +tracks/7/enabled = true +tracks/7/path = NodePath("CollisionShape2D:disabled") +tracks/7/interp = 1 +tracks/7/loop_wrap = true +tracks/7/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 1, +"values": [false] +} [sub_resource type="AnimationLibrary" id="AnimationLibrary_p6l6x"] _data = { @@ -276,10 +304,13 @@ } [node name="Explode0001" type="Area2D"] -modulate = Color(1.4, 1.4, 1.4, 1) +modulate = Color(1.3, 1.3, 1.3, 1) +z_index = 1 +collision_layer = 0 +monitorable = false script = ExtResource("1_qn5pu") -[node name="GPUParticles2D3" type="GPUParticles2D" parent="."] +[node name="ParticlesSmoke" type="GPUParticles2D" parent="."] material = SubResource("CanvasItemMaterial_sk5lv") emitting = false process_material = SubResource("ParticleProcessMaterial_8sxfm") @@ -288,7 +319,7 @@ explosiveness = 0.6 randomness = 1.0 -[node name="GPUParticles2D" type="GPUParticles2D" parent="."] +[node name="ParticlesSmoke2" type="GPUParticles2D" parent="."] material = SubResource("CanvasItemMaterial_sk5lv") emitting = false amount = 10 @@ -298,7 +329,7 @@ explosiveness = 0.6 randomness = 1.0 -[node name="GPUParticles2D2" type="GPUParticles2D" parent="."] +[node name="ParticlesFire" type="GPUParticles2D" parent="."] material = SubResource("CanvasItemMaterial_sk5lv") emitting = false amount = 25 @@ -308,14 +339,17 @@ explosiveness = 0.7 randomness = 1.0 -[node name="Sprite2D" type="Sprite2D" parent="."] +[node name="Circle" type="Sprite2D" parent="."] texture = ExtResource("2_bpdnr") hframes = 7 -[node name="Sprite2D2" type="Sprite2D" parent="."] +[node name="Line" type="Sprite2D" parent="."] texture = ExtResource("4_i3ry2") hframes = 6 +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource("CircleShape2D_isein") + [node name="AnimationPlayer" type="AnimationPlayer" parent="."] libraries = { "": SubResource("AnimationLibrary_p6l6x") diff --git a/DungeonShooting_Godot/scene/World.tscn b/DungeonShooting_Godot/scene/World.tscn index 6abd441..76eca4e 100644 --- a/DungeonShooting_Godot/scene/World.tscn +++ b/DungeonShooting_Godot/scene/World.tscn @@ -1,7 +1,6 @@ -[gd_scene load_steps=4 format=3 uid="uid://bqf2vks5ggnsp"] +[gd_scene load_steps=3 format=3 uid="uid://bqf2vks5ggnsp"] [ext_resource type="Script" path="res://src/game/room/World.cs" id="1_kt3mm"] -[ext_resource type="PackedScene" uid="uid://cw3c600m8rv6t" path="res://prefab/bullet/explode/Explode0001.tscn" id="2_pofgj"] [sub_resource type="Environment" id="Environment_g06jj"] background_mode = 3 @@ -64,6 +63,3 @@ [node name="AffiliationAreaRoot" type="Node2D" parent="."] [node name="FogMaskRoot" type="Node2D" parent="."] - -[node name="Explode0001" parent="." instance=ExtResource("2_pofgj")] -position = Vector2(56, 43) diff --git a/DungeonShooting_Godot/src/framework/common/NodeExtend.cs b/DungeonShooting_Godot/src/framework/common/NodeExtend.cs index deee5c1..e9988f0 100644 --- a/DungeonShooting_Godot/src/framework/common/NodeExtend.cs +++ b/DungeonShooting_Godot/src/framework/common/NodeExtend.cs @@ -50,6 +50,16 @@ { GameApplication.Instance.World.GetRoomLayer(layer).AddChild(node); } + + /// + /// 将节点插入的房间物体根节点,延时调用 + /// + /// 实例 + /// 放入的层 + public static void AddToActivityRootDeferred(this Node2D node, RoomLayerEnum layer) + { + GameApplication.Instance.World.GetRoomLayer(layer).CallDeferred(Node.MethodName.AddChild, node); + } /// /// 设置Ui布局方式是否横向扩展, 如果为 true, 则 GridContainer 的宽度会撑满父物体 diff --git a/DungeonShooting_Godot/src/framework/pool/IPoolItem.cs b/DungeonShooting_Godot/src/framework/pool/IPoolItem.cs new file mode 100644 index 0000000..6cb897a --- /dev/null +++ b/DungeonShooting_Godot/src/framework/pool/IPoolItem.cs @@ -0,0 +1,23 @@ + +/// +/// 可被对象池池回收的实例对象接口 +/// +public interface IPoolItem : IDestroy +{ + /// + /// 是否已经回收 + /// + bool IsRecycled { get; set; } + /// + /// 对象唯一标识,用于在对象池中区分对象类型,可以是资源路径,也可以是配置表id + /// + string Logotype { get; set; } + /// + /// 当物体被回收时调用,也就是进入对象池 + /// + void OnReclaim(); + /// + /// + /// + void OnLeavePool(); +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/framework/pool/ObjectPool.cs b/DungeonShooting_Godot/src/framework/pool/ObjectPool.cs new file mode 100644 index 0000000..27f8e4c --- /dev/null +++ b/DungeonShooting_Godot/src/framework/pool/ObjectPool.cs @@ -0,0 +1,71 @@ + +using System.Collections.Generic; + +/// +/// 对象池,用于获取和回收常用对象,避免每次都创建一个新的 +/// +public static class ObjectPool +{ + private static Dictionary> _pool = new Dictionary>(); + + /// + /// 回收一个对象 + /// + public static void Reclaim(IPoolItem poolItem) + { + var logotype = poolItem.Logotype; + if (!_pool.TryGetValue(logotype, out var poolItems)) + { + poolItems = new Stack(); + _pool.Add(logotype, poolItems); + } + + poolItems.Push(poolItem); + poolItem.IsRecycled = true; + poolItem.OnReclaim(); + } + + /// + /// 根据标识从池中取出一个实例,如果没有该标识类型的实例,则返回null + /// + public static IPoolItem GetItem(string logotype) + { + if (_pool.TryGetValue(logotype, out var poolItems)) + { + if (poolItems.Count > 0) + { + var poolItem = poolItems.Pop(); + poolItem.IsRecycled = false; + poolItem.OnLeavePool(); + return poolItem; + } + } + + return null; + } + + /// + /// 根据标识从池中取出一个实例,如果没有该标识类型的实例,则返回null + /// + public static T GetItem(string logotype) where T : IPoolItem + { + return (T)GetItem(logotype); + } + + /// + /// 销毁所有池中的物体 + /// + public static void DisposeAllItem() + { + foreach (var keyValuePair in _pool) + { + var poolItems = keyValuePair.Value; + while (poolItems.Count > 0) + { + var item = poolItems.Pop(); + item.Destroy(); + } + } + _pool.Clear(); + } +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/GameApplication.cs b/DungeonShooting_Godot/src/game/GameApplication.cs index 0b57564..b5a7da5 100644 --- a/DungeonShooting_Godot/src/game/GameApplication.cs +++ b/DungeonShooting_Godot/src/game/GameApplication.cs @@ -168,6 +168,10 @@ /// public void DestroyWorld() { + //销毁池中所有物体 + ObjectPool.DisposeAllItem(); + + //销毁所有物体 if (World != null) { ClearWorld(); diff --git a/DungeonShooting_Godot/src/game/activity/bullet/explode/Explode.cs b/DungeonShooting_Godot/src/game/activity/bullet/explode/Explode.cs index c0fba47..b9c98bf 100644 --- a/DungeonShooting_Godot/src/game/activity/bullet/explode/Explode.cs +++ b/DungeonShooting_Godot/src/game/activity/bullet/explode/Explode.cs @@ -4,9 +4,41 @@ /// /// 爆炸 /// -public partial class Explode : Area2D, IDestroy +public partial class Explode : Area2D, IPoolItem { + public bool IsRecycled { get; set; } + public string Logotype { get; set; } + public bool IsDestroyed { get; private set; } + + /// + /// 动画播放器 + /// + public AnimationPlayer AnimationPlayer { get; private set; } + /// + /// 碰撞器 + /// + public CollisionShape2D CollisionShape { get; private set; } + /// + /// 碰撞器形状对象 + /// + public CircleShape2D CircleShape { get; private set; } + + /// + /// 爆炸攻击的层级 + /// + public uint AttackLayer { get; private set; } + /// + /// 最小伤害 + /// + public int MinHarm { get; private set; } + /// + /// 最大伤害 + /// + public int MaxHarm { get; private set; } + + private bool _init = false; + public void Destroy() { if (IsDestroyed) @@ -15,16 +47,60 @@ } IsDestroyed = true; + QueueFree(); + } + + public void Init(uint attackLayer, float radius, int minHarm, int maxHarm) + { + if (!_init) + { + _init = true; + AnimationPlayer = GetNode("AnimationPlayer"); + CollisionShape = GetNode("CollisionShape2D"); + CircleShape = (CircleShape2D)CollisionShape.Shape; + AnimationPlayer.AnimationFinished += OnAnimationFinish; + BodyEntered += OnBodyEntered; + } + + AttackLayer = attackLayer; + MinHarm = minHarm; + MaxHarm = maxHarm; + CollisionMask = attackLayer; + CircleShape.Radius = radius; } - public override void _Ready() + public void RunPlay() { GameCamera.Main.CreateShake(new Vector2(6, 6), 0.7f, true); - GetNode("AnimationPlayer").Play(AnimatorNames.Play); + AnimationPlayer.Play(AnimatorNames.Play); + } + + public void OnReclaim() + { + GetParent().RemoveChild(this); + } + + public void OnLeavePool() + { - this.CallDelayInNode(2, () => + } + + private void OnAnimationFinish(StringName name) + { + if (name == AnimatorNames.Play) { - Destroy(); - }); + ObjectPool.Reclaim(this); + } + } + + private void OnBodyEntered(Node2D node) + { + var role = node.AsActivityObject(); + if (role != null) + { + var angle = (role.Position - Position).Angle(); + role.CallDeferred(nameof(role.Hurt), Utils.Random.RandomRangeInt(MinHarm, MaxHarm), angle); + role.MoveController.AddForce(Vector2.FromAngle(angle) * 150, 300); + } } } \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/bullet/normal/Bullet.cs b/DungeonShooting_Godot/src/game/activity/bullet/normal/Bullet.cs index 0527b0c..fae59af 100644 --- a/DungeonShooting_Godot/src/game/activity/bullet/normal/Bullet.cs +++ b/DungeonShooting_Godot/src/game/activity/bullet/normal/Bullet.cs @@ -133,10 +133,12 @@ //击中爆炸,测试用 if (TriggerRole == null || !TriggerRole.IsAi) { - var loadAndInstantiate = ResourceManager.LoadAndInstantiate(ResourcePath.prefab_bullet_explode_Explode0001_tscn); - loadAndInstantiate.Position = Position; - loadAndInstantiate.RotationDegrees = Utils.Random.RandomRangeInt(0, 360); - GetParent().AddChild(loadAndInstantiate); + var explode = ObjectManager.GetExplode(ResourcePath.prefab_bullet_explode_Explode0001_tscn); + explode.Position = Position; + explode.RotationDegrees = Utils.Random.RandomRangeInt(0, 360); + explode.AddToActivityRoot(RoomLayerEnum.YSortLayer); + explode.Init(AttackLayer, 25, MinHarm, MaxHarm); + explode.RunPlay(); } Destroy(); @@ -185,10 +187,12 @@ //击中爆炸,测试用 if (TriggerRole == null || !TriggerRole.IsAi) { - var loadAndInstantiate = ResourceManager.LoadAndInstantiate(ResourcePath.prefab_bullet_explode_Explode0001_tscn); - loadAndInstantiate.Position = Position; - loadAndInstantiate.RotationDegrees = Utils.Random.RandomRangeInt(0, 360); - GetParent().AddChild(loadAndInstantiate); + var explode = ObjectManager.GetExplode(ResourcePath.prefab_bullet_explode_Explode0001_tscn); + explode.Position = Position; + explode.RotationDegrees = Utils.Random.RandomRangeInt(0, 360); + explode.AddToActivityRootDeferred(RoomLayerEnum.YSortLayer); + explode.Init(AttackLayer, 25, MinHarm, MaxHarm); + explode.RunPlay(); } Destroy(); diff --git a/DungeonShooting_Godot/src/game/activity/role/Role.cs b/DungeonShooting_Godot/src/game/activity/role/Role.cs index fcbf036..0342a63 100644 --- a/DungeonShooting_Godot/src/game/activity/role/Role.cs +++ b/DungeonShooting_Godot/src/game/activity/role/Role.cs @@ -983,7 +983,7 @@ /// 受到伤害, 如果是在碰撞信号处理函数中调用该函数, 请使用 CallDeferred 来延时调用, 否则很有可能导致报错 /// /// 伤害的量 - /// 角度 + /// 伤害角度(弧度制) public virtual void Hurt(int damage, float angle) { //受伤闪烁, 无敌状态 diff --git a/DungeonShooting_Godot/src/game/manager/ObjectManager.cs b/DungeonShooting_Godot/src/game/manager/ObjectManager.cs new file mode 100644 index 0000000..5338453 --- /dev/null +++ b/DungeonShooting_Godot/src/game/manager/ObjectManager.cs @@ -0,0 +1,15 @@ + +public static class ObjectManager +{ + public static Explode GetExplode(string resPath) + { + var explode = ObjectPool.GetItem(resPath); + if (explode == null) + { + explode = ResourceManager.LoadAndInstantiate(resPath); + explode.Logotype = resPath; + } + + return explode; + } +} \ No newline at end of file