diff --git a/DungeonShooting_Godot/prefab/role/Enemy.tscn b/DungeonShooting_Godot/prefab/role/Enemy.tscn
index f4e9b41..ae7b5d5 100644
--- a/DungeonShooting_Godot/prefab/role/Enemy.tscn
+++ b/DungeonShooting_Godot/prefab/role/Enemy.tscn
@@ -24,6 +24,7 @@
[node name="AnimatedSprite" parent="." index="2"]
material = SubResource( 2 )
+frame = 1
[node name="Collision" parent="." index="3"]
position = Vector2( 0, -8 )
diff --git a/DungeonShooting_Godot/prefab/role/Player.tscn b/DungeonShooting_Godot/prefab/role/Player.tscn
index 7bfb950..7154560 100644
--- a/DungeonShooting_Godot/prefab/role/Player.tscn
+++ b/DungeonShooting_Godot/prefab/role/Player.tscn
@@ -24,4 +24,4 @@
[node name="AnimatedSprite" parent="." index="2"]
material = SubResource( 2 )
-frame = 3
+frame = 1
diff --git a/DungeonShooting_Godot/src/framework/ActivityObject.cs b/DungeonShooting_Godot/src/framework/ActivityObject.cs
index 089ca37..effd461 100644
--- a/DungeonShooting_Godot/src/framework/ActivityObject.cs
+++ b/DungeonShooting_Godot/src/framework/ActivityObject.cs
@@ -10,6 +10,11 @@
public abstract class ActivityObject : KinematicBody2D
{
///
+ /// 是否是调试模式
+ ///
+ public static bool IsDebug { get; set; }
+
+ ///
/// 当前物体类型id, 用于区分是否是同一种物体, 如果不是通过 ActivityObject.Create() 函数创建出来的对象那么 ItemId 为 null
///
public string ItemId { get; internal set; }
@@ -231,6 +236,13 @@
}
///
+ /// 如果开启 debug, 则每帧调用该函数, 可用于绘制文字线段等
+ ///
+ protected virtual void DebugDraw()
+ {
+ }
+
+ ///
/// 拾起一个 node 节点
///
public void Pickup()
@@ -408,9 +420,10 @@
var temp = arr[i].Value;
if (temp != null && temp.ActivityObject == this && temp.Enable)
{
- if (!temp.IsStart)
+ if (!temp.IsReady)
{
temp.Ready();
+ temp.IsReady = true;
}
temp.Process(delta);
@@ -500,7 +513,54 @@
//计算阴影
CalcShadow();
}
+
+ //调试绘制
+ if (IsDebug)
+ {
+ Update();
+ }
+ }
+
+ public override void _PhysicsProcess(float delta)
+ {
+ //更新组件
+ if (_components.Count > 0)
+ {
+ var arr = _components.ToArray();
+ for (int i = 0; i < arr.Length; i++)
+ {
+ if (IsDestroyed) return;
+ var temp = arr[i].Value;
+ if (temp != null && temp.ActivityObject == this && temp.Enable)
+ {
+ if (!temp.IsReady)
+ {
+ temp.Ready();
+ temp.IsReady = true;
+ }
+ temp.PhysicsProcess(delta);
+ }
+ }
+ }
+ }
+
+ public override void _Draw()
+ {
+ if (IsDebug)
+ {
+ DebugDraw();
+ var arr = _components.ToArray();
+ for (int i = 0; i < arr.Length; i++)
+ {
+ if (IsDestroyed) return;
+ var temp = arr[i].Value;
+ if (temp != null && temp.ActivityObject == this && temp.Enable)
+ {
+ temp.DebugDraw();
+ }
+ }
+ }
}
///
@@ -524,29 +584,7 @@
}
}
- public override void _PhysicsProcess(float delta)
- {
- //更新组件
- if (_components.Count > 0)
- {
- var arr = _components.ToArray();
- for (int i = 0; i < arr.Length; i++)
- {
- if (IsDestroyed) return;
- var temp = arr[i].Value;
- if (temp != null && temp.ActivityObject == this && temp.Enable)
- {
- if (!temp.IsStart)
- {
- temp.Ready();
- }
-
- temp.PhysicsProcess(delta);
- }
- }
- }
- }
-
+
///
/// 销毁物体
///
diff --git a/DungeonShooting_Godot/src/framework/Component.cs b/DungeonShooting_Godot/src/framework/Component.cs
index f81a600..2f2264a 100644
--- a/DungeonShooting_Godot/src/framework/Component.cs
+++ b/DungeonShooting_Godot/src/framework/Component.cs
@@ -142,8 +142,10 @@
///
public bool IsDestroyed { get; private set; }
- //是否调用过 start 函数
- internal bool IsStart = false;
+ ///
+ /// 是否调用过 Ready 函数
+ ///
+ public bool IsReady { get; set; }
///
/// 第一次调用 Process 或 PhysicsProcess 之前调用
@@ -202,6 +204,13 @@
public virtual void OnDisable()
{
}
+
+ ///
+ /// 如果开启 debug, 则每帧调用该函数, 可用于绘制文字线段等
+ ///
+ public virtual void DebugDraw()
+ {
+ }
///
/// 当组件销毁
diff --git a/DungeonShooting_Godot/src/game/GameApplication.cs b/DungeonShooting_Godot/src/game/GameApplication.cs
index f53ca90..587b2f2 100644
--- a/DungeonShooting_Godot/src/game/GameApplication.cs
+++ b/DungeonShooting_Godot/src/game/GameApplication.cs
@@ -61,6 +61,8 @@
public override void _EnterTree()
{
+ ActivityObject.IsDebug = Debug;
+
GlobalNodeRoot = GetNode(GlobalNodeRootPath);
// 初始化鼠标
Cursor = CursorPack.Instance();
diff --git a/DungeonShooting_Godot/src/game/role/StateBase.cs b/DungeonShooting_Godot/src/game/role/StateBase.cs
index 531f049..d2a7586 100644
--- a/DungeonShooting_Godot/src/game/role/StateBase.cs
+++ b/DungeonShooting_Godot/src/game/role/StateBase.cs
@@ -6,9 +6,14 @@
public abstract class StateBase where T : ActivityObject where S : Enum
{
///
- /// 当前状态对象对应的状态枚举类型
+ /// 当前活跃的状态对象实例
///
- public S StateType { get; }
+ public StateBase CurrStateBase => StateController.CurrStateBase;
+
+ ///
+ /// 当前对象对应的状态枚举
+ ///
+ public S State { get; }
///
/// 当前状态对象挂载的角色对象
@@ -22,7 +27,7 @@
public StateBase(S state)
{
- StateType = state;
+ State = state;
}
///
@@ -36,7 +41,7 @@
}
///
- /// 物理帧每帧更新
+ /// 如果当前状态已被激活, 物理帧每帧更新
///
public virtual void PhysicsProcess(float delta)
{
@@ -60,4 +65,28 @@
{
}
+
+ ///
+ /// 当启用 debug 后调用该函数, 调试绘制, 需要调用 Master 身上的绘制函数
+ ///
+ public virtual void DebugDraw()
+ {
+
+ }
+
+ ///
+ /// 立即切换到下一个指定状态, 并且这一帧会被调用 PhysicsProcess
+ ///
+ public void ChangeState(S next, params object[] args)
+ {
+ StateController.ChangeState(next, args);
+ }
+
+ ///
+ /// 切换到下一个指定状态, 下一帧才会调用 PhysicsProcess
+ ///
+ public void ChangeStateLate(S next, params object[] args)
+ {
+ StateController.ChangeStateLate(next, args);
+ }
}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/role/StateController.cs b/DungeonShooting_Godot/src/game/role/StateController.cs
index ad9557d..f12bca1 100644
--- a/DungeonShooting_Godot/src/game/role/StateController.cs
+++ b/DungeonShooting_Godot/src/game/role/StateController.cs
@@ -8,11 +8,10 @@
public class StateController : Component where T : ActivityObject where S : Enum
{
///
- /// 当前活跃的状态
+ /// 当前活跃的状态对象实例
///
- public StateBase CurrStateBase => _currStateBase;
- private StateBase _currStateBase;
-
+ public StateBase CurrStateBase { get; private set; }
+
///
/// 负责存放状态实例对象
///
@@ -22,7 +21,7 @@
/// 记录下当前帧是否有改变的状态
///
private bool _isChangeState;
-
+
public override void PhysicsProcess(float delta)
{
_isChangeState = false;
@@ -37,19 +36,28 @@
}
}
+ public override void DebugDraw()
+ {
+ if (CurrStateBase != null)
+ {
+ CurrStateBase.DebugDraw();
+ }
+ }
+
///
- /// 往状态机力注册一个新的状态
+ /// 往状态机里注册一个新的状态实例
///
public void Register(StateBase stateBase)
{
- if (GetStateInstance(stateBase.StateType) != null)
+ if (GetStateInstance(stateBase.State) != null)
{
GD.PrintErr("当前状态已经在状态机中注册:", stateBase);
return;
}
+
stateBase.Master = ActivityObject as T;
stateBase.StateController = this;
- _states.Add(stateBase.StateType, stateBase);
+ _states.Add(stateBase.State, stateBase);
}
///
@@ -82,29 +90,31 @@
///
private void _changeState(bool late, S next, params object[] arg)
{
- if (_currStateBase != null && _currStateBase.StateType.Equals(next))
+ if (CurrStateBase != null && CurrStateBase.State.Equals(next))
{
return;
}
+
var newState = GetStateInstance(next);
if (newState == null)
{
GD.PrintErr("当前状态机未找到相应状态:" + next);
return;
}
- if (_currStateBase == null)
+
+ if (CurrStateBase == null)
{
- _currStateBase = newState;
+ CurrStateBase = newState;
newState.Enter(default, arg);
}
- else if (_currStateBase.CanChangeState(next))
+ else if (CurrStateBase.CanChangeState(next))
{
_isChangeState = !late;
- var prev = _currStateBase.StateType;
- _currStateBase.Exit(next);
+ var prev = CurrStateBase.State;
+ CurrStateBase.Exit(next);
GD.Print("nextState => " + next);
- _currStateBase = newState;
- _currStateBase.Enter(prev, arg);
+ CurrStateBase = newState;
+ CurrStateBase.Enter(prev, arg);
}
}
}
\ 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
index 720a970..dc0bfb4 100644
--- a/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs
+++ b/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs
@@ -30,6 +30,11 @@
public float ViewRange { get; set; } = 200;
///
+ /// 发现玩家后的视野半径
+ ///
+ public float TailAfterViewRange { get; set; } = 250;
+
+ ///
/// 背后的视野半径, 单位像素
///
public float BackViewRange { get; set; } = 50;
diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiNormalState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiNormalState.cs
index 6939a45..8eb006b 100644
--- a/DungeonShooting_Godot/src/game/role/enemy/state/AiNormalState.cs
+++ b/DungeonShooting_Godot/src/game/role/enemy/state/AiNormalState.cs
@@ -20,7 +20,7 @@
if (Master.IsInViewRange(playerPos) && Master.TestViewRayCast(playerPos) == null)
{
//发现玩家
- StateController.ChangeStateLate(AIStateEnum.AITailAfter);
+ ChangeStateLate(AIStateEnum.AITailAfter);
}
}
}
diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiTailAfterState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiTailAfterState.cs
index 5fe44e0..07396bc 100644
--- a/DungeonShooting_Godot/src/game/role/enemy/state/AiTailAfterState.cs
+++ b/DungeonShooting_Godot/src/game/role/enemy/state/AiTailAfterState.cs
@@ -1,32 +1,47 @@
+using Godot;
+
///
/// AI 发现玩家
///
public class AiTailAfterState : StateBase
{
-
+ //导航目标点刷新计时器
private float _navigationUpdateTimer = 0;
+ private float _navigationInterval = 0.3f;
+
+ private bool _isInView = true;
+
+ private float _viewTimer = 0;
public AiTailAfterState() : base(AIStateEnum.AITailAfter)
{
}
+ public override void Enter(AIStateEnum prev, params object[] args)
+ {
+ _isInView = true;
+ _navigationUpdateTimer = 0;
+ }
+
public override void PhysicsProcess(float delta)
{
- //跟随玩家
var master = Master;
if (master.NavigationAgent2D.IsNavigationFinished())
{
return;
}
- var playerGlobalPosition = GameApplication.Instance.Room.Player.GlobalPosition;
- //临时处理, 让敌人跟随玩家
+ var masterPos = master.GlobalPosition;
+ var playerPos = GameApplication.Instance.Room.Player.GlobalPosition;
+
+ //更新玩家位置
if (_navigationUpdateTimer <= 0)
{
- _navigationUpdateTimer = 0.2f;
- if (master.NavigationAgent2D.GetTargetLocation() != playerGlobalPosition)
+ //每隔一段时间秒更改目标位置
+ _navigationUpdateTimer = _navigationInterval;
+ if (master.NavigationAgent2D.GetTargetLocation() != playerPos)
{
- master.NavigationAgent2D.SetTargetLocation(playerGlobalPosition);
+ master.NavigationAgent2D.SetTargetLocation(playerPos);
}
}
else
@@ -34,10 +49,42 @@
_navigationUpdateTimer -= delta;
}
+ //计算移动
var nextPos = master.NavigationAgent2D.GetNextLocation();
- master.LookTargetPosition(playerGlobalPosition);
+ master.LookTargetPosition(playerPos);
master.AnimatedSprite.Animation = AnimatorNames.Run;
master.Velocity = (nextPos - master.GlobalPosition - master.NavigationPoint.Position).Normalized() * master.MoveSpeed;
master.CalcMove(delta);
+
+ //检测玩家是否在视野内, 此时视野可穿墙, 直接检测距离即可
+ _isInView = masterPos.DistanceSquaredTo(playerPos) <= master.TailAfterViewRange * master.TailAfterViewRange;
+ if (_isInView)
+ {
+ _viewTimer = 0;
+ }
+ else //超出视野
+ {
+ if (_viewTimer > 10) //10秒
+ {
+ ChangeStateLate(AIStateEnum.AIProbe);
+ }
+ else
+ {
+ _viewTimer += delta;
+ }
+ }
+ }
+
+ public override void DebugDraw()
+ {
+ var playerPos = GameApplication.Instance.Room.Player.GlobalPosition;
+ if (_isInView)
+ {
+ Master.DrawLine(Vector2.Zero, Master.ToLocal(playerPos), Colors.Red);
+ }
+ else
+ {
+ Master.DrawLine(Vector2.Zero, Master.ToLocal(playerPos), Colors.Blue);
+ }
}
}
\ No newline at end of file