using System; using System.Collections.Generic; using GAS.Runtime; using Sog; using UnityEngine; using xFrame; namespace CoreGame.Render { public static class BulletSrv { private static List s_RandomElementTypes = new List { ElementType.Fire, ElementType.Toxic, ElementType.Thunder }; // 追踪弹可能需要一个目标列表 public static CombatEntity[] CreateBulletWithEntity(GameAbilityContext ctx, int bulletId) { return null; } public static CombatEntity[] CreateBulletWithPoint(GameAbilityContext casterGaCtx, int bulletId, Fixed64 rotateAngle, bool randomElementType = false) { // 发射者死亡,不发射 var casterEid = casterGaCtx.ownerEnt.creationIndex; var ownerEnt = casterGaCtx.ownerEnt; if (ownerEnt.IsValid() == false) return null; if (ownerEnt.hasBullet) casterEid = ownerEnt.bullet.casterEid; else if(ownerEnt.hasZone) casterEid = ownerEnt.zone.casterEid; var caster = Contexts.Combat.GetEntity(casterEid); if (caster.IsValid() == false) return null; var casterProp = caster.property.container; var level = casterGaCtx.level; var gaInitCfg = casterGaCtx.abilitySpec.gaInitCfg; TraceLog.Assert(casterGaCtx.castPos.Count > 0, $"{casterGaCtx.abilitySpec.Ability.Name} CreateBulletWithPoint castPos.Count == 0"); // 释放位置外部已经选好了,必须有 var targetPos = TargetSelectSrv.GetCastTargetPos(casterGaCtx); var eid = casterGaCtx.castTarget.Count > 0 ? casterGaCtx.castTarget[0] : -1; var targetEnt = Contexts.Combat.GetEntity(eid); var tbBullet = BulletDescMgr.Instance.GetConfig(bulletId); if (tbBullet == null) return null; var bulletCount = (int)ExpressionEvaluator.CalcVal_WithPropAndGaCfg(tbBullet.bulletsAmount, casterProp, level, gaInitCfg, 3f); var existTime = (float)ExpressionEvaluator.CalcVal_WithPropAndGaCfg(tbBullet.duration, casterProp, level, gaInitCfg, 3f); var maxDistance = (float)ExpressionEvaluator.CalcVal_WithPropAndGaCfg(tbBullet.maxDistance, casterProp, level, gaInitCfg, 5f); var collisionTimes = (int)ExpressionEvaluator.CalcVal_WithPropAndGaCfg(tbBullet.collisionTimes, casterProp, level, gaInitCfg, 1f); var collisionInterval = (float)ExpressionEvaluator.CalcVal_WithPropAndGaCfg(tbBullet.collisionInterval, casterProp, level, gaInitCfg, 1f); var scale = (float)ExpressionEvaluator.CalcVal_WithPropAndGaCfg(tbBullet.scale, casterProp, level, gaInitCfg, 1f); var bounceTimes = (int)ExpressionEvaluator.CalcVal_WithPropAndGaCfg(tbBullet.bounceTimes, casterProp, level, gaInitCfg, 1f); var bulletEntities = new CombatEntity[bulletCount]; var speed = 0f; Fixed64 addAngle = 0; Fixed64 nowAngle = 0; var distance = 0f; var maxRandomAngle = 0f; var randomCurvePerpendicularControl = 0f; var randomCurveParallelControl = 0f; if (tbBullet.shootType == ShootType.Direct || tbBullet.shootType == ShootType.Curve) { maxRandomAngle = (float)ExpressionEvaluator.CalcVal_WithPropAndGaCfg(tbBullet.bulletRandomAngle, casterProp, level, gaInitCfg); speed = (float)ExpressionEvaluator.CalcVal_WithPropAndGaCfg(tbBullet.speed, casterProp, level, gaInitCfg, 10f); //飞行速度 var bulletAngle = tbBullet.fireAngle * BattleConst.TenThousandReverse; if (bulletAngle > 0 && bulletCount > 1) { var maxCastAngle = (float)ExpressionEvaluator.CalcVal_WithPropAndGaCfg(tbBullet.fireAngleMax, casterProp, level, gaInitCfg); var totalAngle = (bulletCount - 1) * bulletAngle; if (totalAngle > maxCastAngle) { totalAngle = maxCastAngle; bulletAngle = maxCastAngle / (bulletCount - 1); } nowAngle = -totalAngle * Fixed64._0_50 + rotateAngle; addAngle = bulletAngle; } else { nowAngle = rotateAngle; } if (tbBullet.shootType == ShootType.Curve) { randomCurvePerpendicularControl = (float)ExpressionEvaluator.CalcVal_WithPropAndGaCfg( tbBullet.moveParam1, casterProp, level, gaInitCfg, -1f); randomCurveParallelControl = (float)ExpressionEvaluator.CalcVal_WithPropAndGaCfg( tbBullet.moveParam2, casterProp, level, gaInitCfg, -1f); } } else if (tbBullet.shootType == ShootType.Surround) { speed = (float)ExpressionEvaluator.CalcVal_WithPropAndGaCfg(tbBullet.moveParam1, casterProp, level, gaInitCfg, 180f); //角速度 distance = (float)ExpressionEvaluator.CalcVal_WithPropAndGaCfg(tbBullet.moveParam2, casterProp, level, gaInitCfg, 2f); addAngle = 360f / bulletCount; if (targetEnt == null) return null; } var element = ElementType.None; var fromType = BulletFromType.None; if (casterGaCtx.gunEid > 0) { var gun = Contexts.Combat.GetEntity(casterGaCtx.gunEid); if (gun != null) { var gunGunData = gun.gunData; if (gunGunData != null) { var gunCfg = WeaponDescMgr.Instance.GetConfig(gunGunData.gunCfgId); element = gunGunData.elementType; fromType = (BulletFromType)((int)gunCfg.Type << 2 + 1); } } } for (var i = 0; i < bulletCount; i++) { var cb = CreateBulletEntity(caster, element, fromType, tbBullet, existTime, maxDistance, collisionTimes, collisionInterval, scale, speed, randomElementType); // 传递技能参数 cb.AddGaCtxTransfer(casterGaCtx.abilitySpec.gaParams, casterGaCtx.abilitySpec.gaInitCfg, casterGaCtx.level); cb.AddLogicTransform(Fixed64Vector2.zero, Fixed64Vector2.one,Fixed64Vector2.right); // cb.AddTransformProxy(Vector2.zero, Vector2.right, cb); if (tbBullet.shootType == ShootType.Direct) { AddDirectBullet(ownerEnt, cb, targetPos, nowAngle, maxRandomAngle); nowAngle += addAngle; } else if (tbBullet.shootType == ShootType.Surround) { AddSurroundBullet(targetEnt, cb, nowAngle, distance); nowAngle += addAngle; } else if (tbBullet.shootType == ShootType.Curve) { // AddCurveBullet(ownerEnt, cb, targetPos, nowAngle, maxRandomAngle, tbBullet.warningPrefab, // tbBullet.warningScale * BattleConst.TenThousandReverse, randomCurvePerpendicularControl, // randomCurveParallelControl); nowAngle += addAngle; } if (bounceTimes > 0) { var propertyComponent = cb.property; propertyComponent.SetProperty(PropertyDef.BulletBounceCount, bounceTimes, true); propertyComponent.SetProperty(PropertyDef.MaxBulletBounceCount, bounceTimes, true); } if (tbBullet.extraSkill > 0) { var context = AbilitySrv.GrantTransferAbility(cb, tbBullet.extraSkill, gaInitCfg, null, level); if (context == null) { XLog.LogWarning("AttachActiveAbility failed: " + tbBullet.extraSkill); } } // cb.LoadAsset(); bulletEntities[i] = cb; } caster.DispatchEvent(ClientEvent.OnShootBullets, bulletEntities); return bulletEntities; } private static CombatEntity CreateBulletEntity(CombatEntity caster, ElementType element, BulletFromType fromType, BulletDesc tbBullet, float existTime, float maxDistance, int collisionTimes, float collisionInterval, float scale, float speed, bool randomElementType) { if (caster.IsValid() == false) return null; var cb = Contexts.Combat.CreateEntity(); if (randomElementType) element = s_RandomElementTypes[RandomSrv.Range(0, s_RandomElementTypes.Count)]; var paramCount = tbBullet.colliderParam.Length; var colliderParam1 = paramCount > 0 ? tbBullet.colliderParam[0] * BattleConst.TenThousandReverse * scale : 0; var colliderParam2 = paramCount > 1 ? tbBullet.colliderParam[1] * BattleConst.TenThousandReverse * scale : 0; if (tbBullet.skill == 0 && tbBullet.endSkill == 0) { XLog.LogError($"{tbBullet.bulletID} 子弹配置不对,没有技能!!!!"); } cb.AddBullet(caster.creationIndex, element, fromType, tbBullet.bulletID, existTime, maxDistance, scale, speed, tbBullet.colliderType, colliderParam1, colliderParam2, tbBullet.collideWith, collisionTimes, collisionInterval, tbBullet.skill, tbBullet.endSkill, tbBullet.isTriggerHitSkillOnEnd != 0, ListPool.Pop()); var casterFaction = caster.faction; cb.AddFaction(casterFaction.faction, SelectUseType.Bullet, casterFaction.opposite); cb.AddProperty(); caster.property.TaskSnapShot(cb.property.container); cb.property.SetProperty(PropertyDef.BulletFlyDistanceMax, maxDistance, true); var ignore = ListPool.Pop(); ignore.Add(typeof(Animator)); var prefabPath = GetBulletPrefabPathByElementType(element, tbBullet); cb.AddAsset(new MainAssetParam() { path = prefabPath, parentNodeName = "RenderWorld", scaleParam = scale }, ignore); cb.AddAudio(cb.creationIndex); return cb; } /// /// 发射直线子弹 /// /// 创造者,可能是人,也可能是子弹等 /// 子弹实体 /// 目标实体 /// 偏转角度 /// 最大随机偏转角度 private static void AddDirectBullet(CombatEntity owner, CombatEntity bullet, Fixed64Vector2 target, Fixed64 nowAngle, Fixed64 maxRandomAngle) { var randomAngle = maxRandomAngle * RandomSrv.Range(-1f, 1f); GetFirePos(owner, bullet, out var pos, out var dir); if (target != pos ) { dir = target - pos; dir.Normalize(); } bullet.logicTransform.SetPosition(pos); Fixed64 angleRadians = FixedMath.Deg2Rad * (nowAngle + randomAngle); Fixed64 x = dir.x * FixedMath.Cos(angleRadians) - dir.y * FixedMath.Sin(angleRadians); Fixed64 y = dir.x * FixedMath.Sin(angleRadians) + dir.y * FixedMath.Cos(angleRadians); bullet.logicTransform.SetDirection(new Fixed64Vector2(x, y)); bullet.isDirectBullet = true; } /// /// 发射环绕子弹 /// /// 环绕目标实体 /// 子弹实体 /// 角度 /// 子弹和持有者的距离 private static void AddSurroundBullet(CombatEntity targetEnt, CombatEntity bullet, Fixed64 nowAngle, Fixed64 distance) { var transformProxy = bullet.logicTransform; GetFirePos(targetEnt, bullet, out var pos, out var dir); transformProxy.SetPosition(pos + Fixed64Vector2.right * distance); bullet.AddSurroundBullet(targetEnt.creationIndex, distance); bullet.surroundBullet.angle = nowAngle; } /// /// 发射抛物线子弹 /// /// 创造者,可能是人,也可能是子弹等 /// 子弹实体 /// 目标位置 /// 角度 /// 最大随机偏转角度 /// 爆炸预警prefab路径 /// 爆炸预警prefab大小 /// 垂直方向控制点随机参数 /// 平行方向控制点随机参数 private static void AddCurveBullet(CombatEntity owner, CombatEntity bulletEnt, Fixed64Vector2 targetPos, Fixed64 nowAngle, Fixed64 maxRandomAngle, string explosionRangePath, Fixed64 explosionRangeScale, Fixed64 randomCurvePerpendicularControl, Fixed64 randomCurveParallelControl) { Fixed64 randomAngle = maxRandomAngle * RandomSrv.Range(-1, 1); // var start = caster.transformProxy.position; // var dir = caster.transformProxy.direction; GetFirePos(owner, bulletEnt, out var start, out var dir); Fixed64 distance = 3; if (targetPos != start) { dir = targetPos - start; distance = dir.Magnitude; } var totalFlyTime = distance / bulletEnt.bullet.speed; bulletEnt.logicTransform.SetDirection(Fixed64Vector2.up); bulletEnt.logicTransform.SetPosition(start); var angleRadians = FixedMath.Deg2Rad * (nowAngle + randomAngle); var x = dir.x * FixedMath.Cos(angleRadians) - dir.y * FixedMath.Sin(angleRadians); var y = dir.x * FixedMath.Sin(angleRadians) + dir.y * FixedMath.Cos(angleRadians); var startToEnd = new Fixed64Vector2(x, y); var end = startToEnd + start; // Vector2 control; // if (randomCurvePerpendicularControl > 0f) // { // Fixed64Vector2 parallel = startToEnd.Normalize(); // var perpendicular = new Fixed64Vector2(-parallel.y, parallel.x); // var parallelDisRate = 0.5f; // if (randomCurveParallelControl > 0f) // { // parallelDisRate = RandomSrv.Range(0, randomCurveParallelControl); // } // var perpendicularDis = RandomSrv.Range(-randomCurvePerpendicularControl, randomCurvePerpendicularControl); // control = start + parallel * (distance * parallelDisRate) + perpendicular * perpendicularDis; // } // else // { // var halfPoint = (start + end) / 2f; // control = distance * BattleConst.CurveBulletControlParam * Vector2.up + halfPoint; // } // var explosionRangeEid = 0; // if (string.IsNullOrEmpty(explosionRangePath) == false && explosionRangeScale > 0) // { // var explosionRangeEnt = Contexts.Combat.CreateEntity("ExplosionRange_" + bulletEnt.creationIndex); // explosionRangeEnt.AddTransformProxy(targetPos, Vector2.right, explosionRangeEnt); // explosionRangeEnt.AddAsset(new MainAssetParam() // { // path = explosionRangePath, // parentNodeName = "RenderWorld", // scaleParam = explosionRangeScale // }, null); // explosionRangeEid = explosionRangeEnt.creationIndex; // } // bulletEnt.AddCurveBullet(start, end, control, explosionRangeEid, totalFlyTime); } private static void DestroyInvalidBulletFromSpec(AbilitySpec abilitySpec) { var ctxOwnerEnt = abilitySpec.ctx.ownerEnt; var bullet = ctxOwnerEnt.bullet; // var orDefault = TDRoot.Tables.Bullet.GetOrDefault(1); if (bullet == null) { XLog.LogDebug("DestroyBulletFromSpec bullet == null"); return; } if (bullet.hasInvalidated == false) { XLog.LogDebug("DestroyBulletFromSpec bullet not invalidated"); return; } bullet.invalidatedCount--; if (bullet.invalidatedCount <= 0) { DestroyBullet(ctxOwnerEnt); } } public static void DestroyBullet(CombatEntity bullet) { bullet.isDestroyEnt = true; } public static void InvalidateBullet(CombatEntity bullet) { if (bullet.hasBullet == false) { XLog.LogDebug("InvalidateBullet bullet.hasBullet == false"); return; } bullet.asset?.HideMainAsset(); // 隐藏模型, 但不销毁 bullet.isDirectBullet = false; bullet.RemoveSurroundBullet(); bullet.RemoveCurveBullet(); bullet.bullet.hasInvalidated = true; } private static string GetBulletPrefabPathByElementType(ElementType elementType, BulletDesc tbBullet) { var prefabPath = tbBullet.prefab; if (elementType == ElementType.Fire && !string.IsNullOrEmpty(tbBullet.firePrefab)) prefabPath = tbBullet.firePrefab; else if (elementType == ElementType.Toxic && !string.IsNullOrEmpty(tbBullet.toxicPrefab)) prefabPath = tbBullet.toxicPrefab; else if (elementType == ElementType.Thunder && !string.IsNullOrEmpty(tbBullet.thunderPrefab)) prefabPath = tbBullet.thunderPrefab; return prefabPath; } private static void GetFirePos(CombatEntity owner, CombatEntity bullet, out Fixed64Vector2 pos, out Fixed64Vector2 dir) { var tp = owner.logicTransform; pos = tp.position; dir = tp.forward; // pos = tp.position; // dir = tp.direction; // var bulletCfgId = bullet.bullet.bulletCfgId; // var bulletCfg = BulletDescMgr.Instance.GetConfig(bulletCfgId); // var bindPoint = owner.bindPointProxy; // if (bindPoint == null) // return; // if (bindPoint.TryGetBindPointOrDefault(bulletCfg.bornPoint, out var point)) // { // pos = point.position; // // dir = point.right; // } } public static CombatEntity CreateBounceBullet(CombatEntity caster, GameAbilityContext middleGaCtx, int newBulletId = 0) { var originEnt = middleGaCtx.ownerEnt; if (originEnt.hasBullet == false) { XLog.LogDebug("CreateBounceBullet originEnt is not a bullet"); return null; } var originBullet = originEnt.bullet; if (caster.IsValid() == false) return null; if (newBulletId == 0) newBulletId = originBullet.bulletCfgId; var tbBullet = BulletDescMgr.Instance.GetConfig(newBulletId); // 释放位置外部已经选好了,必须有 var targetPos = TargetSelectSrv.GetCastTargetPos(middleGaCtx); var bullet = Contexts.Combat.CreateEntity(); var casterProp = caster.property.container; var level = middleGaCtx.level; var gaInitCfg = middleGaCtx.abilitySpec.gaInitCfg; var existTime = (float)ExpressionEvaluator.CalcVal_WithPropAndGaCfg(tbBullet.duration, casterProp, level, gaInitCfg, 3f); var maxDistance = (float)ExpressionEvaluator.CalcVal_WithPropAndGaCfg(tbBullet.maxDistance, casterProp, level, gaInitCfg, 5f); var scale = (float)ExpressionEvaluator.CalcVal_WithPropAndGaCfg(tbBullet.scale, casterProp, level, gaInitCfg, 1f); var speed = (float)ExpressionEvaluator.CalcVal_WithPropAndGaCfg(tbBullet.speed, casterProp, level, gaInitCfg, 10f); var paramCount = tbBullet.colliderParam.Length; var colliderParam1 = paramCount > 0 ? tbBullet.colliderParam[0] * BattleConst.TenThousandReverse * scale : 0f; var colliderParam2 = paramCount > 1 ? tbBullet.colliderParam[1] * BattleConst.TenThousandReverse * scale : 0f; var collisionTimes = (int)ExpressionEvaluator.CalcVal_WithPropAndGaCfg(tbBullet.collisionTimes, casterProp, level, gaInitCfg, 1f); var collisionInterval = (float)ExpressionEvaluator.CalcVal_WithPropAndGaCfg(tbBullet.collisionInterval, casterProp, level, gaInitCfg, 1f); if (tbBullet.skill == 0 && tbBullet.endSkill == 0) { XLog.LogError($"{tbBullet.bulletID} 子弹配置不对,没有技能!!!!"); } bullet.AddBullet(originBullet.casterEid, originBullet.elementType, originBullet.bulletFromType, tbBullet.bulletID, existTime, maxDistance, scale, speed, tbBullet.colliderType, colliderParam1, colliderParam2, tbBullet.collideWith, collisionTimes, collisionInterval, tbBullet.skill, tbBullet.endSkill, tbBullet.isTriggerHitSkillOnEnd != 0, ListPool.Pop()); bullet.AddGaCtxTransfer(middleGaCtx.abilitySpec.gaParams, middleGaCtx.abilitySpec.gaInitCfg, middleGaCtx.level); bullet.AddFaction(caster.faction.faction, SelectUseType.Bullet, caster.faction.opposite); bullet.AddProperty(); caster.property.TaskSnapShot(bullet.property.container); bullet.property.SetProperty(PropertyDef.BulletFlyDistanceMax, maxDistance, true); var ignore = ListPool.Pop(); ignore.Add(typeof(Animator)); var prefabPath = GetBulletPrefabPathByElementType(originBullet.elementType, tbBullet); bullet.AddAsset(new MainAssetParam { path = prefabPath, parentNodeName = "RenderWorld", scaleParam = scale }, ignore); bullet.AddAudio(bullet.creationIndex); bullet.AddTransformProxy(Vector2.zero, Vector2.right, bullet); if (tbBullet.shootType == ShootType.Direct) { AddDirectBullet(originEnt, bullet, targetPos, 0f, 0f); } else if (tbBullet.shootType == ShootType.Surround) { XLog.LogDebug("CreateBounceBullet surround bullet not supported"); return null; } else if (tbBullet.shootType == ShootType.Curve) { var randomCurvePerpendicularControl = (float)ExpressionEvaluator.CalcVal_WithPropAndGaCfg( tbBullet.moveParam1, casterProp, level, gaInitCfg, -1f); var randomCurveParallelControl = (float)ExpressionEvaluator.CalcVal_WithPropAndGaCfg( tbBullet.moveParam2, casterProp, level, gaInitCfg, -1f); AddCurveBullet(originEnt, bullet, targetPos, 0f, 0f, tbBullet.warningPrefab, tbBullet.warningScale * BattleConst.TenThousandReverse, randomCurvePerpendicularControl, randomCurveParallelControl); } foreach (var filterEid in middleGaCtx.filterEids) { var hitData = SptPool.Malloc(); hitData.Awake(filterEid, 0f, bullet.transformProxy.position, bullet.transformProxy.direction); bullet.bullet.hitDatas.Add(hitData); } return bullet; } public static bool CanCast(GameAbilityContext ctx, string canCastExpr, float defval = 1f) { if (string.IsNullOrEmpty(canCastExpr)) { return true; } var casterEid = ctx.ownerEnt.creationIndex; var ownerEnt = ctx.ownerEnt; if (ownerEnt.IsValid() == false) return false; if (ownerEnt.hasBullet) casterEid = ownerEnt.bullet.casterEid; var caster = Contexts.Combat.GetEntity(casterEid); if (caster.IsValid() == false) return false; var casterProp = caster.property.container; var level = ctx.level; var gaInitCfg = ctx.abilitySpec.gaInitCfg; var castRate = ExpressionEvaluator.CalcVal_WithPropAndGaCfg(canCastExpr, casterProp, level, gaInitCfg, defval); if (RandomSrv.Range(0f, 1f) > castRate) { return false; } return true; } public static void BulletSkillCast(CombatEntity bulletEnt, int targetEid, Fixed64Vector2 targetPos, int abilityId) { if (!bulletEnt.hasBullet || bulletEnt.isDestroyEnt) return; var bullet = bulletEnt.bullet; var gaCtxTransfer = bulletEnt.gaCtxTransfer; // 不给他不行,发射者死亡的话,子弹也要有伤害 var context = AbilitySrv.GrantTransferAbility(bulletEnt, abilityId, gaCtxTransfer.gaInitCfg, null, gaCtxTransfer.level); if (context == null) { XLog.LogDebug("AttachActiveAbility failed: " + abilityId); return; } if (targetEid != 0) { var targetEnt = Contexts.Combat.GetEntity(targetEid); // 子弹释放的目标, 具体作用对象由子弹技能的 catchTarget 来决定 if (targetEnt.isObstacle) { context.castPos.Add(bulletEnt.logicTransform.position); } else { context.castTarget.Add(targetEnt.creationIndex); context.castPos.Add(targetEnt.logicTransform.position); } } else { // context.castPos.Add(targetPos); } ref var gaParam = ref gaCtxTransfer.gaParams; gaParam.SetParam1Int(targetEid); BulletTryRegisterEndAbility(bullet, context); context.dropAbilityWhenEnd = true; //TDRoot.Tables.Skill[abilityId].DropAbillity; context.AddHitDataByRefs(bullet.hitDatas); // XLog.LogDebug("bullet skill cast: " + abilityId + " " + context.abilitySpec.Ability.Name + " " + entity); context.asc.TryActivateAbility_WithSeqAndParam(context.gaSeq, gaParam); // 是否是枪械发出的子弹 int bulletFirstType = (int)bullet.bulletFromType & 0b11; if (bulletFirstType == 1) { } } private static void BulletTryRegisterEndAbility(BulletComponent bullet, GameAbilityContext context) { if (bullet.hasInvalidated) { bullet.invalidatedCount++; context.abilitySpec.RegisterEndAbility(DestroyInvalidBulletFromSpec); } } } }