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";