diff --git a/DungeonShooting_Godot/scene/Main.tscn b/DungeonShooting_Godot/scene/Main.tscn
index 35a4274..aff7b6d 100644
--- a/DungeonShooting_Godot/scene/Main.tscn
+++ b/DungeonShooting_Godot/scene/Main.tscn
@@ -24,7 +24,6 @@
[node name="Main" type="Node2D"]
script = ExtResource( 3 )
-Debug = true
CursorPack = ExtResource( 4 )
RoomPath = NodePath("ViewCanvas/ViewportContainer/Viewport/Room")
ViewportPath = NodePath("ViewCanvas/ViewportContainer/Viewport")
diff --git a/DungeonShooting_Godot/src/framework/ActivityObject.cs b/DungeonShooting_Godot/src/framework/ActivityObject.cs
index effd461..530d06a 100644
--- a/DungeonShooting_Godot/src/framework/ActivityObject.cs
+++ b/DungeonShooting_Godot/src/framework/ActivityObject.cs
@@ -17,7 +17,7 @@
///
/// 当前物体类型id, 用于区分是否是同一种物体, 如果不是通过 ActivityObject.Create() 函数创建出来的对象那么 ItemId 为 null
///
- public string ItemId { get; internal set; }
+ public string ItemId { get; private set; }
///
/// 是否放入 ySort 节点下
diff --git a/DungeonShooting_Godot/src/game/item/weapon/gun/Gun.cs b/DungeonShooting_Godot/src/game/item/weapon/gun/Gun.cs
index 4d9fa2a..eddf18b 100644
--- a/DungeonShooting_Godot/src/game/item/weapon/gun/Gun.cs
+++ b/DungeonShooting_Godot/src/game/item/weapon/gun/Gun.cs
@@ -34,8 +34,8 @@
//开火前延时
DelayedTime = 0f;
//攻击距离
- MinDistance = 500;
- MaxDistance = 600;
+ MinDistance = 300;
+ MaxDistance = 400;
//发射子弹数量
MinFireBulletCount = 1;
MaxFireBulletCount = 1;
@@ -74,8 +74,8 @@
//开火前延时
DelayedTime = 0f;
//攻击距离
- MinDistance = 500;
- MaxDistance = 600;
+ MinDistance = 250;
+ MaxDistance = 300;
//发射子弹数量
MinFireBulletCount = 1;
MaxFireBulletCount = 1;
diff --git a/DungeonShooting_Godot/src/game/item/weapon/gun/Shotgun.cs b/DungeonShooting_Godot/src/game/item/weapon/gun/Shotgun.cs
index ca60708..3e9d8e3 100644
--- a/DungeonShooting_Godot/src/game/item/weapon/gun/Shotgun.cs
+++ b/DungeonShooting_Godot/src/game/item/weapon/gun/Shotgun.cs
@@ -31,8 +31,8 @@
//开火前延时
DelayedTime = 0f;
//攻击距离
- MinDistance = 500;
- MaxDistance = 600;
+ MinDistance = 200;
+ MaxDistance = 250;
//发射子弹数量
MinFireBulletCount = 1;
MaxFireBulletCount = 1;
@@ -81,7 +81,7 @@
GameApplication.Instance.Room.GetRoot(true).AddChild(sprite);
//播放射击音效
- SoundManager.PlaySoundEffectPosition(ResourcePath.resource_sound_sfx_ordinaryBullet_ogg, GameApplication.Instance.ViewToGlobalPosition(GlobalPosition), 6f);
+ SoundManager.PlaySoundEffectPosition(ResourcePath.resource_sound_sfx_ordinaryBullet3_mp3, GameApplication.Instance.ViewToGlobalPosition(GlobalPosition), -15);
}
protected override void OnShoot(float fireRotation)
diff --git a/DungeonShooting_Godot/src/game/role/Role.cs b/DungeonShooting_Godot/src/game/role/Role.cs
index ae5d0f2..80a19ee 100644
--- a/DungeonShooting_Godot/src/game/role/Role.cs
+++ b/DungeonShooting_Godot/src/game/role/Role.cs
@@ -103,6 +103,7 @@
{
int temp = _maxHp;
_maxHp = value;
+ //护盾值改变
if (temp != _maxHp) OnChangeMaxHp(_maxHp);
}
}
@@ -118,6 +119,9 @@
{
int temp = _shield;
_shield = value;
+ //护盾被破坏
+ if (temp > 0 && _shield <= 0) OnShieldDamage();
+ //护盾值改变
if (temp != _shield) OnChangeShield(_shield);
}
}
@@ -184,6 +188,13 @@
}
///
+ /// 当护盾被破坏时调用
+ ///
+ protected virtual void OnShieldDamage()
+ {
+ }
+
+ ///
/// 当受伤时调用
///
/// 受到的伤害
@@ -451,10 +462,18 @@
/// 伤害的量
public virtual void Hurt(int damage)
{
- Hp -= damage;
+ OnHit(damage);
+ if (Shield > 0)
+ {
+ Shield -= damage;
+ }
+ else
+ {
+ Hp -= damage;
+ }
+
AnimationPlayer.Stop();
AnimationPlayer.Play("hit");
- OnHit(damage);
}
///
diff --git a/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs b/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs
index 0d21d27..e27189d 100644
--- a/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs
+++ b/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs
@@ -93,8 +93,9 @@
StateController.Register(new AiNormalState());
StateController.Register(new AiProbeState());
StateController.Register(new AiTailAfterState());
- StateController.Register(new AiTargetInViewState());
+ StateController.Register(new AiFollowUpState());
StateController.Register(new AiLeaveForState());
+ StateController.Register(new AiSurroundState());
}
public override void _Ready()
@@ -138,7 +139,7 @@
for (var i = 0; i < _enemies.Count; i++)
{
var enemy = _enemies[i];
- if (enemy.StateController.CurrState == AiStateEnum.AiTargetInView) //目标在视野内
+ if (enemy.StateController.CurrState == AiStateEnum.AiFollowUp) //目标在视野内
{
IsFindTarget = true;
FindTargetPosition = Player.Current.GetCenterPosition();
@@ -170,6 +171,21 @@
}
///
+ /// 获取武器攻击范围 (最大距离值与最小距离的中间值)
+ ///
+ /// 从最小到最大距离的过渡量, 0 - 1, 默认 0.5
+ public float GetWeaponRange(float weight = 0.5f)
+ {
+ if (Holster.ActiveWeapon != null)
+ {
+ var attribute = Holster.ActiveWeapon.Attribute;
+ return Mathf.Lerp(attribute.MinDistance, attribute.MaxDistance, weight);
+ }
+
+ return 0;
+ }
+
+ ///
/// 返回目标点是否在视野范围内
///
public bool IsInViewRange(Vector2 target)
diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AIStateEnum.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AIStateEnum.cs
index d7f44b6..e94b6fb 100644
--- a/DungeonShooting_Godot/src/game/role/enemy/state/AIStateEnum.cs
+++ b/DungeonShooting_Godot/src/game/role/enemy/state/AIStateEnum.cs
@@ -18,7 +18,11 @@
///
AiTailAfter,
///
- /// 目标在视野内
+ /// 目标在视野内, 跟进目标
///
- AiTargetInView,
+ AiFollowUp,
+ ///
+ /// 距离足够进, 在目标附近随机移动
+ ///
+ AiSurround,
}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiFollowUpState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiFollowUpState.cs
new file mode 100644
index 0000000..af61de8
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/role/enemy/state/AiFollowUpState.cs
@@ -0,0 +1,112 @@
+
+using Godot;
+
+///
+/// 目标在视野范围内, 跟进目标
+///
+public class AiFollowUpState : StateBase
+{
+
+ ///
+ /// 目标是否在视野内
+ ///
+ public bool IsInView;
+
+ //导航目标点刷新计时器
+ private float _navigationUpdateTimer = 0;
+ private float _navigationInterval = 0.3f;
+
+ //是否存在下一个移动点
+ //private bool _hasNextPosition;
+ //private Vector2 _nextPosition;
+
+ public AiFollowUpState() : base(AiStateEnum.AiFollowUp)
+ {
+ }
+
+ public override void Enter(AiStateEnum prev, params object[] args)
+ {
+ _navigationUpdateTimer = 0;
+ IsInView = true;
+ }
+
+ public override void PhysicsProcess(float delta)
+ {
+ var playerPos = Player.Current.GetCenterPosition();
+
+ //更新玩家位置
+ if (_navigationUpdateTimer <= 0)
+ {
+ //每隔一段时间秒更改目标位置
+ _navigationUpdateTimer = _navigationInterval;
+ if (Master.NavigationAgent2D.GetTargetLocation() != playerPos)
+ {
+ Master.NavigationAgent2D.SetTargetLocation(playerPos);
+ }
+ }
+ else
+ {
+ _navigationUpdateTimer -= delta;
+ }
+
+ var masterPosition = Master.GlobalPosition;
+
+ //是否在攻击范围内
+ var inAttackRange = false;
+
+ var weapon = Master.Holster.ActiveWeapon;
+ if (weapon != null)
+ {
+ inAttackRange = masterPosition.DistanceSquaredTo(playerPos) <= Mathf.Pow(Master.GetWeaponRange(0.7f), 2);
+ }
+
+ //枪口指向玩家
+ Master.LookTargetPosition(playerPos);
+ if (!Master.NavigationAgent2D.IsNavigationFinished())
+ {
+ //计算移动
+ var nextPos = Master.NavigationAgent2D.GetNextLocation();
+ Master.AnimatedSprite.Animation = AnimatorNames.Run;
+ Master.Velocity = (nextPos - masterPosition - Master.NavigationPoint.Position).Normalized() *
+ Master.MoveSpeed;
+ Master.CalcMove(delta);
+ }
+
+ //检测玩家是否在视野内
+ if (Master.IsInTailAfterViewRange(playerPos))
+ {
+ IsInView = !Master.TestViewRayCast(playerPos);
+ //关闭射线检测
+ Master.TestViewRayCastOver();
+ }
+ else
+ {
+ IsInView = false;
+ }
+
+ if (IsInView)
+ {
+ if (inAttackRange) //在攻击范围内
+ {
+ //发起攻击
+ Master.EnemyAttack();
+
+ //距离够近, 可以切换到环绕模式
+ if (Master.GlobalPosition.DistanceSquaredTo(playerPos) <= Mathf.Pow(weapon.Attribute.MinDistance, 2) * 0.7f)
+ {
+ ChangeStateLate(AiStateEnum.AiSurround);
+ }
+ }
+ }
+ else
+ {
+ ChangeStateLate(AiStateEnum.AiTailAfter);
+ }
+ }
+
+ public override void DebugDraw()
+ {
+ var playerPos = GameApplication.Instance.Room.Player.GetCenterPosition();
+ Master.DrawLine(new Vector2(0, -8), Master.ToLocal(playerPos), Colors.Red);
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiLeaveForState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiLeaveForState.cs
index ccfdfb4..2b3c3e3 100644
--- a/DungeonShooting_Godot/src/game/role/enemy/state/AiLeaveForState.cs
+++ b/DungeonShooting_Godot/src/game/role/enemy/state/AiLeaveForState.cs
@@ -63,7 +63,7 @@
//关闭射线检测
Master.TestViewRayCastOver();
//切换成发现目标状态
- ChangeStateLate(AiStateEnum.AiTargetInView);
+ ChangeStateLate(AiStateEnum.AiFollowUp);
return;
}
else
diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiNormalState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiNormalState.cs
index c451a40..8260a7e 100644
--- a/DungeonShooting_Godot/src/game/role/enemy/state/AiNormalState.cs
+++ b/DungeonShooting_Godot/src/game/role/enemy/state/AiNormalState.cs
@@ -65,6 +65,7 @@
}
else if (_pauseTimer >= 0)
{
+ Master.AnimatedSprite.Animation = AnimatorNames.Idle;
_pauseTimer -= delta;
}
else if (_isMoveOver) //没发现玩家, 且已经移动完成
diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiSurroundState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiSurroundState.cs
new file mode 100644
index 0000000..aaa465d
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/role/enemy/state/AiSurroundState.cs
@@ -0,0 +1,125 @@
+
+using Godot;
+
+///
+/// 距离足够进, 在目标附近随机移动
+///
+public class AiSurroundState : StateBase
+{
+ ///
+ /// 目标是否在视野内
+ ///
+ public bool IsInView = true;
+
+ //是否移动结束
+ private bool _isMoveOver;
+
+ //移动停顿计时器
+ private float _pauseTimer;
+
+ //下一个移动点
+ private Vector2 _nextPosition;
+
+ public AiSurroundState() : base(AiStateEnum.AiSurround)
+ {
+ }
+
+ public override void Enter(AiStateEnum prev, params object[] args)
+ {
+ IsInView = true;
+ _isMoveOver = true;
+ _pauseTimer = 0;
+ }
+
+ public override void PhysicsProcess(float delta)
+ {
+ var playerPos = Player.Current.GetCenterPosition();
+ var weapon = Master.Holster.ActiveWeapon;
+
+ //枪口指向玩家
+ Master.LookTargetPosition(playerPos);
+
+ //检测玩家是否在视野内
+ if (Master.IsInTailAfterViewRange(playerPos))
+ {
+ IsInView = !Master.TestViewRayCast(playerPos);
+ //关闭射线检测
+ Master.TestViewRayCastOver();
+ }
+ else
+ {
+ IsInView = false;
+ }
+
+ if (IsInView)
+ {
+ if (_pauseTimer >= 0)
+ {
+ Master.AnimatedSprite.Animation = AnimatorNames.Idle;
+ _pauseTimer -= delta;
+ }
+ else if (_isMoveOver) //移动已经完成
+ {
+ RunOver(playerPos);
+ _isMoveOver = false;
+ }
+ else
+ {
+ //计算移动
+ var nextPos = Master.NavigationAgent2D.GetNextLocation();
+ Master.AnimatedSprite.Animation = AnimatorNames.Run;
+ Master.Velocity = (nextPos - Master.GlobalPosition - Master.NavigationPoint.Position).Normalized() *
+ Master.MoveSpeed;
+ Master.CalcMove(delta);
+
+ if (Master.NavigationAgent2D.IsNavigationFinished()) //到达终点
+ {
+ _pauseTimer = Utils.RandRange(0f, 0.5f);
+ _isMoveOver = true;
+ }
+ else
+ {
+ var lastSlideCollision = Master.GetLastSlideCollision();
+ if (lastSlideCollision != null && lastSlideCollision.Collider is Role) //碰到其他角色
+ {
+ _pauseTimer = Utils.RandRange(0f, 0.3f);
+ _isMoveOver = true;
+ }
+ }
+
+ if (weapon != null)
+ {
+ var position = Master.GlobalPosition;
+ if (position.DistanceSquaredTo(playerPos) > Mathf.Pow(Master.GetWeaponRange(0.7f), 2)) //玩家离开正常射击范围
+ {
+ ChangeStateLate(AiStateEnum.AiFollowUp);
+ }
+ else
+ {
+ //发起攻击
+ Master.EnemyAttack();
+ }
+ }
+ }
+ }
+ else //目标离开视野
+ {
+ ChangeStateLate(AiStateEnum.AiTailAfter);
+ }
+ }
+
+ private void RunOver(Vector2 targetPos)
+ {
+ var distance = (int)(Master.Holster.ActiveWeapon.Attribute.MinDistance * 0.7f);
+ _nextPosition = new Vector2(
+ targetPos.x + Utils.RandRangeInt(-distance, distance),
+ targetPos.y + Utils.RandRangeInt(-distance, distance)
+ );
+ Master.NavigationAgent2D.SetTargetLocation(_nextPosition);
+ }
+
+ public override void DebugDraw()
+ {
+ Master.DrawLine(new Vector2(0, -8), Master.ToLocal(_nextPosition), Colors.White);
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiTailAfterState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiTailAfterState.cs
index fa04edb..32b2a97 100644
--- a/DungeonShooting_Godot/src/game/role/enemy/state/AiTailAfterState.cs
+++ b/DungeonShooting_Godot/src/game/role/enemy/state/AiTailAfterState.cs
@@ -66,7 +66,7 @@
//关闭射线检测
Master.TestViewRayCastOver();
//切换成发现目标状态
- ChangeStateLate(AiStateEnum.AiTargetInView);
+ ChangeStateLate(AiStateEnum.AiFollowUp);
return;
}
else
diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiTargetInViewState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiTargetInViewState.cs
deleted file mode 100644
index 3ee2124..0000000
--- a/DungeonShooting_Godot/src/game/role/enemy/state/AiTargetInViewState.cs
+++ /dev/null
@@ -1,97 +0,0 @@
-
-using Godot;
-
-///
-/// 目标在视野范围内, 发起攻击
-///
-public class AiTargetInViewState : StateBase
-{
-
- ///
- /// 是否在视野内
- ///
- public bool IsInView;
-
- //导航目标点刷新计时器
- private float _navigationUpdateTimer = 0;
- private float _navigationInterval = 0.3f;
-
- public AiTargetInViewState() : base(AiStateEnum.AiTargetInView)
- {
- }
-
- public override void Enter(AiStateEnum prev, params object[] args)
- {
- _navigationUpdateTimer = 0;
- IsInView = true;
- }
-
- public override void PhysicsProcess(float delta)
- {
- var playerPos = GameApplication.Instance.Room.Player.GetCenterPosition();
-
- //更新玩家位置
- if (_navigationUpdateTimer <= 0)
- {
- //每隔一段时间秒更改目标位置
- _navigationUpdateTimer = _navigationInterval;
- if (Master.NavigationAgent2D.GetTargetLocation() != playerPos)
- {
- Master.NavigationAgent2D.SetTargetLocation(playerPos);
- }
- }
- else
- {
- _navigationUpdateTimer -= delta;
- }
-
- var masterPosition = Master.GlobalPosition;
- var canMove = false;
-
- var weapon = Master.Holster.ActiveWeapon;
- if (weapon != null)
- {
- canMove = masterPosition.DistanceSquaredTo(playerPos) >= 50 * 50;
- }
-
- if (canMove)
- {
- if (!Master.NavigationAgent2D.IsNavigationFinished())
- {
- //计算移动
- var nextPos = Master.NavigationAgent2D.GetNextLocation();
- Master.LookTargetPosition(playerPos);
- Master.AnimatedSprite.Animation = AnimatorNames.Run;
- Master.Velocity = (nextPos - masterPosition - Master.NavigationPoint.Position).Normalized() * Master.MoveSpeed;
- Master.CalcMove(delta);
- }
- }
-
- //检测玩家是否在视野内
- if (Master.IsInTailAfterViewRange(playerPos))
- {
- IsInView = !Master.TestViewRayCast(playerPos);
- //关闭射线检测
- Master.TestViewRayCastOver();
- }
- else
- {
- IsInView = false;
- }
-
- if (IsInView)
- {
- Master.EnemyAttack();
- }
- else
- {
- ChangeStateLate(AiStateEnum.AiTailAfter);
- }
- }
-
- public override void DebugDraw()
- {
- var playerPos = GameApplication.Instance.Room.Player.GetCenterPosition();
- Master.DrawLine(new Vector2(0, -8), Master.ToLocal(playerPos), Colors.Red);
- }
-}
\ 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 50d18db..17d5396 100644
--- a/DungeonShooting_Godot/src/game/room/RoomManager.cs
+++ b/DungeonShooting_Godot/src/game/room/RoomManager.cs
@@ -80,12 +80,12 @@
//播放bgm
SoundManager.PlayMusic(ResourcePath.resource_sound_bgm_Intro_ogg, -17f);
- // var enemy1 = new Enemy();
- // enemy1.Name = "Enemy";
- // enemy1.PutDown(new Vector2(150, 300));
- // enemy1.PickUpWeapon(WeaponManager.GetGun("1003"));
- // enemy1.PickUpWeapon(WeaponManager.GetGun("1001"));
- //
+ var enemy1 = new Enemy();
+ enemy1.Name = "Enemy";
+ enemy1.PutDown(new Vector2(150, 300));
+ enemy1.PickUpWeapon(WeaponManager.GetGun("1003"));
+ enemy1.PickUpWeapon(WeaponManager.GetGun("1001"));
+
// for (int i = 0; i < 10; i++)
// {
// var enemyTemp = new Enemy();
@@ -94,13 +94,13 @@
// enemyTemp.PickUpWeapon(WeaponManager.GetGun("1003"));
// enemyTemp.PickUpWeapon(WeaponManager.GetGun("1001"));
// }
- //
- // var enemy2 = new Enemy();
- // enemy2.Name = "Enemy2";
- // enemy2.PutDown(new Vector2(540, 100));
- // enemy2.PickUpWeapon(WeaponManager.GetGun("1002"));
- // enemy2.PickUpWeapon(WeaponManager.GetGun("1004"));
- // enemy2.PickUpWeapon(WeaponManager.GetGun("1003"));
+
+ var enemy2 = new Enemy();
+ enemy2.Name = "Enemy2";
+ enemy2.PutDown(new Vector2(540, 100));
+ enemy2.PickUpWeapon(WeaponManager.GetGun("1002"));
+ enemy2.PickUpWeapon(WeaponManager.GetGun("1004"));
+ enemy2.PickUpWeapon(WeaponManager.GetGun("1003"));
//
var enemy3 = new Enemy();
enemy3.Name = "Enemy3";