using FL.Battle.Actions; using FL.Battle.Skills; using System; using System.Collections.Generic; using UnityEngine; using XGame; using XGame.Database; using XGame.Framework; using XGame.Framework.Components; namespace FL.Battle.Components.AI { public interface IBulletAIContext { long UID { get; } long MasterId { get; } int TableId { get; } ITargetSelector Selector { get; } Vector3 Position { get; } VfxComponent Vfx { get; } MoveComponent Move { get; } } /// /// AI组件和具体的EntityView绑定,其他Component最好不要尝试获取AI组件 /// public class BulletAI : Component { /// /// 已攻击过的目标id /// private HashSet _lastTargetIds = new HashSet(); private SkillTable _skill; private SkillVfxsTable _skillVfxs; protected override void OnDispose() { _skill = null; _skillVfxs = null; _lastTargetIds.Clear(); } public void Start(long targetId, Vector3 targetPosition, EEntityType targetType, int delay) { _skill = SkillTableRepo.Get(Context.TableId); _skillVfxs = SkillVfxsTableRepo.Get(Context.TableId); var bulletType = (ESkillVfxType)_skill.SkillVfxType; _lastTargetIds.Clear(); // 子弹的移动方向 var direction = Vector3.Normalize(targetPosition - Context.Position); var moveArgs = new MoveArgs() { target = targetPosition, speed = _skillVfxs.SkillVfxSpeed, timeScale = 1, delay = delay, moveType = (EMoveType)(_skillVfxs.SkillVfxMoveType - 1), onComplete = () => { if (bulletType == ESkillVfxType.BulletMortar) { MortarExplode(targetPosition, targetType); End(); return; } var target = Context.Selector.GetTarget(targetId); if (target == null || target.IsDead) { //Log.Debug($"子弹移动到目标位置, 目标已死亡. Target:{targetId} Bullet:{Context.TableId}"); End(); return; } PlayHitVfx(_skillVfxs.HitVfx, targetPosition); target.Calculation.Damage(-_skill.Damage, Context.MasterId, _skill); switch (bulletType) { case ESkillVfxType.BulletSplit: // 分裂 _lastTargetIds.Add(targetId); Split(direction, target.Entity.EntityType); End(); break; case ESkillVfxType.BulletJump: // 弹跳 _lastTargetIds.Add(targetId); Jump(target.Entity.EntityType, 1); break; default: End(); break; } } }; if (Enum.TryParse(_skillVfxs.SkillVfxMoveEase, true, out var ease)) { moveArgs.ease = ease; } Context.Move.MoveTo(moveArgs); } /// /// 受击特效 /// /// /// private void PlayHitVfx(string hitVfx, Vector3 targetPosition) { var actPosition = ObjectPool.Acquire(); actPosition.position = targetPosition; var hitArgs = ObjectPool.Acquire(); hitArgs.vfxName = hitVfx; hitArgs.duration = _skillVfxs.HitVfxTime; hitArgs.followType = EVfxFollowType.World; hitArgs.action = actPosition; Context.Vfx.Play(hitArgs); } /// /// 子弹分裂 /// DamageArgs[数量,夹角(为零则就近原则找目标),缩放万分比] /// DamageArgs[1]大于零时,以子弹的初始移动方向为中心,按顺序(先左后右)累加角度获取分裂后子弹的方向 /// 怪物在该方向上则命中,否则子弹按直线飞出屏幕 /// /// 子弹的移动方向 /// 目标类型 private void Split(Vector3 direction, EEntityType targetType) { var childCount = _skill.DamageArgs[0]; var childAngle = _skill.DamageArgs[1]; var childScale = _skill.DamageArgs[2]; var from = Context.Position; var finderInfo = new FinderInfo() { uid = Context.UID, position = from, radius = 0, }; for (var i = 0; i < childCount; i++) { var target = Context.Selector.FindSync(finderInfo, targetType, ETargetFindType.Nearest, (a) => { if (_lastTargetIds.Contains(a.Entity.EntityId)) { return true; } if (childAngle == 0) return false; var tarAngle = ((i + 1) / 2) * childAngle; var tempDir = Vector3.Normalize(a.Position - from); // angle:[0, 180] var angle = Vector3.Angle(direction, tempDir); //Log.Debug($"Bullet Split index:{i} direction:{direction} temp:{tempDir} angle:{angle} tar:{tarAngle}"); if (Mathf.Abs(angle - tarAngle) < 20) { //角度范围-5到5 if (i == 0) return false; //cross.z > 0 目标向量在方向向量的左侧 var cross = Vector3.Cross(direction, tempDir); if (i % 2 == 0) { // i为偶数在右边 return cross.z > 0; } return cross.z < 0; } return true; }); if (target == null && childAngle == 0) { continue; } Vector3 to; if (target != null) { _lastTargetIds.Add(target.Entity.EntityId); to = target.Position; } else { if (i == 0) { to = from + direction * 12; } else { // i为偶数在右边 负数 var sign = i % 2 == 0 ? -1 : 1; var tarAngle = ((i + 1) / 2) * childAngle; //var tempDir = direction.RotateLeft(sign * tarAngle); //var angle = Vector3.Angle(direction, tempDir); //Log.Debug($"Bullet Split index:{i} direction:{direction} temp:{tempDir} angle:{angle} tar:{tarAngle} {Vector3.Angle(direction, Vector3.up)} {Vector3.Angle(tempDir, Vector3.up)}"); to = from + direction.RotateLeft(sign * tarAngle) * 12; } } //子弹移动 var duration = Mathf.CeilToInt(Vector3.Distance(from, to) * 1000 / _skillVfxs.SkillVfxSpeed); if (duration == 0) { // 位置重合,直接结算伤害 Log.Debug($"子弹分裂目标重合: from:{from} to:{to} speed:{_skillVfxs.SkillVfxSpeed}"); target?.Calculation.Damage(-_skill.Damage, Context.MasterId, _skill); continue; } var actMove = ObjectPool.Acquire(); actMove.args = new MoveActionArgs() { from = from, to = to, duration = duration }; //子弹特效 var bulletArgs = ObjectPool.Acquire(); bulletArgs.vfxName = _skillVfxs.SkillVfx; bulletArgs.duration = duration; bulletArgs.followType = EVfxFollowType.World; bulletArgs.action = actMove; if (target != null) { var skill = _skill; var masterId = Context.MasterId; bulletArgs.onFinish = () => { if (target == null || target.IsDead) { return; } target.Calculation.Damage(-skill.Damage, masterId, skill); }; //受击特效位置 var actPosition = ObjectPool.Acquire(); actPosition.position = to; //受击特效 var hitArgs = ObjectPool.Acquire(); hitArgs.vfxName = _skillVfxs.HitVfx; hitArgs.duration = _skillVfxs.HitVfxTime; hitArgs.followType = EVfxFollowType.World; hitArgs.action = actPosition; bulletArgs.next = hitArgs; } Context.Vfx.Play(bulletArgs); } } /// /// 弹跳 /// DamageArgs[弹跳次数,伤害递减万分比] /// /// 目标类型 /// 弹跳次数 private void Jump(EEntityType targetType, int jumpTimes) { var jumpLimit = _skill.DamageArgs[0]; var hurtReduce = _skill.DamageArgs[1]; var finderInfo = new FinderInfo() { uid = Context.UID, position = Context.Position, radius = 0, }; var target = Context.Selector.FindSync(finderInfo, targetType, ETargetFindType.Nearest, (a) => { if (_lastTargetIds.Contains(a.Entity.EntityId)) { return true; } return false; }); if (target == null) { End(); return; } var targetId = target.Entity.EntityId; _lastTargetIds.Add(targetId); var tarPosition = target.Position; var moveArgs = new MoveArgs() { target = tarPosition, speed = _skillVfxs.SkillVfxSpeed * 0.8f, timeScale = 1, onComplete = () => { target = Context.Selector.GetTarget(targetId); if (target == null || target.IsDead) { Log.Debug($"子弹移动到目标位置, 目标已死亡. Target:{targetId} Bullet:{Context.TableId}"); End(); return; } // 受击特效 PlayHitVfx(_skillVfxs.HitVfx, tarPosition); // 计算受击伤害 target.Calculation.Damage(-JumpHurt(_skill.Damage, jumpTimes, hurtReduce), Context.MasterId, _skill); jumpTimes++; if (jumpTimes > jumpLimit) { End(); return; } Jump(targetType, jumpTimes); } }; Context.Move.MoveTo(moveArgs); } private int JumpHurt(int baseVal, int jumpTimes, int hurtReduce) { float hurtVal = baseVal; for (var i = 0; i < jumpTimes; i++) { hurtVal *= (1 - hurtReduce * 0.0001f); } return Mathf.RoundToInt(hurtVal); } /// /// 迫击炮爆炸 /// /// /// private void MortarExplode(Vector3 targetPosition, EEntityType targetType) { var hitRadius = _skill.Ranges[0]; var explodeRadius = _skill.Ranges[1]; var explodeHurt = _skill.DamageArgs[0]; PlayHitVfx(_skillVfxs.HitVfx, targetPosition); // 查找受击目标 var finderInfo = new FinderInfo() { uid = Context.UID, position = Context.Position, radius = hitRadius, }; var hitTarget = Context.Selector.FindSync(finderInfo, targetType, ETargetFindType.NearestAlive); if (hitTarget != null) { hitTarget.Calculation.Damage(-_skill.Damage, Context.MasterId, _skill); } // 查找爆炸受击目标 finderInfo.radius = explodeRadius; var targets = new List(); if (Context.Selector.FindTargets(finderInfo, targetType, ETargetFindType.InRangeAlive, null, ref targets)) { foreach (var explodeTar in targets) { explodeTar.Calculation.Damage(-explodeHurt, Context.MasterId, _skill); } } } /// /// 结束 /// private void End() { _skill = null; _skillVfxs = null; _lastTargetIds.Clear(); EventSingle.Instance.Notify(EventDefine.GameMainMapRemoveSimpleEntity, Context.UID); } } }