#region 基础敌人设计思路 /* 敌人有三种状态: 状态1: 未发现玩家, 视野不可穿墙, 该状态下敌人移动比较规律, 移动速度较慢, 一旦玩家进入视野或者听到玩家枪声, 立刻切换至状态3, 该房间的敌人不能再回到状态1 状态2: 发现有玩家, 但不知道在哪, 视野不可穿墙, 该情况下敌人移动速度明显加快, 移动不规律, 一旦玩家进入视野或者听到玩家枪声, 立刻切换至状态3 状态3: 明确知道玩家的位置, 视野允许穿墙, 移动速度与状态2一致, 进入该状态时, 敌人之间会相互告知玩家所在位置, 并朝着玩家位置开火, 如果有墙格挡, 则有一定概率继续开火, 一旦玩家立刻敌人视野超哥一段时间, 敌人自动切换为状态2 敌人状态1只存在于少数房间内, 比如特殊房间, 大部分情况下敌人应该是状态2, 或者玩家进入房间时就被敌人发现 */ #endregion using System.Collections.Generic; using Godot; /// <summary> /// 基础敌人 /// </summary> public class Enemy : Role { /// <summary> /// 公共属性, 是否找到目标, 如果找到目标, 则所有敌人都会知道玩家的位置 /// </summary> public static bool IsFindTarget { get; private set; } /// <summary> /// 找到的目标的位置, 如果目标在视野内, 则一直更新 /// </summary> public static Vector2 FindTargetPosition { get; private set; } private static readonly List<Enemy> _enemies = new List<Enemy>(); /// <summary> /// 敌人身上的状态机控制器 /// </summary> public StateController<Enemy, AiStateEnum> StateController { get; } /// <summary> /// 视野半径, 单位像素, 发现玩家后改视野范围可以穿墙 /// </summary> public float ViewRange { get; set; } = 250; /// <summary> /// 发现玩家后的视野半径 /// </summary> public float TailAfterViewRange { get; set; } = 400; /// <summary> /// 背后的视野半径, 单位像素 /// </summary> public float BackViewRange { get; set; } = 50; /// <summary> /// 视野检测射线, 朝玩家打射线, 检测是否碰到墙 /// </summary> public RayCast2D ViewRay { get; } /// <summary> /// 导航代理 /// </summary> public NavigationAgent2D NavigationAgent2D { get; } /// <summary> /// 导航代理中点 /// </summary> public Position2D NavigationPoint { get; } private float _enemyAttackTimer = 0; public Enemy() : base(ResourcePath.prefab_role_Enemy_tscn) { StateController = new StateController<Enemy, AiStateEnum>(); AddComponent(StateController); AttackLayer = PhysicsLayer.Wall | PhysicsLayer.Props | PhysicsLayer.Player; Camp = CampEnum.Camp2; MoveSpeed = 30; Holster.SlotList[2].Enable = true; Holster.SlotList[3].Enable = true; //视野射线 ViewRay = GetNode<RayCast2D>("ViewRay"); NavigationPoint = GetNode<Position2D>("NavigationPoint"); NavigationAgent2D = NavigationPoint.GetNode<NavigationAgent2D>("NavigationAgent2D"); //PathSign = new PathSign(this, PathSignLength, GameApplication.Instance.Room.Player); //注册Ai状态机 StateController.Register(new AiNormalState()); StateController.Register(new AiProbeState()); StateController.Register(new AiTailAfterState()); StateController.Register(new AiFollowUpState()); StateController.Register(new AiLeaveForState()); StateController.Register(new AiSurroundState()); } public override void _Ready() { base._Ready(); //防撞速度计算 NavigationAgent2D.Connect("velocity_computed", this, nameof(OnVelocityComputed)); //默认状态 StateController.ChangeState(AiStateEnum.AiNormal); NavigationAgent2D.SetTargetLocation(GameApplication.Instance.Room.Player.GlobalPosition); } public override void _EnterTree() { if (!_enemies.Contains(this)) { _enemies.Add(this); } } public override void _ExitTree() { _enemies.Remove(this); } public override void _PhysicsProcess(float delta) { base._PhysicsProcess(delta); _enemyAttackTimer -= delta; } /// <summary> /// 更新敌人视野 /// </summary> public static void UpdateEnemiesView() { IsFindTarget = false; for (var i = 0; i < _enemies.Count; i++) { var enemy = _enemies[i]; if (enemy.StateController.CurrState == AiStateEnum.AiFollowUp) //目标在视野内 { IsFindTarget = true; FindTargetPosition = Player.Current.GetCenterPosition(); } } } /// <summary> /// Ai触发的攻击 /// </summary> public void EnemyAttack() { var weapon = Holster.ActiveWeapon; if (weapon != null) { if (weapon.Attribute.ContinuousShoot) //连发 { Attack(); } else //单发 { if (_enemyAttackTimer <= 0) { _enemyAttackTimer = 60f / weapon.Attribute.StartFiringSpeed; Attack(); } } } } /// <summary> /// 获取武器攻击范围 (最大距离值与最小距离的中间值) /// </summary> /// <param name="weight">从最小到最大距离的过渡量, 0 - 1, 默认 0.5</param> 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; } /// <summary> /// 返回目标点是否在视野范围内 /// </summary> public bool IsInViewRange(Vector2 target) { var isForward = IsPositionInForward(target); if (isForward) { if (GlobalPosition.DistanceSquaredTo(target) <= ViewRange * ViewRange) //没有超出视野半径 { return true; } } return false; } /// <summary> /// 返回目标点是否在跟随状态下的视野半径内 /// </summary> public bool IsInTailAfterViewRange(Vector2 target) { var isForward = IsPositionInForward(target); if (isForward) { if (GlobalPosition.DistanceSquaredTo(target) <= TailAfterViewRange * TailAfterViewRange) //没有超出视野半径 { return true; } } return false; } /// <summary> /// 调用视野检测, 如果被墙壁和其它物体遮挡, 则返回被挡住视野的物体对象, 视野无阻则返回 null /// </summary> public bool TestViewRayCast(Vector2 target) { ViewRay.Enabled = true; ViewRay.CastTo = ViewRay.ToLocal(target); ViewRay.ForceRaycastUpdate(); return ViewRay.IsColliding(); } /// <summary> /// 调用视野检测完毕后, 需要调用 TestViewRayCastOver() 来关闭视野检测射线 /// </summary> public void TestViewRayCastOver() { ViewRay.Enabled = false; } private void OnVelocityComputed(Vector2 velocity) { GD.Print("velocity: " + velocity); } }