diff --git a/DungeonShooting_Godot/prefab/role/Enemy.tscn b/DungeonShooting_Godot/prefab/role/Enemy.tscn index 98ed763..c4fd32f 100644 --- a/DungeonShooting_Godot/prefab/role/Enemy.tscn +++ b/DungeonShooting_Godot/prefab/role/Enemy.tscn @@ -23,4 +23,7 @@ [node name="AnimatedSprite" parent="." index="2"] material = SubResource( 2 ) -frame = 2 +frame = 0 + +[node name="ViewRay" type="RayCast2D" parent="." index="7"] +position = Vector2( 0, -8 ) diff --git a/DungeonShooting_Godot/src/framework/ActivityObject.cs b/DungeonShooting_Godot/src/framework/ActivityObject.cs index 4de5ab2..71984fa 100644 --- a/DungeonShooting_Godot/src/framework/ActivityObject.cs +++ b/DungeonShooting_Godot/src/framework/ActivityObject.cs @@ -327,6 +327,10 @@ RestoreCollision(); } + /// + /// 往当前物体上挂载一个组件 + /// + /// 组件对象 public void AddComponent(Component component) { if (!ContainsComponent(component)) @@ -337,6 +341,10 @@ } } + /// + /// 移除一个组件 + /// + /// 组件对象 public void RemoveComponent(Component component) { for (int i = 0; i < _components.Count; i++) @@ -351,6 +359,9 @@ } } + /// + /// 根据类型获取一个组件 + /// public Component GetComponent(Type type) { for (int i = 0; i < _components.Count; i++) @@ -365,6 +376,9 @@ return null; } + /// + /// 根据类型获取一个组件 + /// public TC GetComponent() where TC : Component { var component = GetComponent(typeof(TC)); diff --git a/DungeonShooting_Godot/src/framework/Component.cs b/DungeonShooting_Godot/src/framework/Component.cs index 16c81e0..f81a600 100644 --- a/DungeonShooting_Godot/src/framework/Component.cs +++ b/DungeonShooting_Godot/src/framework/Component.cs @@ -2,7 +2,7 @@ using Godot; /// -/// 组件基类, 用于挂载到游戏物体上, 相比于原生 Node 更加轻量化, 可以大量添加组件 +/// 组件基类, 用于挂载到游戏物体上, 相比于原生 Node 更加轻量化, 实例化 Component 不会创建额外的 Node, 可以大量添加组件 /// public abstract class Component : IProcess, IDestroy { diff --git a/DungeonShooting_Godot/src/game/GameApplication.cs b/DungeonShooting_Godot/src/game/GameApplication.cs index 51aa4d2..6bb09c1 100644 --- a/DungeonShooting_Godot/src/game/GameApplication.cs +++ b/DungeonShooting_Godot/src/game/GameApplication.cs @@ -68,11 +68,17 @@ Ui.AddChild(Cursor); } + /// + /// 将 viewport 以外的全局坐标 转换成 viewport 内的全局坐标 + /// public Vector2 GlobalToViewPosition(Vector2 globalPos) { return globalPos / GameConfig.WindowScale - (GameConfig.ViewportSize / 2) + GameCamera.Main.GlobalPosition; } + /// + /// 将 viewport 以内的全局坐标 转换成 viewport 外的全局坐标 + /// public Vector2 ViewToGlobalPosition(Vector2 viewPos) { return (viewPos - GameCamera.Main.GlobalPosition + (GameConfig.ViewportSize / 2)) * GameConfig.WindowScale - GameCamera.Main.SubPixelPosition; diff --git a/DungeonShooting_Godot/src/game/common/MathUtils.cs b/DungeonShooting_Godot/src/game/common/MathUtils.cs deleted file mode 100644 index 9c6033a..0000000 --- a/DungeonShooting_Godot/src/game/common/MathUtils.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Godot; - -public static class MathUtils -{ - /// - /// 返回一个区间内的随机小数 - /// - public static float RandRange(float min, float max) - { - if (min == max) return min; - if (min > max) - return GD.Randf() * (min - max) + max; - return GD.Randf() * (max - min) + min; - } - - /// - /// 返回一个区间内的随机整数 - /// - public static int RandRangeInt(int min, int max) - { - if (min == max) return min; - if (min > max) - return Mathf.FloorToInt(GD.Randf() * (min - max + 1) + max); - return Mathf.FloorToInt(GD.Randf() * (max - min + 1) + min); - } -} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/common/NodeExtend.cs b/DungeonShooting_Godot/src/game/common/NodeExtend.cs index bd54eee..f6db78c 100644 --- a/DungeonShooting_Godot/src/game/common/NodeExtend.cs +++ b/DungeonShooting_Godot/src/game/common/NodeExtend.cs @@ -1,5 +1,4 @@ using Godot; -using System; /// /// 该类为 node 节点通用扩展函数类 @@ -7,7 +6,7 @@ public static class NodeExtend { /// - /// 尝试将一个node2d节点转换成一个 ActivityObject 类 + /// 尝试将一个 Node2d 节点转换成一个 ActivityObject 对象, 如果转换失败, 则返回 null /// public static ActivityObject AsActivityObject(this Node2D node2d) { diff --git a/DungeonShooting_Godot/src/game/common/Utils.cs b/DungeonShooting_Godot/src/game/common/Utils.cs new file mode 100644 index 0000000..153d6ca --- /dev/null +++ b/DungeonShooting_Godot/src/game/common/Utils.cs @@ -0,0 +1,29 @@ +using Godot; + +/// +/// 常用函数工具类 +/// +public static class Utils +{ + /// + /// 返回一个区间内的随机小数 + /// + public static float RandRange(float min, float max) + { + if (min == max) return min; + if (min > max) + return GD.Randf() * (min - max) + max; + return GD.Randf() * (max - min) + min; + } + + /// + /// 返回一个区间内的随机整数 + /// + public static int RandRangeInt(int min, int max) + { + if (min == max) return min; + if (min > max) + return Mathf.FloorToInt(GD.Randf() * (min - max + 1) + max); + return Mathf.FloorToInt(GD.Randf() * (max - min + 1) + min); + } +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/item/weapon/Weapon.cs b/DungeonShooting_Godot/src/game/item/weapon/Weapon.cs index 7680055..8f43ae9 100644 --- a/DungeonShooting_Godot/src/game/item/weapon/Weapon.cs +++ b/DungeonShooting_Godot/src/game/item/weapon/Weapon.cs @@ -418,7 +418,7 @@ if (!Attribute.ContinuousShoot) { continuousCount = - MathUtils.RandRangeInt(Attribute.MinContinuousCount, Attribute.MaxContinuousCount); + Utils.RandRangeInt(Attribute.MinContinuousCount, Attribute.MaxContinuousCount); } } @@ -475,7 +475,7 @@ OnFire(); //开火发射的子弹数量 - var bulletCount = MathUtils.RandRangeInt(Attribute.MaxFireBulletCount, Attribute.MinFireBulletCount); + var bulletCount = Utils.RandRangeInt(Attribute.MaxFireBulletCount, Attribute.MinFireBulletCount); //武器口角度 var angle = new Vector2(GameConfig.ScatteringDistance, CurrScatteringRange).Angle(); @@ -502,7 +502,7 @@ //武器身位置 Position = new Vector2( Mathf.Max(-Attribute.MaxBacklash, - Position.x - MathUtils.RandRange(Attribute.MinBacklash, Attribute.MaxBacklash)), Position.y); + Position.x - Utils.RandRange(Attribute.MinBacklash, Attribute.MaxBacklash)), Position.y); if (FireEvent != null) { @@ -711,8 +711,8 @@ if (flag) { Throw(new Vector2(30, 15), GlobalPosition, 0, 0, - MathUtils.RandRangeInt(-20, 20), MathUtils.RandRangeInt(20, 50), - MathUtils.RandRangeInt(-180, 180)); + Utils.RandRangeInt(-20, 20), Utils.RandRangeInt(20, 50), + Utils.RandRangeInt(-180, 180)); } } else //没有武器 @@ -755,10 +755,10 @@ } var startHeight = 6; - var direction = master.GlobalRotationDegrees + MathUtils.RandRangeInt(-20, 20); + var direction = master.GlobalRotationDegrees + Utils.RandRangeInt(-20, 20); var xf = 30; - var yf = MathUtils.RandRangeInt(60, 120); - var rotate = MathUtils.RandRangeInt(-180, 180); + var yf = Utils.RandRangeInt(60, 120); + var rotate = Utils.RandRangeInt(-180, 180); Throw(new Vector2(30, 15), master.MountPoint.GlobalPosition, startHeight, direction, xf, yf, rotate, true); } diff --git a/DungeonShooting_Godot/src/game/item/weapon/bullet/Bullet.cs b/DungeonShooting_Godot/src/game/item/weapon/bullet/Bullet.cs index 5e40c91..cfdffde 100644 --- a/DungeonShooting_Godot/src/game/item/weapon/bullet/Bullet.cs +++ b/DungeonShooting_Godot/src/game/item/weapon/bullet/Bullet.cs @@ -60,7 +60,7 @@ //播放受击动画 Node2D hit = ResourceManager.Load(ResourcePath.prefab_effect_Hit_tscn).Instance(); - hit.RotationDegrees = MathUtils.RandRangeInt(0, 360); + hit.RotationDegrees = Utils.RandRangeInt(0, 360); hit.GlobalPosition = GlobalPosition; GameApplication.Instance.Room.GetRoot(true).AddChild(hit); diff --git a/DungeonShooting_Godot/src/game/item/weapon/gun/Gun.cs b/DungeonShooting_Godot/src/game/item/weapon/gun/Gun.cs index c2d6da6..dd91ad0 100644 --- a/DungeonShooting_Godot/src/game/item/weapon/gun/Gun.cs +++ b/DungeonShooting_Godot/src/game/item/weapon/gun/Gun.cs @@ -93,10 +93,10 @@ //创建一个弹壳 var startPos = GlobalPosition + new Vector2(0, 5); var startHeight = 6; - var direction = GlobalRotationDegrees + MathUtils.RandRangeInt(-30, 30) + 180; - var xf = MathUtils.RandRangeInt(20, 60); - var yf = MathUtils.RandRangeInt(60, 120); - var rotate = MathUtils.RandRangeInt(-720, 720); + var direction = GlobalRotationDegrees + Utils.RandRangeInt(-30, 30) + 180; + var xf = Utils.RandRangeInt(20, 60); + var yf = Utils.RandRangeInt(60, 120); + var rotate = Utils.RandRangeInt(-720, 720); var shell = new ShellCase(); shell.Throw(new Vector2(10, 5), startPos, startHeight, direction, xf, yf, rotate, true); @@ -115,7 +115,7 @@ //CreateBullet(BulletPack, FirePoint.GlobalPosition, fireRotation); var bullet = new Bullet( ResourcePath.prefab_weapon_bullet_Bullet_tscn, - MathUtils.RandRange(Attribute.MinDistance, Attribute.MaxDistance), + Utils.RandRange(Attribute.MinDistance, Attribute.MaxDistance), FirePoint.GlobalPosition, fireRotation, Master != null ? Master.AttackLayer : Role.DefaultAttackLayer diff --git a/DungeonShooting_Godot/src/game/item/weapon/gun/Shotgun.cs b/DungeonShooting_Godot/src/game/item/weapon/gun/Shotgun.cs index f1424b1..a8685d6 100644 --- a/DungeonShooting_Godot/src/game/item/weapon/gun/Shotgun.cs +++ b/DungeonShooting_Godot/src/game/item/weapon/gun/Shotgun.cs @@ -59,10 +59,10 @@ //创建一个弹壳 var startPos = GlobalPosition + new Vector2(0, 5); var startHeight = 6; - var direction = GlobalRotationDegrees + MathUtils.RandRangeInt(-30, 30) + 180; - var xf = MathUtils.RandRangeInt(20, 60); - var yf = MathUtils.RandRangeInt(60, 120); - var rotate = MathUtils.RandRangeInt(-720, 720); + var direction = GlobalRotationDegrees + Utils.RandRangeInt(-30, 30) + 180; + var xf = Utils.RandRangeInt(20, 60); + var yf = Utils.RandRangeInt(60, 120); + var rotate = Utils.RandRangeInt(-720, 720); var shell = new ShellCase(); shell.Throw(new Vector2(5, 10), startPos, startHeight, direction, xf, yf, rotate, true); @@ -84,9 +84,9 @@ var bullet = new Bullet( ResourcePath.prefab_weapon_bullet_Bullet_tscn, - MathUtils.RandRange(Attribute.MinDistance, Attribute.MaxDistance), + Utils.RandRange(Attribute.MinDistance, Attribute.MaxDistance), FirePoint.GlobalPosition, - fireRotation + MathUtils.RandRange(-20 / 180f * Mathf.Pi, 20 / 180f * Mathf.Pi), + fireRotation + Utils.RandRange(-20 / 180f * Mathf.Pi, 20 / 180f * Mathf.Pi), Master != null ? Master.AttackLayer : Role.DefaultAttackLayer ); bullet.PutDown(); diff --git a/DungeonShooting_Godot/src/game/manager/InputManager.cs b/DungeonShooting_Godot/src/game/manager/InputManager.cs index 9dc3121..359b9be 100644 --- a/DungeonShooting_Godot/src/game/manager/InputManager.cs +++ b/DungeonShooting_Godot/src/game/manager/InputManager.cs @@ -1,12 +1,15 @@  using Godot; +/// +/// 输入管理器 +/// public static class InputManager { /// - /// 获取鼠标坐标 + /// 获取鼠标在Viewport节点下的坐标 /// - public static Vector2 GetMousePosition() + public static Vector2 GetViewportMousePosition() { var application = GameApplication.Instance; return application.GlobalToViewPosition(application.GetGlobalMousePosition()); diff --git a/DungeonShooting_Godot/src/game/role/Enemy.cs b/DungeonShooting_Godot/src/game/role/Enemy.cs deleted file mode 100644 index 20b3a77..0000000 --- a/DungeonShooting_Godot/src/game/role/Enemy.cs +++ /dev/null @@ -1,30 +0,0 @@ - -public class Enemy : Role -{ - public Enemy() : base(ResourcePath.prefab_role_Enemy_tscn) - { - 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); - Attack(); - } - - public override void _PhysicsProcess(float delta) - { - base._PhysicsProcess(delta); - - if (LookTarget != null) - { - AnimatedSprite.Animation = AnimatorNames.ReverseRun; - Velocity = (LookTarget.GlobalPosition - GlobalPosition).Normalized() * MoveSpeed; - CalcMove(delta); - } - } -} diff --git a/DungeonShooting_Godot/src/game/role/IState.cs b/DungeonShooting_Godot/src/game/role/IState.cs new file mode 100644 index 0000000..14d609d --- /dev/null +++ b/DungeonShooting_Godot/src/game/role/IState.cs @@ -0,0 +1,44 @@ +/// +/// 状态接口 +/// +public interface IState where T : ActivityObject +{ + /// + /// 当前状态对象对应的状态枚举类型 + /// + StateEnum StateType { get; } + + /// + /// 当前状态对象挂载的角色对象 + /// + T Master { get; set; } + + /// + /// 当前状态对象所处的状态机对象 + /// + StateController 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/Player.cs b/DungeonShooting_Godot/src/game/role/Player.cs index 89b3681..8103e7f 100644 --- a/DungeonShooting_Godot/src/game/role/Player.cs +++ b/DungeonShooting_Godot/src/game/role/Player.cs @@ -48,7 +48,7 @@ var gPos = GlobalPosition; if (LookTarget == null) { - Vector2 mousePos = InputManager.GetMousePosition(); + Vector2 mousePos = InputManager.GetViewportMousePosition(); if (mousePos.x > gPos.x && Face == FaceDirection.Left) { Face = FaceDirection.Right; diff --git a/DungeonShooting_Godot/src/game/role/Role.cs b/DungeonShooting_Godot/src/game/role/Role.cs index 1387d2b..1cb7309 100644 --- a/DungeonShooting_Godot/src/game/role/Role.cs +++ b/DungeonShooting_Godot/src/game/role/Role.cs @@ -69,12 +69,12 @@ private FaceDirection _face; /// - /// 是否启用角色移动 + /// 是否启用角色移动, 如果禁用, 那么调用 CalcMove() 将不再有任何效果 /// public bool EnableMove { get; set; } = true; /// - /// 移动速度 + /// 移动速度, 通过调用 CalcMove() 函数来移动 /// public Vector2 Velocity { get; set; } = Vector2.Zero; @@ -144,9 +144,9 @@ public ActivityObject LookTarget { get; set; } /// - /// + /// 角色身上的状态机 /// - public StateCtr StateCtr { get; } + public StateController StateController { get; } //初始缩放 private Vector2 _startScale; @@ -218,6 +218,8 @@ public Role(string scenePath) : base(scenePath) { Holster = new Holster(this); + StateController = new StateController(); + AddComponent(StateController); } public override void _Ready() @@ -307,6 +309,16 @@ } /// + /// 判断指定坐标是否在角色视野方向 + /// + public bool IsPositionInForward(Vector2 pos) + { + var gps = GlobalPosition; + return (Face == FaceDirection.Left && pos.x <= gps.x) || + (Face == FaceDirection.Right && pos.x >= gps.x); + } + + /// /// 计算角色移动 /// public virtual void CalcMove(float delta) diff --git a/DungeonShooting_Godot/src/game/role/StateController.cs b/DungeonShooting_Godot/src/game/role/StateController.cs new file mode 100644 index 0000000..2ffc47a --- /dev/null +++ b/DungeonShooting_Godot/src/game/role/StateController.cs @@ -0,0 +1,109 @@ +using Godot; +using System.Collections.Generic; + +/// +/// 对象状态机控制器 +/// +public class StateController : Component where T : ActivityObject +{ + /// + /// 当前活跃的状态 + /// + 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.Master = ActivityObject as T; + 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/StateEnum.cs b/DungeonShooting_Godot/src/game/role/StateEnum.cs new file mode 100644 index 0000000..3ba322d --- /dev/null +++ b/DungeonShooting_Godot/src/game/role/StateEnum.cs @@ -0,0 +1,20 @@ + +public enum StateEnum +{ + /// + /// 无状态 + /// + None = 0, + /// + /// 静止状态 + /// + Idle = 1, + /// + /// 奔跑状态 + /// + Run = 2, + /// + /// 行走状态 + /// + Walk = 3, +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs b/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs new file mode 100644 index 0000000..060f075 --- /dev/null +++ b/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs @@ -0,0 +1,98 @@ +#region 基础敌人设计思路 +/* +敌人有三种状态: +状态1: 未发现玩家, 视野不可穿墙, 该状态下敌人移动比较规律, 移动速度较慢, 一旦玩家进入视野或者听到玩家枪声, 立刻切换至状态3, 该房间的敌人不能再回到状态1 +状态2: 发现有玩家, 但不知道在哪, 视野不可穿墙, 该情况下敌人移动速度明显加快, 移动不规律, 一旦玩家进入视野或者听到玩家枪声, 立刻切换至状态3 +状态3: 明确知道玩家的位置, 视野允许穿墙, 移动速度与状态2一致, 进入该状态时, 敌人之间会相互告知玩家所在位置, 并朝着玩家位置开火, + 如果有墙格挡, 则有一定概率继续开火, 一旦玩家立刻敌人视野超哥一段时间, 敌人自动切换为状态2 + +敌人状态1只存在于少数房间内, 比如特殊房间, 大部分情况下敌人应该是状态2, 或者玩家进入房间时就被敌人发现 +*/ +#endregion + + +using Godot; + +/// +/// 基础敌人 +/// +public class Enemy : Role +{ + + /// + /// 视野半径, 单位像素 + /// + public float ViewRange { get; set; } = 200; + + /// + /// 背后的视野半径, 单位像素 + /// + public float BackViewRange { get; set; } = 50; + + /// + /// 视野检测射线, 朝玩家打射线, 检测是否碰到墙 + /// + public RayCast2D ViewRay { get; } + + public Enemy() : base(ResourcePath.prefab_role_Enemy_tscn) + { + AttackLayer = PhysicsLayer.Wall | PhysicsLayer.Props | PhysicsLayer.Player; + Camp = CampEnum.Camp2; + + MoveSpeed = 20; + + //视野射线 + ViewRay = GetNode("ViewRay"); + + //注册Ai状态机 + StateController.Register(new AIIdleState()); + StateController.Register(new AIRunState()); + //默认状态 + StateController.ChangeState(StateEnum.Idle); + } + + public override void _PhysicsProcess(float delta) + { + base._PhysicsProcess(delta); + + var player = GameApplication.Instance.Room.Player; + //玩家中心点坐标 + var playerPos = player.MountPoint.GlobalPosition; + + //检测是否在视野内 + var pos = GlobalPosition; + + //玩家是否在前方 + var isForward = IsPositionInForward(playerPos); + + if (isForward) //脸朝向玩家 + { + if (pos.DistanceSquaredTo(playerPos) <= ViewRange * ViewRange) //没有超出视野半径 + { + //射线检测墙体 + ViewRay.Enabled = true; + var localPos = ViewRay.ToLocal(playerPos); + ViewRay.CastTo = localPos; + ViewRay.ForceRaycastUpdate(); + + if (ViewRay.IsColliding()) + { + LookTarget = null; + StateController.ChangeState(StateEnum.Idle); + } + else + { + LookTarget = player; + StateController.ChangeState(StateEnum.Run); + } + + ViewRay.Enabled = false; + } + else + { + LookTarget = null; + StateController.ChangeState(StateEnum.Idle); + } + } + } +} diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AIIdleState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AIIdleState.cs new file mode 100644 index 0000000..601d820 --- /dev/null +++ b/DungeonShooting_Godot/src/game/role/enemy/state/AIIdleState.cs @@ -0,0 +1,29 @@ + +/// +/// AI 静止行为 +/// +public class AIIdleState : IState +{ + public StateEnum StateType { get; } = StateEnum.Idle; + public Role Master { get; set; } + public StateController StateController { get; set; } + public void Enter(StateEnum prev, params object[] args) + { + + } + + public void PhysicsProcess(float delta) + { + + } + + public bool CanChangeState(StateEnum next) + { + return true; + } + + public void Exit(StateEnum next) + { + + } +} diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AIRunState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AIRunState.cs new file mode 100644 index 0000000..2105c2f --- /dev/null +++ b/DungeonShooting_Godot/src/game/role/enemy/state/AIRunState.cs @@ -0,0 +1,35 @@ + +/// +/// AI 奔跑行为 +/// +public class AIRunState : IState +{ + public StateEnum StateType { get; } = StateEnum.Run; + public Role Master { get; set; } + public StateController StateController { get; set; } + public void Enter(StateEnum prev, params object[] args) + { + + } + + public void PhysicsProcess(float delta) + { + var master = Master; + if (master.LookTarget != null) + { + master.AnimatedSprite.Animation = AnimatorNames.ReverseRun; + master.Velocity = (master.LookTarget.GlobalPosition - master.GlobalPosition).Normalized() * master.MoveSpeed; + master.CalcMove(delta); + } + } + + public bool CanChangeState(StateEnum next) + { + return true; + } + + public void Exit(StateEnum next) + { + + } +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/role/state/IState.cs b/DungeonShooting_Godot/src/game/role/state/IState.cs deleted file mode 100644 index 8276dd0..0000000 --- a/DungeonShooting_Godot/src/game/role/state/IState.cs +++ /dev/null @@ -1,44 +0,0 @@ -/// -/// 状态接口 -/// -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 deleted file mode 100644 index d0305ce..0000000 --- a/DungeonShooting_Godot/src/game/role/state/StateCtr.cs +++ /dev/null @@ -1,109 +0,0 @@ -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 deleted file mode 100644 index 151b523..0000000 --- a/DungeonShooting_Godot/src/game/role/state/StateEnum.cs +++ /dev/null @@ -1,8 +0,0 @@ - -public enum StateEnum -{ - None = 0, - Idle = 1, - Run = 2, - Move = 3, -} \ No newline at end of file