diff --git a/DungeonShooting_Godot/prefab/role/Role.tscn b/DungeonShooting_Godot/prefab/role/Role.tscn index d1e173b..28ad803 100644 --- a/DungeonShooting_Godot/prefab/role/Role.tscn +++ b/DungeonShooting_Godot/prefab/role/Role.tscn @@ -129,6 +129,7 @@ position = Vector2( 0, -12 ) frames = SubResource( 6 ) animation = "idle" +frame = 1 playing = true [node name="Collision" type="CollisionShape2D" parent="."] diff --git a/DungeonShooting_Godot/prefab/weapon/bullet/Bullet.tscn b/DungeonShooting_Godot/prefab/weapon/bullet/Bullet.tscn index cc8610a..5b9b099 100644 --- a/DungeonShooting_Godot/prefab/weapon/bullet/Bullet.tscn +++ b/DungeonShooting_Godot/prefab/weapon/bullet/Bullet.tscn @@ -13,19 +13,26 @@ } ] [sub_resource type="RectangleShape2D" id=2] -extents = Vector2( 5.5, 2 ) +extents = Vector2( 6, 2 ) [node name="Bullet" type="Node"] script = ExtResource( 1 ) -CollisionLayer = 2 [node name="ShadowSprite" type="Sprite" parent="."] material = ExtResource( 2 ) [node name="AnimatedSprite" type="AnimatedSprite" parent="."] -position = Vector2( -1, 0 ) +modulate = Color( 1.5, 1.5, 1.5, 1 ) +position = Vector2( 2.38419e-07, 0 ) frames = SubResource( 1 ) [node name="Collision" type="CollisionShape2D" parent="."] position = Vector2( 1.5, 0 ) + +[node name="CollisionArea" type="Area2D" parent="."] +collision_layer = 0 +collision_mask = 0 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="CollisionArea"] +position = Vector2( 2, 0 ) shape = SubResource( 2 ) diff --git a/DungeonShooting_Godot/project.godot b/DungeonShooting_Godot/project.godot index acd52e2..e1ec7c8 100644 --- a/DungeonShooting_Godot/project.godot +++ b/DungeonShooting_Godot/project.godot @@ -26,6 +26,7 @@ window/size/width=1920 window/size/height=1080 +window/size/resizable=false window/dpi/allow_hidpi=true window/vsync/use_vsync=false window/stretch/mode="2d" diff --git a/DungeonShooting_Godot/resource/sprite/bullet/bullet2.png b/DungeonShooting_Godot/resource/sprite/bullet/bullet2.png new file mode 100644 index 0000000..3cea799 --- /dev/null +++ b/DungeonShooting_Godot/resource/sprite/bullet/bullet2.png Binary files differ diff --git a/DungeonShooting_Godot/resource/sprite/bullet/bullet2.png.import b/DungeonShooting_Godot/resource/sprite/bullet/bullet2.png.import new file mode 100644 index 0000000..b2003cd --- /dev/null +++ b/DungeonShooting_Godot/resource/sprite/bullet/bullet2.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/bullet2.png-6ca9527061677971732c8192cb1aa209.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resource/sprite/bullet/bullet2.png" +dest_files=[ "res://.import/bullet2.png-6ca9527061677971732c8192cb1aa209.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=false +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +process/normal_map_invert_y=false +stream=false +size_limit=0 +detect_3d=false +svg/scale=1.0 diff --git a/DungeonShooting_Godot/scene/Room.tscn b/DungeonShooting_Godot/scene/Room.tscn index 9f664bf..a9137ae 100644 --- a/DungeonShooting_Godot/scene/Room.tscn +++ b/DungeonShooting_Godot/scene/Room.tscn @@ -1,12 +1,19 @@ -[gd_scene load_steps=4 format=2] +[gd_scene load_steps=5 format=2] [ext_resource path="res://resource/map/dungeon_test.tmx" type="PackedScene" id=2] [ext_resource path="res://src/game/room/RoomManager.cs" type="Script" id=3] [ext_resource path="res://src/game/camera/GameCamera.cs" type="Script" id=5] +[sub_resource type="Environment" id=1] +background_mode = 4 +glow_enabled = true + [node name="Room" type="Node2D"] script = ExtResource( 3 ) +[node name="WorldEnvironment" type="WorldEnvironment" parent="."] +environment = SubResource( 1 ) + [node name="MapRoot" type="Node2D" parent="."] z_index = -10 @@ -15,7 +22,6 @@ [node name="Camera2D" type="Camera2D" parent="."] position = Vector2( 253, 219 ) current = true -process_mode = 0 limit_smoothed = true editor_draw_drag_margin = true script = ExtResource( 5 ) diff --git a/DungeonShooting_Godot/src/framework/ActivityObject.cs b/DungeonShooting_Godot/src/framework/ActivityObject.cs index 6416fd1..4de5ab2 100644 --- a/DungeonShooting_Godot/src/framework/ActivityObject.cs +++ b/DungeonShooting_Godot/src/framework/ActivityObject.cs @@ -339,10 +339,15 @@ public void RemoveComponent(Component component) { - if (ContainsComponent(component)) + for (int i = 0; i < _components.Count; i++) { - component.OnUnMount(); - component._SetActivityObject(null); + if (_components[i].Value == component) + { + _components.RemoveAt(i); + component.OnUnMount(); + component._SetActivityObject(null); + return; + } } } @@ -381,10 +386,10 @@ { if (!temp.IsStart) { - temp.Start(); + temp.Ready(); } - temp.Update(delta); + temp.Process(delta); } } } @@ -509,10 +514,10 @@ { if (!temp.IsStart) { - temp.Start(); + temp.Ready(); } - temp.PhysicsUpdate(delta); + temp.PhysicsProcess(delta); } } } @@ -547,6 +552,7 @@ CallDeferred(nameof(Destroy)); } + //返回该组件是否被挂载到当前物体上 private bool ContainsComponent(Component component) { for (int i = 0; i < _components.Count; i++) diff --git a/DungeonShooting_Godot/src/framework/Component.cs b/DungeonShooting_Godot/src/framework/Component.cs index 3e1d828..16c81e0 100644 --- a/DungeonShooting_Godot/src/framework/Component.cs +++ b/DungeonShooting_Godot/src/framework/Component.cs @@ -30,6 +30,69 @@ } /// + /// 当前组件所挂载物体的缩放 + /// + public Vector2 Scale + { + get => ActivityObject.Scale; + set => ActivityObject.Scale = value; + } + + /// + /// 当前组件所挂载物体的全局缩放 + /// + public Vector2 GlobalScale + { + get => ActivityObject.GlobalScale; + set => ActivityObject.GlobalScale = value; + } + + /// + /// 当前组件所挂载物体的旋转角度 + /// + public float Rotation + { + get => ActivityObject.Rotation; + set => ActivityObject.Rotation = value; + } + + /// + /// 当前组件所挂载物体的全局旋转角度 + /// + public float GlobalRotation + { + get => ActivityObject.GlobalRotation; + set => ActivityObject.GlobalRotation = value; + } + + /// + /// 当前组件所挂载物体的角度制旋转角度 + /// + public float RotationDegrees + { + get => ActivityObject.RotationDegrees; + set => ActivityObject.RotationDegrees = value; + } + + /// + /// 当前组件所挂载物体的全局角度制旋转角度 + /// + public float GlobalRotationDegrees + { + get => ActivityObject.GlobalRotationDegrees; + set => ActivityObject.GlobalRotationDegrees = value; + } + + /// + /// 当前组件所挂载物体的ZIndex + /// + public int ZIndex + { + get => ActivityObject.ZIndex; + set => ActivityObject.ZIndex = value; + } + + /// /// 当前组件是否显示 /// public bool Visible @@ -52,9 +115,27 @@ public CollisionShape2D Collision => ActivityObject.Collision; /// - /// 是否启用当前组件 + /// 是否启用当前组件, 如果禁用, 则不会调用 Process 和 PhysicsProcess /// - public bool Enable { get; set; } = true; + public bool Enable + { + get => _enable; + set + { + if (!_enable && value) + { + OnEnable(); + } + else if (_enable && !value) + { + OnDisable(); + } + + _enable = value; + } + } + + private bool _enable = true; /// /// 是否被销毁 @@ -65,25 +146,25 @@ internal bool IsStart = false; /// - /// 第一次调用 Update 或 PhysicsUpdate 之前调用 + /// 第一次调用 Process 或 PhysicsProcess 之前调用 /// - public virtual void Start() + public virtual void Ready() { } /// - /// 如果启用了组件, 则每帧会调用一次 Update + /// 如果启用了组件, 则每帧会调用一次 Process /// /// - public virtual void Update(float delta) + public virtual void Process(float delta) { } /// - /// 如果启用了组件, 则每物理帧会调用一次 PhysicsUpdate + /// 如果启用了组件, 则每物理帧会调用一次 PhysicsProcess /// /// - public virtual void PhysicsUpdate(float delta) + public virtual void PhysicsProcess(float delta) { } @@ -109,6 +190,20 @@ } /// + /// 当组件启用时调用 + /// + public virtual void OnEnable() + { + } + + /// + /// 当组件禁用时调用 + /// + public virtual void OnDisable() + { + } + + /// /// 当组件销毁 /// public void Destroy() diff --git a/DungeonShooting_Godot/src/framework/IProcess.cs b/DungeonShooting_Godot/src/framework/IProcess.cs index bc64da8..3e5777f 100644 --- a/DungeonShooting_Godot/src/framework/IProcess.cs +++ b/DungeonShooting_Godot/src/framework/IProcess.cs @@ -7,10 +7,10 @@ /// /// 普通帧每帧调用 /// - void Update(float delta); + void Process(float delta); /// /// 物理帧每帧调用 /// - void PhysicsUpdate(float delta); + void PhysicsProcess(float delta); } \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/GameApplication.cs b/DungeonShooting_Godot/src/game/GameApplication.cs index 517f5ef..51aa4d2 100644 --- a/DungeonShooting_Godot/src/game/GameApplication.cs +++ b/DungeonShooting_Godot/src/game/GameApplication.cs @@ -44,6 +44,9 @@ /// public RoomUI Ui { get; private set; } + /// + /// 全局根节点 + /// public Node2D GlobalNodeRoot { get; private set; } public GameApplication() diff --git a/DungeonShooting_Godot/src/game/item/weapon/bullet/Bullet.cs b/DungeonShooting_Godot/src/game/item/weapon/bullet/Bullet.cs index 4c43084..5e40c91 100644 --- a/DungeonShooting_Godot/src/game/item/weapon/bullet/Bullet.cs +++ b/DungeonShooting_Godot/src/game/item/weapon/bullet/Bullet.cs @@ -8,18 +8,25 @@ public Bullet(string scenePath, float maxDistance, Vector2 position, float rotation, uint targetLayer) : base(scenePath) { + CollisionArea = GetNode("CollisionArea"); + CollisionArea.CollisionMask = targetLayer; + CollisionArea.Connect("body_entered", this, nameof(_BodyEntered)); + + Collision.Disabled = true; + MaxDistance = maxDistance; - CollisionMask = targetLayer; Position = position; Rotation = rotation; ShadowOffset = new Vector2(0, 5); } + public Area2D CollisionArea { get; } + // 最大飞行距离 private float MaxDistance; // 子弹飞行速度 - private float FlySpeed = 600; + private float FlySpeed = 450; //当前子弹已经飞行的距离 private float CurrFlyDistance = 0; @@ -34,35 +41,29 @@ public override void _PhysicsProcess(float delta) { base._PhysicsProcess(delta); - //移动 - var kinematicCollision = MoveAndCollide(new Vector2(FlySpeed * delta, 0).Rotated(Rotation)); - //有碰撞 - if (kinematicCollision == null) + Position += new Vector2(FlySpeed * delta, 0).Rotated(Rotation); + //距离太大, 自动销毁 + CurrFlyDistance += FlySpeed * delta; + if (CurrFlyDistance >= MaxDistance) { - //距离太大, 自动销毁 - CurrFlyDistance += FlySpeed * delta; - if (CurrFlyDistance >= MaxDistance) - { - Destroy(); - } - } - else - { - var collider = kinematicCollision.Collider; - if (collider is Role role) - { - role.Hit(1); - } - - //播放受击动画 - Node2D hit = ResourceManager.Load(ResourcePath.prefab_effect_Hit_tscn).Instance(); - hit.RotationDegrees = MathUtils.RandRangeInt(0, 360); - hit.GlobalPosition = kinematicCollision.Position; - GameApplication.Instance.Room.GetRoot(true).AddChild(hit); - Destroy(); } + } + private void _BodyEntered(Node2D other) + { + if (other is Role role) + { + role.Hit(1); + } + + //播放受击动画 + Node2D hit = ResourceManager.Load(ResourcePath.prefab_effect_Hit_tscn).Instance(); + hit.RotationDegrees = MathUtils.RandRangeInt(0, 360); + hit.GlobalPosition = GlobalPosition; + GameApplication.Instance.Room.GetRoot(true).AddChild(hit); + + Destroy(); } } \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/item/weapon/gun/Gun.cs b/DungeonShooting_Godot/src/game/item/weapon/gun/Gun.cs index b1162a5..c2d6da6 100644 --- a/DungeonShooting_Godot/src/game/item/weapon/gun/Gun.cs +++ b/DungeonShooting_Godot/src/game/item/weapon/gun/Gun.cs @@ -23,8 +23,8 @@ ScatteringRangeBackSpeed = 40; //连发 ContinuousShoot = true; - AmmoCapacity = 120; - MaxAmmoCapacity = 120 * 70; + AmmoCapacity = 30; + MaxAmmoCapacity = 30 * 70; //扳机检测间隔 TriggerInterval = 0f; //连发数量 @@ -106,7 +106,7 @@ GameCamera.Main.ProcessDirectionalShake(Vector2.Right.Rotated(GlobalRotation) * 1.5f); } //播放射击音效 - SoundManager.PlaySoundEffect("ordinaryBullet.ogg", this, 6f); + SoundManager.PlaySoundEffectPosition(ResourcePath.resource_sound_sfx_ordinaryBullet_ogg, GameApplication.Instance.ViewToGlobalPosition(GlobalPosition), 6f); } protected override void OnShoot(float fireRotation) diff --git a/DungeonShooting_Godot/src/game/item/weapon/gun/Shotgun.cs b/DungeonShooting_Godot/src/game/item/weapon/gun/Shotgun.cs index 8d04498..f1424b1 100644 --- a/DungeonShooting_Godot/src/game/item/weapon/gun/Shotgun.cs +++ b/DungeonShooting_Godot/src/game/item/weapon/gun/Shotgun.cs @@ -72,7 +72,7 @@ GameCamera.Main.ProcessDirectionalShake(Vector2.Right.Rotated(GlobalRotation) * 1.5f); } //播放射击音效 - SoundManager.PlaySoundEffect("ordinaryBullet.ogg", this, 6f); + SoundManager.PlaySoundEffectPosition(ResourcePath.resource_sound_sfx_ordinaryBullet_ogg, GameApplication.Instance.ViewToGlobalPosition(GlobalPosition), 6f); } protected override void OnShoot(float fireRotation) diff --git a/DungeonShooting_Godot/src/game/item/weapon/shell/ShellCase.cs b/DungeonShooting_Godot/src/game/item/weapon/shell/ShellCase.cs index cf520ef..60e73d6 100644 --- a/DungeonShooting_Godot/src/game/item/weapon/shell/ShellCase.cs +++ b/DungeonShooting_Godot/src/game/item/weapon/shell/ShellCase.cs @@ -20,6 +20,6 @@ { //30秒后销毁 await ToSignal(GetTree().CreateTimer(30), "timeout"); - QueueFree(); + Destroy(); } } \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/manager/ResourceManager.cs b/DungeonShooting_Godot/src/game/manager/ResourceManager.cs index 4c69cd1..686c730 100644 --- a/DungeonShooting_Godot/src/game/manager/ResourceManager.cs +++ b/DungeonShooting_Godot/src/game/manager/ResourceManager.cs @@ -14,9 +14,11 @@ { _shadowMaterial = ResourceLoader.Load(ResourcePath.resource_materlal_Blend_tres); } + return _shadowMaterial; } } + private static ShaderMaterial _shadowMaterial; /// @@ -30,9 +32,11 @@ { _shadowShader = ResourceLoader.Load(ResourcePath.resource_materlal_Blend_gdshader); } + return _shadowShader; } } + private static Shader _shadowShader; private static readonly Dictionary CachePack = new Dictionary(); @@ -55,7 +59,12 @@ CachePack.Add(path, pack); return pack as T; } + else + { + GD.PrintErr("加载资源失败, 未找到资源: " + path); + } } - return default(T); + + return default; } } \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/manager/SoundManager.cs b/DungeonShooting_Godot/src/game/manager/SoundManager.cs index 2cf4f34..d7dfd26 100644 --- a/DungeonShooting_Godot/src/game/manager/SoundManager.cs +++ b/DungeonShooting_Godot/src/game/manager/SoundManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Godot; /// @@ -15,70 +16,158 @@ /// public class SoundManager { - public static SoundManager Instance { get => SingleTon.singleTon; } + private static Stack _streamPlayer2DStack = new Stack(); + private static Stack _streamPlayerStack = new Stack(); - private static class SingleTon + /// + /// 2D音频播放节点 + /// + private class AudioPlayer2D : AudioStreamPlayer2D { - internal static SoundManager singleTon = new SoundManager(); + public override void _Ready() + { + Connect("finished", this, nameof(OnPlayFinish)); + } + + public void OnPlayFinish() + { + RecycleAudioPlayer2D(this); + } } - private static AudioStreamPlayer audioStreamPlayer = new AudioStreamPlayer(); - - /// - /// 背景音乐路径 + /// 音频播放节点 /// - public static string BGMPath = "res://resource/sound/bgm/"; - /// - /// 音效路径 - /// - public static string SFXPath = "res://resource/sound/sfx/"; + private class AudioPlayer : AudioStreamPlayer + { + public override void _Ready() + { + Connect("finished", this, nameof(OnPlayFinish)); + } + + public void OnPlayFinish() + { + RecycleAudioPlayer(this); + } + } /// /// 播放声音 用于bgm /// - /// bgm名字 在resource/sound/bgm/目录下 - /// 需要播放声音得节点 将成为音频播放节点的父节点 + /// bgm路径 /// 音量 - public static void PlayeMusic(string soundName, Node node, float volume) + public static AudioStreamPlayer PlayMusic(string soundName, float volume = 0.5f) { - AudioStream sound = ResourceManager.Load(BGMPath + soundName); - if (sound != null) - { - AudioStreamPlayer soundNode = new AudioStreamPlayer(); - node.AddChild(soundNode); - soundNode.Stream = sound; - soundNode.Bus = Enum.GetName(typeof(BUS), 0); - soundNode.VolumeDb = volume; - soundNode.Play(); - } - else - { - GD.Print("没有这个资源!!!"); - } + var sound = ResourceManager.Load(soundName); + var soundNode = GetAudioPlayerInstance(); + GameApplication.Instance.GlobalNodeRoot.AddChild(soundNode); + soundNode.Stream = sound; + soundNode.Bus = Enum.GetName(typeof(BUS), 0); + soundNode.VolumeDb = volume; + soundNode.Play(); + return soundNode; } + /// /// 添加并播放音效 用于音效 /// - /// 音效文件名字 在resource/sound/sfx/目录下 - /// 需要播放声音得节点 将成为音频播放节点的父节点 + /// 音效文件路径 /// 音量 - public static void PlaySoundEffect(string soundName, Node node, float volume = 0f) + public static AudioStreamPlayer PlaySoundEffect(string soundName, float volume = 0.5f) { - AudioStream sound = ResourceManager.Load(SFXPath + soundName); - if (sound != null) + var sound = ResourceManager.Load(soundName); + var soundNode = GetAudioPlayerInstance(); + GameApplication.Instance.GlobalNodeRoot.AddChild(soundNode); + soundNode.Stream = sound; + soundNode.Bus = Enum.GetName(typeof(BUS), 1); + soundNode.VolumeDb = volume; + soundNode.Play(); + return soundNode; + } + + /// + /// 在指定的节点下播放音效 用于音效 + /// + /// 音效文件路径 + /// 发声节点所在全局坐标 + /// 音量 + /// 挂载节点, 为null则挂载到房间根节点下 + public static AudioStreamPlayer2D PlaySoundEffectPosition(string soundName, Vector2 pos, float volume = 0.5f, Node2D target = null) + { + var sound = ResourceManager.Load(soundName); + var soundNode = GetAudioPlayer2DInstance(); + if (target != null) { - AudioStreamPlayer soundNode = new AudioStreamPlayer(); - node.AddChild(soundNode); - soundNode.Stream = sound; - soundNode.Bus = Enum.GetName(typeof(BUS), 1); - soundNode.VolumeDb = volume; - soundNode.Play(); - GD.Print("bus:", soundNode.Bus); + target.AddChild(soundNode); } else { - GD.Print("没有这个资源!!!"); + GameApplication.Instance.GlobalNodeRoot.AddChild(soundNode); } + + soundNode.GlobalPosition = pos; + soundNode.Stream = sound; + soundNode.Bus = Enum.GetName(typeof(BUS), 1); + soundNode.VolumeDb = volume; + soundNode.Play(); + return soundNode; + } + + /// + /// 获取2D音频播放节点 + /// + private static AudioPlayer2D GetAudioPlayer2DInstance() + { + if (_streamPlayer2DStack.Count > 0) + { + return _streamPlayer2DStack.Pop(); + } + + var inst = new AudioPlayer2D(); + inst.AreaMask = 0; + return inst; + } + + /// + /// 获取音频播放节点 + /// + private static AudioPlayer GetAudioPlayerInstance() + { + if (_streamPlayerStack.Count > 0) + { + return _streamPlayerStack.Pop(); + } + + return new AudioPlayer(); + } + + /// + /// 回收2D音频播放节点 + /// + private static void RecycleAudioPlayer2D(AudioPlayer2D inst) + { + var parent = inst.GetParent(); + if (parent != null) + { + parent.RemoveChild(inst); + } + + inst.Stream = null; + _streamPlayer2DStack.Push(inst); + } + + /// + /// 回收音频播放节点 + /// + private static void RecycleAudioPlayer(AudioPlayer inst) + { + var parent = inst.GetParent(); + if (parent != null) + { + parent.RemoveChild(inst); + } + + inst.Stream = null; + _streamPlayerStack.Push(inst); } } \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/role/Enemy.cs b/DungeonShooting_Godot/src/game/role/Enemy.cs index 922e7f6..20b3a77 100644 --- a/DungeonShooting_Godot/src/game/role/Enemy.cs +++ b/DungeonShooting_Godot/src/game/role/Enemy.cs @@ -5,15 +5,26 @@ { AttackLayer = PhysicsLayer.Wall | PhysicsLayer.Props | PhysicsLayer.Player; Camp = CampEnum.Camp2; + + MoveSpeed = 20; + LookTarget = GameApplication.Instance.Room.Player; } public override void _Process(float delta) { base._Process(delta); - - if (Holster.ActiveWeapon != null) + Attack(); + } + + public override void _PhysicsProcess(float delta) + { + base._PhysicsProcess(delta); + + if (LookTarget != null) { - Holster.ActiveWeapon.Trigger(); + AnimatedSprite.Animation = AnimatorNames.ReverseRun; + Velocity = (LookTarget.GlobalPosition - GlobalPosition).Normalized() * MoveSpeed; + CalcMove(delta); } } } diff --git a/DungeonShooting_Godot/src/game/role/Player.cs b/DungeonShooting_Godot/src/game/role/Player.cs index d1dadc4..89b3681 100644 --- a/DungeonShooting_Godot/src/game/role/Player.cs +++ b/DungeonShooting_Godot/src/game/role/Player.cs @@ -2,51 +2,16 @@ public class Player : Role { + /// /// 移动加速度 /// - public float Acceleration = 1500f; - + public float Acceleration { get; set; } = 1500f; + /// /// 移动摩擦力 /// - public float Friction = 800f; - /// - /// 移动速度 - /// - public Vector2 Velocity = Vector2.Zero; - - /// - /// 当前护盾值 - /// - public int Shield - { - get => _shield; - protected set - { - int temp = _shield; - _shield = value; - if (temp != _shield) OnChangeShield(_shield); - } - } - private int _shield = 0; - - /// - /// 最大护盾值 - /// - public int MaxShield - { - get => _maxShield; - protected set - { - int temp = _maxShield; - _maxShield = value; - if (temp != _maxShield) OnChangeMaxShield(_maxShield); - } - } - private int _maxShield = 0; - - [Export] public PackedScene GunPrefab; + public float Friction { get; set; } = 800f; public Player(): base(ResourcePath.prefab_role_Player_tscn) { @@ -139,11 +104,9 @@ public override void _PhysicsProcess(float delta) { base._PhysicsProcess(delta); - Move(delta); + HandleMoveInput(delta); //播放动画 PlayAnim(); - //GlobalPosition = GlobalPosition.Round(); - //AnimatedSprite.Playing = false; } protected override void OnChangeHp(int hp) @@ -173,12 +136,12 @@ } } - protected void OnChangeShield(int shield) + protected override void OnChangeShield(int shield) { GameApplication.Instance.Ui.SetShield(shield); } - protected void OnChangeMaxShield(int maxShield) + protected override void OnChangeMaxShield(int maxShield) { GameApplication.Instance.Ui.SetMaxShield(maxShield); } @@ -211,20 +174,33 @@ } } - private void Move(float delta) + //处理角色移动的输入 + private void HandleMoveInput(float delta) { //角色移动 // 得到输入的 vector2 getvector方法返回值已经归一化过了noemalized Vector2 dir = Input.GetVector("move_left", "move_right", "move_up", "move_down"); - // 移动. 如果移动的数值接近0(是用 摇杆可能出现 方向 可能会出现浮点),就fricition的值 插值 到 0 + // 移动. 如果移动的数值接近0(是用 摇杆可能出现 方向 可能会出现浮点),就friction的值 插值 到 0 // 如果 有输入 就以当前速度,用acceleration 插值到 对应方向 * 最大速度 - if (Mathf.IsZeroApprox(dir.x)) Velocity.x = Mathf.MoveToward(Velocity.x, 0, Friction * delta); - else Velocity.x = Mathf.MoveToward(Velocity.x, dir.x * MoveSpeed, Acceleration * delta); + if (Mathf.IsZeroApprox(dir.x)) + { + Velocity = new Vector2(Mathf.MoveToward(Velocity.x, 0, Friction * delta), Velocity.y); + } + else + { + Velocity = new Vector2(Mathf.MoveToward(Velocity.x, dir.x * MoveSpeed, Acceleration * delta), Velocity.y); + } - if (Mathf.IsZeroApprox(dir.y)) Velocity.y = Mathf.MoveToward(Velocity.y, 0, Friction * delta); - else Velocity.y = Mathf.MoveToward(Velocity.y, dir.y * MoveSpeed, Acceleration * delta); - - Velocity = MoveAndSlide(Velocity); + if (Mathf.IsZeroApprox(dir.y)) + { + Velocity = new Vector2(Velocity.x, Mathf.MoveToward(Velocity.y, 0, Friction * delta)); + } + else + { + Velocity = new Vector2(Velocity.x, Mathf.MoveToward(Velocity.y, dir.y * MoveSpeed, Acceleration * delta)); + } + + CalcMove(delta); } // 播放动画 diff --git a/DungeonShooting_Godot/src/game/role/Role.cs b/DungeonShooting_Godot/src/game/role/Role.cs index 51e9377..1387d2b 100644 --- a/DungeonShooting_Godot/src/game/role/Role.cs +++ b/DungeonShooting_Godot/src/game/role/Role.cs @@ -69,6 +69,16 @@ private FaceDirection _face; /// + /// 是否启用角色移动 + /// + public bool EnableMove { get; set; } = true; + + /// + /// 移动速度 + /// + public Vector2 Velocity { get; set; } = Vector2.Zero; + + /// /// 血量 /// public int Hp @@ -97,18 +107,53 @@ } } private int _maxHp = 0; + + /// + /// 当前护盾值 + /// + public int Shield + { + get => _shield; + protected set + { + int temp = _shield; + _shield = value; + if (temp != _shield) OnChangeShield(_shield); + } + } + private int _shield = 0; + + /// + /// 最大护盾值 + /// + public int MaxShield + { + get => _maxShield; + protected set + { + int temp = _maxShield; + _maxShield = value; + if (temp != _maxShield) OnChangeMaxShield(_maxShield); + } + } + private int _maxShield = 0; /// /// 当前角色所看向的对象, 也就是枪口指向的对象 /// public ActivityObject LookTarget { get; set; } + /// + /// + /// + public StateCtr StateCtr { get; } + //初始缩放 - private Vector2 StartScele; + private Vector2 _startScale; //所有角色碰撞的道具 - private readonly List InteractiveItemList = new List(); + private readonly List _interactiveItemList = new List(); - private CheckInteractiveResult TempResultData; + private CheckInteractiveResult _tempResultData; /// /// 可以互动的道具 @@ -128,6 +173,20 @@ protected virtual void OnChangeMaxHp(int maxHp) { } + + /// + /// 护盾值改变时调用 + /// + protected virtual void OnChangeShield(int shield) + { + } + + /// + /// 最大护盾值改变时调用 + /// + protected virtual void OnChangeMaxShield(int maxShield) + { + } /// /// 当受伤时调用 @@ -165,7 +224,7 @@ { base._Ready(); AnimationPlayer = GetNode("AnimationPlayer"); - StartScele = Scale; + _startScale = Scale; MountPoint = GetNode("MountPoint"); MountPoint.Master = this; BackMountPoint = GetNode("BackMountPoint"); @@ -209,12 +268,12 @@ //检查可互动的道具 bool findFlag = false; - for (int i = 0; i < InteractiveItemList.Count; i++) + for (int i = 0; i < _interactiveItemList.Count; i++) { - var item = InteractiveItemList[i]; + var item = _interactiveItemList[i]; if (item == null) { - InteractiveItemList.RemoveAt(i--); + _interactiveItemList.RemoveAt(i--); } else { @@ -230,12 +289,12 @@ InteractiveItem = item; ChangeInteractiveItem(result); } - else if (result.ShowIcon != TempResultData.ShowIcon) //切换状态 + else if (result.ShowIcon != _tempResultData.ShowIcon) //切换状态 { ChangeInteractiveItem(result); } } - TempResultData = result; + _tempResultData = result; } } } @@ -248,6 +307,17 @@ } /// + /// 计算角色移动 + /// + public virtual void CalcMove(float delta) + { + if (EnableMove && Velocity != Vector2.Zero) + { + Velocity = MoveAndSlide(Velocity); + } + } + + /// /// 拾起一个武器, 并且切换到这个武器 /// /// 武器对象 @@ -256,7 +326,7 @@ if (Holster.PickupWeapon(weapon) != -1) { //从可互动队列中移除 - InteractiveItemList.Remove(weapon); + _interactiveItemList.Remove(weapon); } } @@ -356,12 +426,12 @@ if (face == FaceDirection.Right) { RotationDegrees = 0; - Scale = StartScele; + Scale = _startScale; } else { RotationDegrees = 180; - Scale = new Vector2(StartScele.x, -StartScele.y); + Scale = new Vector2(_startScale.x, -_startScale.y); } } } @@ -393,9 +463,9 @@ ActivityObject propObject = other.AsActivityObject(); if (propObject != null) { - if (!InteractiveItemList.Contains(propObject)) + if (!_interactiveItemList.Contains(propObject)) { - InteractiveItemList.Add(propObject); + _interactiveItemList.Add(propObject); } } } @@ -409,9 +479,9 @@ ActivityObject propObject = other.AsActivityObject(); if (propObject != null) { - if (InteractiveItemList.Contains(propObject)) + if (_interactiveItemList.Contains(propObject)) { - InteractiveItemList.Remove(propObject); + _interactiveItemList.Remove(propObject); } if (InteractiveItem == propObject) { diff --git a/DungeonShooting_Godot/src/game/role/state/IState.cs b/DungeonShooting_Godot/src/game/role/state/IState.cs new file mode 100644 index 0000000..8276dd0 --- /dev/null +++ b/DungeonShooting_Godot/src/game/role/state/IState.cs @@ -0,0 +1,44 @@ +/// +/// 状态接口 +/// +public interface IState +{ + /// + /// 当前状态对象对应的状态枚举类型 + /// + StateEnum StateType { get; } + + /// + /// 当前状态对象挂载的角色对象 + /// + Role Role { get; set; } + + /// + /// 当前状态对象所处的状态机对象 + /// + StateCtr StateController { get; set; } + + /// + /// 当从其他状态进入到当前状态时调用 + /// + /// 上一个状态类型 + /// 切换当前状态时附带的参数 + void Enter(StateEnum prev, params object[] args); + + /// + /// 物理帧每帧更新 + /// + void PhysicsProcess(float delta); + + /// + /// 是否允许切换至下一个状态 + /// + /// 下一个状态类型 + bool CanChangeState(StateEnum next); + + /// + /// 从当前状态退出时调用 + /// + /// 下一个状态类型 + void Exit(StateEnum next); +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/role/state/StateCtr.cs b/DungeonShooting_Godot/src/game/role/state/StateCtr.cs new file mode 100644 index 0000000..d0305ce --- /dev/null +++ b/DungeonShooting_Godot/src/game/role/state/StateCtr.cs @@ -0,0 +1,109 @@ +using Godot; +using System.Collections.Generic; + +/// +/// 角色状态机控制器 +/// +public class StateCtr : Component +{ + /// + /// 当前活跃的状态 + /// + public IState CurrState => _currState; + private IState _currState; + + /// + /// 负责存放状态实例对象 + /// + private readonly Dictionary _states = new Dictionary(); + + /// + /// 记录下当前帧是否有改变的状态 + /// + private bool _isChangeState; + + public override void PhysicsProcess(float delta) + { + _isChangeState = false; + if (CurrState != null) + { + CurrState.PhysicsProcess(delta); + //判断当前帧是否有改变的状态, 如果有, 则重新调用 PhysicsProcess() 方法 + if (_isChangeState) + { + PhysicsProcess(delta); + } + } + } + + /// + /// 往状态机力注册一个新的状态 + /// + public void Register(IState state) + { + if (GetStateInstance(state.StateType) != null) + { + GD.PrintErr("当前状态已经在状态机中注册:", state); + return; + } + state.Role = ActivityObject as Role; + state.StateController = this; + _states.Add(state.StateType, state); + } + + /// + /// 立即切换到下一个指定状态, 并且这一帧会被调用 PhysicsProcess + /// + public void ChangeState(StateEnum next, params object[] arg) + { + _changeState(false, next, arg); + } + + /// + /// 切换到下一个指定状态, 下一帧才会调用 PhysicsProcess + /// + public void ChangeStateLate(StateEnum next, params object[] arg) + { + _changeState(true, next, arg); + } + + /// + /// 根据状态类型获取相应的状态对象 + /// + private IState GetStateInstance(StateEnum stateType) + { + _states.TryGetValue(stateType, out var v); + return v; + } + + /// + /// 切换状态 + /// + private void _changeState(bool late, StateEnum next, params object[] arg) + { + if (_currState != null && _currState.StateType == next) + { + return; + } + var newState = GetStateInstance(next); + if (newState == null) + { + GD.PrintErr("当前状态机未找到相应状态:" + next); + return; + } + if (_currState == null) + { + _currState = newState; + newState.Enter(StateEnum.None, arg); + } + else if (_currState.CanChangeState(next)) + { + _isChangeState = !late; + var prev = _currState.StateType; + _currState.Exit(next); + GD.Print("nextState => " + next); + _currState = newState; + _currState.Enter(prev, arg); + } + } +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/role/state/StateEnum.cs b/DungeonShooting_Godot/src/game/role/state/StateEnum.cs new file mode 100644 index 0000000..151b523 --- /dev/null +++ b/DungeonShooting_Godot/src/game/role/state/StateEnum.cs @@ -0,0 +1,8 @@ + +public enum StateEnum +{ + None = 0, + Idle = 1, + Run = 2, + Move = 3, +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/room/RoomManager.cs b/DungeonShooting_Godot/src/game/room/RoomManager.cs index e7769d2..d8f1587 100644 --- a/DungeonShooting_Godot/src/game/room/RoomManager.cs +++ b/DungeonShooting_Godot/src/game/room/RoomManager.cs @@ -38,8 +38,7 @@ public override void _Ready() { //播放bgm - SoundManager.PlayeMusic("intro.ogg", this, -17f); - _enemy.LookTarget = Player; + SoundManager.PlayMusic(ResourcePath.resource_sound_bgm_Intro_ogg, -17f); _enemy.PickUpWeapon(WeaponManager.GetGun("1001")); WeaponManager.GetGun("1001").PutDown(new Vector2(80, 100));