- 好友
- 0
- 在线时间
- 128 小时
- 最后登录
- 2024-12-20
见习骑士
- UID
- 2758789
- 第纳尔
- 2148
- 精华
- 0
- 互助
- 21
- 荣誉
- 1
- 贡献
- 0
- 魅力
- 201
- 注册时间
- 2016-7-18
鲜花( 22) 鸡蛋( 0)
|
本帖最后由 路过的罗格 于 2024-3-20 16:30 编辑
最近摸了一遍战斗系统,东西比较多,所以教程一点一点来写.
首先是游戏源码,机制的入口是 TaleWorlds.MountAndBlade.Mission.GetDefendCollisionResults,防御相关的回调函数.这里的东西不多,主要是继续调用MissionCombatMechanicsHelper.GetDefendCollisionResults方法去处理数据.在MissionCombatMechanicsHelper.GetDefendCollisionResults处理完数据后,将算好的数据返回这个函数,然后继续往后续的攻击处理过程去传递数据
- internal void GetDefendCollisionResults(Agent attackerAgent, Agent defenderAgent, CombatCollisionResult collisionResult, int attackerWeaponSlotIndex, bool isAlternativeAttack, StrikeType strikeType, Agent.UsageDirection attackDirection, float collisionDistanceOnWeapon, float attackProgress, bool attackIsParried, bool isPassiveUsageHit, bool isHeavyAttack, ref float defenderStunPeriod, ref float attackerStunPeriod, ref bool crushedThrough)
- {
- bool flag = false;
- MissionCombatMechanicsHelper.GetDefendCollisionResults(attackerAgent, defenderAgent, collisionResult, attackerWeaponSlotIndex, isAlternativeAttack, strikeType, attackDirection, collisionDistanceOnWeapon, attackProgress, attackIsParried, isPassiveUsageHit, isHeavyAttack, ref defenderStunPeriod, ref attackerStunPeriod, ref crushedThrough, ref flag);
- if ((crushedThrough || flag) && (attackerAgent.CanLogCombatFor || defenderAgent.CanLogCombatFor))
- {
- CombatLogData combatLog = new CombatLogData(false, attackerAgent.IsHuman, attackerAgent.IsMine, attackerAgent.RiderAgent != null, attackerAgent.RiderAgent != null && attackerAgent.RiderAgent.IsMine, attackerAgent.IsMount, defenderAgent.IsHuman, defenderAgent.IsMine, defenderAgent.Health <= 0f, defenderAgent.HasMount, defenderAgent.RiderAgent != null && defenderAgent.RiderAgent.IsMine, defenderAgent.IsMount, false, defenderAgent.RiderAgent == attackerAgent, crushedThrough, flag, 0f);
- this.AddCombatLogSafe(attackerAgent, defenderAgent, null, combatLog);
- }
- }
复制代码
|
然后进TaleWorlds.MountAndBlade.MissionCombatMechanicsHelper.GetDefendCollisionResults,简单扫一眼它的输入输出,其中包含ref bool crushedThrough,一个是否突破格挡的判定值将被返回.然后进函数里面看这个变量怎么获取的.可以在最后一句看到
- crushedThrough = !chamber && MissionGameModels.Current.AgentApplyDamageModel.DecideCrushedThrough(attackerAgent, defenderAgent, num, attackDirection, strikeType, weaponComponentData2, isPassiveUsageHit);
复制代码 在没有触发挡反(大师反)的时候,调用AgentApplyDamageModel.DecideCrushedThrough函数,进行下一步计算.这个函数需要传入的大多数数据,都是直接从MissionCombatMechanicsHelper.GetDefendCollisionResults的传入数据里获取的,只有两个新的变量num和weaponComponentData2.其中weaponComponentData2是受击方使用的防御物品,由以下语句获取
- //获取防御方左手武器的索引
- EquipmentIndex wieldedItemIndex = defenderAgent.GetWieldedItemIndex(Agent.HandIndex.OffHand);
- //如果左手物品为空
- if (wieldedItemIndex == EquipmentIndex.None)
- {
- //则获取右手武器的索引
- wieldedItemIndex = defenderAgent.GetWieldedItemIndex(Agent.HandIndex.MainHand);
- }
- //获取该索引值对应的物品数据
- WeaponComponentData weaponComponentData2 = ((wieldedItemIndex != EquipmentIndex.None) ? defenderAgent.Equipment[wieldedItemIndex].CurrentUsageItem : null);
复制代码 num是"总攻击能量",基本可以理解为不计算武器配件伤害倍率,不计算perk加成,但是吃速度加成的伤害计算.
- //基础值为10
- float num = 10f;
- //当攻击方武器不为空时
- if (!missionWeapon.IsEmpty)
- {
- //获取攻击方的当前武器偏移值
- float z = attackerAgent.GetCurWeaponOffset().z;
- //获取攻击方武器真实长度
- float realWeaponLength = weaponComponentData.GetRealWeaponLength();
- //当前武器偏移值+武器真实长度
- float num2 = realWeaponLength + z;
- //计算一个武器上的碰撞点位,等于(0.2+传入的武器碰撞距离)/(当前武器偏移值+武器真实长度),并且限制这个数介于10%~98%
- float impactPoint = MBMath.ClampFloat((0.2f + collisionDistanceOnWeapon) / num2, 0.1f, 0.98f);
- //获取这次攻击的额外速度加成
- float exraLinearSpeed = MissionCombatMechanicsHelper.ComputeRelativeSpeedDiffOfAgents(attackerAgent, defenderAgent);
- float num3;
- //如果这次的攻击时戳刺动作
- if (strikeType == StrikeType.Thrust)
- {
- //进行一次不吃skill/perk伤害加成的伤害计算流程.
- num3 = CombatStatCalculator.CalculateBaseBlowMagnitudeForThrust((float)missionWeapon.GetModifiedThrustSpeedForCurrentUsage() / 11.764706f * MissionCombatMechanicsHelper.SpeedGraphFunction(attackProgress, strikeType, attackDirection), missionWeapon.Item.Weight, exraLinearSpeed);
- }
- //对挥砍类型的动作同理
- else
- {
- num3 = CombatStatCalculator.CalculateBaseBlowMagnitudeForSwing((float)missionWeapon.GetModifiedSwingSpeedForCurrentUsage() / 4.5454545f * MissionCombatMechanicsHelper.SpeedGraphFunction(attackProgress, strikeType, attackDirection), realWeaponLength, missionWeapon.Item.Weight, weaponComponentData.Inertia, weaponComponentData.CenterOfMass, impactPoint, exraLinearSpeed);
- }
- //如果这次的攻击时戳刺动作
- if (strikeType == StrikeType.Thrust)
- {
- //刚才计算的伤害值吃一个80%的减益
- num3 *= 0.8f;
- }
- //如果这次攻击的方向是上打
- else if (attackDirection == Agent.UsageDirection.AttackUp)
- {
- //刚才计算的伤害值吃一个125%的增益
- num3 *= 1.25f;
- }
- //如果这次攻击是一个重击(完整的进行完攻击的准备动作后,本次攻击是为重击)
- else if (isHeavyAttack)
- {
- //获取一个配置xml里的数值作为增益
- num3 *= ManagedParameters.Instance.GetManagedParameter(ManagedParametersEnum.HeavyAttackMomentumMultiplier);
- }
- //基础值加上这个吃完增减益的攻击值
- num += num3;
- }
复制代码
- internal static void GetDefendCollisionResults(Agent attackerAgent, Agent defenderAgent, CombatCollisionResult collisionResult, int attackerWeaponSlotIndex, bool isAlternativeAttack, StrikeType strikeType, Agent.UsageDirection attackDirection, float collisionDistanceOnWeapon, float attackProgress, bool attackIsParried, bool isPassiveUsageHit, bool isHeavyAttack, ref float defenderStunPeriod, ref float attackerStunPeriod, ref bool crushedThrough, ref bool chamber)
- {
- MissionWeapon missionWeapon = ((attackerWeaponSlotIndex >= 0) ? attackerAgent.Equipment[attackerWeaponSlotIndex] : MissionWeapon.Invalid);
- WeaponComponentData weaponComponentData = (missionWeapon.IsEmpty ? null : missionWeapon.CurrentUsageItem);
- EquipmentIndex wieldedItemIndex = defenderAgent.GetWieldedItemIndex(Agent.HandIndex.OffHand);
- if (wieldedItemIndex == EquipmentIndex.None)
- {
- wieldedItemIndex = defenderAgent.GetWieldedItemIndex(Agent.HandIndex.MainHand);
- }
- ItemObject itemObject = ((wieldedItemIndex != EquipmentIndex.None) ? defenderAgent.Equipment[wieldedItemIndex].Item : null);
- WeaponComponentData weaponComponentData2 = ((wieldedItemIndex != EquipmentIndex.None) ? defenderAgent.Equipment[wieldedItemIndex].CurrentUsageItem : null);
- float num = 10f;
- attackerStunPeriod = ((strikeType == StrikeType.Thrust) ? ManagedParameters.Instance.GetManagedParameter(ManagedParametersEnum.StunPeriodAttackerThrust) : ManagedParameters.Instance.GetManagedParameter(ManagedParametersEnum.StunPeriodAttackerSwing));
- chamber = false;
- if (!missionWeapon.IsEmpty)
- {
- float z = attackerAgent.GetCurWeaponOffset().z;
- float realWeaponLength = weaponComponentData.GetRealWeaponLength();
- float num2 = realWeaponLength + z;
- float impactPoint = MBMath.ClampFloat((0.2f + collisionDistanceOnWeapon) / num2, 0.1f, 0.98f);
- float exraLinearSpeed = MissionCombatMechanicsHelper.ComputeRelativeSpeedDiffOfAgents(attackerAgent, defenderAgent);
- float num3;
- if (strikeType == StrikeType.Thrust)
- {
- num3 = CombatStatCalculator.CalculateBaseBlowMagnitudeForThrust((float)missionWeapon.GetModifiedThrustSpeedForCurrentUsage() / 11.764706f * MissionCombatMechanicsHelper.SpeedGraphFunction(attackProgress, strikeType, attackDirection), missionWeapon.Item.Weight, exraLinearSpeed);
- }
- else
- {
- num3 = CombatStatCalculator.CalculateBaseBlowMagnitudeForSwing((float)missionWeapon.GetModifiedSwingSpeedForCurrentUsage() / 4.5454545f * MissionCombatMechanicsHelper.SpeedGraphFunction(attackProgress, strikeType, attackDirection), realWeaponLength, missionWeapon.Item.Weight, weaponComponentData.Inertia, weaponComponentData.CenterOfMass, impactPoint, exraLinearSpeed);
- }
- if (strikeType == StrikeType.Thrust)
- {
- num3 *= 0.8f;
- }
- else if (attackDirection == Agent.UsageDirection.AttackUp)
- {
- num3 *= 1.25f;
- }
- else if (isHeavyAttack)
- {
- num3 *= ManagedParameters.Instance.GetManagedParameter(ManagedParametersEnum.HeavyAttackMomentumMultiplier);
- }
- num += num3;
- }
- float num4 = 1f;
- defenderStunPeriod = num * ManagedParameters.Instance.GetManagedParameter(ManagedParametersEnum.StunMomentumTransferFactor);
- if (weaponComponentData2 != null)
- {
- if (weaponComponentData2.IsShield)
- {
- float managedParameter = ManagedParameters.Instance.GetManagedParameter(ManagedParametersEnum.StunDefendWeaponWeightOffsetShield);
- num4 += managedParameter * itemObject.Weight;
- }
- else
- {
- num4 = 0.9f;
- float managedParameter2 = ManagedParameters.Instance.GetManagedParameter(ManagedParametersEnum.StunDefendWeaponWeightMultiplierWeaponWeight);
- num4 += managedParameter2 * itemObject.Weight;
- ItemObject.ItemTypeEnum itemType = itemObject.ItemType;
- if (itemType == ItemObject.ItemTypeEnum.TwoHandedWeapon)
- {
- managedParameter2 = ManagedParameters.Instance.GetManagedParameter(ManagedParametersEnum.StunDefendWeaponWeightBonusTwoHanded);
- }
- else if (itemType == ItemObject.ItemTypeEnum.Polearm)
- {
- num4 += ManagedParameters.Instance.GetManagedParameter(ManagedParametersEnum.StunDefendWeaponWeightBonusPolearm);
- }
- }
- if (collisionResult == CombatCollisionResult.Parried)
- {
- attackerStunPeriod += MathF.Min(0.15f, 0.12f * num4);
- num4 += ManagedParameters.Instance.GetManagedParameter(ManagedParametersEnum.StunDefendWeaponWeightBonusActiveBlocked);
- }
- else if (collisionResult == CombatCollisionResult.ChamberBlocked)
- {
- attackerStunPeriod += MathF.Min(0.25f, 0.25f * num4);
- num4 += ManagedParameters.Instance.GetManagedParameter(ManagedParametersEnum.StunDefendWeaponWeightBonusChamberBlocked);
- chamber = true;
- }
- }
- if (!defenderAgent.GetIsLeftStance())
- {
- num4 += ManagedParameters.Instance.GetManagedParameter(ManagedParametersEnum.StunDefendWeaponWeightBonusRightStance);
- }
- defenderStunPeriod /= num4;
- float num5;
- float num6;
- MissionGameModels.Current.AgentApplyDamageModel.CalculateDefendedBlowStunMultipliers(attackerAgent, defenderAgent, collisionResult, weaponComponentData, weaponComponentData2, out num5, out num6);
- attackerStunPeriod *= num5;
- defenderStunPeriod *= num6;
- float managedParameter3 = ManagedParameters.Instance.GetManagedParameter(ManagedParametersEnum.StunPeriodMax);
- attackerStunPeriod = MathF.Min(attackerStunPeriod, managedParameter3);
- defenderStunPeriod = MathF.Min(defenderStunPeriod, managedParameter3);
- crushedThrough = !chamber && MissionGameModels.Current.AgentApplyDamageModel.DecideCrushedThrough(attackerAgent, defenderAgent, num, attackDirection, strikeType, weaponComponentData2, isPassiveUsageHit);
- }
复制代码
|
有了这些数据,就可以继续看AgentApplyDamageModel.DecideCrushedThrough怎么处理这些数据了.
这里稍微注意一下,对于沙盒/战役模式,与自定义战斗/多人游戏的代码是不一样的,虽然差别不大就是了.对于沙盒战役模式中,我这边用的1.28版本的代码,已经取消掉了物品的突破格挡标签,也就意味着随便一把武器都可以参与突破格挡的判定.而自定义战斗和多人,依旧有这个标签的判定,需要使用有这个标签的武器,才能触发.以判定较多的自定义战斗/多人游戏的代码为准,这边的代码不多,直接每行都过一遍(同时也能看到一些抽象的逻辑):
- public override bool DecideCrushedThrough(Agent attackerAgent, Agent defenderAgent, float totalAttackEnergy, Agent.UsageDirection attackDirection, StrikeType strikeType, WeaponComponentData defendItem, bool isPassiveUsage)
- {
- //获取攻击方的左手物品索引
- EquipmentIndex wieldedItemIndex = attackerAgent.GetWieldedItemIndex(Agent.HandIndex.OffHand);
- //如果左手物品为空
- if (wieldedItemIndex == EquipmentIndex.None)
- {
- //则取获取右手的物品
- //这里注意一下,这里只有你左手没有物品时,才会获取右手物品.如果你左手有个盾牌,那么会拿着你的盾去看有没有突破格挡属性
- wieldedItemIndex = attackerAgent.GetWieldedItemIndex(Agent.HandIndex.MainHand);
- }
- //获取索引对应物品的物品数据,在索引不为空的前提下
- WeaponComponentData weaponComponentData = ((wieldedItemIndex != EquipmentIndex.None) ? attackerAgent.Equipment[wieldedItemIndex].CurrentUsageItem : null);
- //如果:物品数据不为空,或者是被动攻击(骑枪突刺/长矛反骑),或者没有突破格挡属性,或者攻击类型不为挥砍,或者攻击方向不是上打
- if (weaponComponentData == null || isPassiveUsage || !weaponComponentData.WeaponFlags.HasAnyFlag(WeaponFlags.CanCrushThrough) || strikeType != StrikeType.Swing || attackDirection != Agent.UsageDirection.AttackUp)
- {
- //就直接判定无法突破格挡
- //这里面坑就很多,每一个判定都抽象.首先是骑枪突刺直接判定无法突破格挡,然后是你左手有物品时会判定你左手物品有没有突破格挡的属性.还限制了只有挥砍的上劈才能突破格挡,突刺直接没戏.
- return false;
- }
- //如果没有被前面的一堆条件直接给判定为无法突破格挡
- //初始化一个数值为58
- float num = 58f;
- //如果受击方的防御武器不为空且为盾牌
- if (defendItem != null && defendItem.IsShield)
- {
- //数值乘1.2倍,为69.6
- num *= 1.2f;
- }
- //直接进行判定,传递进来的那个num,有没有大于这个函数里的num,如果传进来的那个数值,大于58(用盾为69.6),则判定为突破格挡
- return totalAttackEnergy > num;
- }
复制代码 可以看到这个判定非常简单,就是在没有被直接判定为无法突破格挡的前提下,不计算武器配件伤害倍率和技能加成的前提下伤害够高,就能完成突破格挡.
然后再回去看一下刚才的那个伤害计算是怎么算的.因为原本只有挥砍能突破格挡,所以只看挥砍的.
首先看一眼输入的变量名:浮点型角速度,浮点型武器长度,浮点型武器重量,浮点型武器惯性,浮点型武器质心,浮点型冲击点,浮点型速度加成
- public static float CalculateBaseBlowMagnitudeForSwing(float angularSpeed, float weaponReach, float weaponWeight, float weaponInertia, float weaponCoM, float impactPoint, float exraLinearSpeed)
复制代码 然后输入的东西,除了角速度要稍微计算一下外,其他的都等于是从武器属性上获取的,或者是之前这个函数传入的
角速度=获取当前武器实际挥砍速度(这个数值可以吃到武器前缀的额外速度)/4.5454545*动作完成度影响的数值,这个数值最大为1
- CombatStatCalculator.CalculateBaseBlowMagnitudeForSwing((float)missionWeapon.GetModifiedSwingSpeedForCurrentUsage() / 4.5454545f * MissionCombatMechanicsHelper.SpeedGraphFunction(attackProgress, strikeType, attackDirection), realWeaponLength, missionWeapon.Item.Weight, weaponComponentData.Inertia, weaponComponentData.CenterOfMass, impactPoint, exraLinearSpeed);
复制代码
再来看CalculateBaseBlowMagnitudeForSwing,这里跳过了一层过度的代码,那个代码里会多次计算碰撞点,来获取一个最大是伤害值,实际的伤害计算在这里
- public static float CalculateStrikeMagnitudeForSwing(float swingSpeed, float impactPointAsPercent, float weaponWeight, float weaponLength, float weaponInertia, float weaponCoM, float extraLinearSpeed)
- {
- float num = weaponLength * impactPointAsPercent - weaponCoM;
- float num2 = swingSpeed * (0.5f + weaponCoM) + extraLinearSpeed;
- float num3 = 0.5f * weaponWeight * num2 * num2;
- float num4 = 0.5f * weaponInertia * swingSpeed * swingSpeed;
- float num5 = num3 + num4;
- float num6 = (num2 + swingSpeed * num) / (1f / weaponWeight + num * num / weaponInertia);
- float num7 = num2 - num6 / weaponWeight;
- float num8 = swingSpeed - num6 * num / weaponInertia;
- float num9 = 0.5f * weaponWeight * num7 * num7;
- float num10 = 0.5f * weaponInertia * num8 * num8;
- float num11 = num9 + num10;
- float num12 = num5 - num11 + 0.5f;
- return 0.067f * num12;
- }
复制代码
|
反编译源码丢gpt里过一遍,稍微能看一点的逻辑是这样
- public static float CalculateStrikeMagnitudeForSwing(float swingSpeed, float impactPointAsPercent, float weaponWeight, float weaponLength, float weaponInertia, float weaponCoM, float extraLinearSpeed)
- {
- // 计算击中点距离武器重心的距离
- float impactPointDistance = weaponLength * impactPointAsPercent - weaponCoM;
-
- // 计算武器在击中瞬间的初始线速度
- float initialVelocity = swingSpeed * (0.5f + weaponCoM) + extraLinearSpeed;
- // 计算击中之前的动能,包括线动能和角动能
- float kineticEnergyBeforeImpact = 0.5f * weaponWeight * initialVelocity * initialVelocity +
- 0.5f * weaponInertia * swingSpeed * swingSpeed;
- // 计算击中后的最终线速度
- float finalLinearVelocity = initialVelocity - (initialVelocity + swingSpeed * impactPointDistance) / (1f / weaponWeight + impactPointDistance * impactPointDistance / weaponInertia) / weaponWeight;
-
- // 计算击中后的最终角速度
- float finalAngularVelocity = swingSpeed - (initialVelocity + swingSpeed * impactPointDistance) * impactPointDistance / weaponInertia;
- // 计算击中后的动能,包括线动能和角动能
- float kineticEnergyAfterImpact = 0.5f * weaponWeight * finalLinearVelocity * finalLinearVelocity +
- 0.5f * weaponInertia * finalAngularVelocity * finalAngularVelocity;
- // 返回冲击力的大小,基于动能的改变量,并乘以一个系数0.067,再加上一个常数0.5的一半
- return 0.067f * (kineticEnergyBeforeImpact - kineticEnergyAfterImpact + 0.5f);
- }
复制代码 到这一步我脑子已经不够用了,直接复制一下gpt的回复
- 使函数 CalculateStrikeMagnitudeForSwing 的输出值变大,即增加冲击力的大小,你可以通过调整输入参数来实现。下面是一些可能的方法:
- 增加挥动速度(swingSpeed):
- 增加挥动速度会直接增加击中前的动能,因为动能与速度的平方成正比。这通常会导致更大的冲击力。
- 增加武器长度(weaponLength)并调整冲击点:
- 增加武器长度并适当调整击中点(impactPointAsPercent),使其更接近武器末端,但不是太远以至于超过武器的有效范围,可以增加杠杆效应,从而增加冲击力。
- 增加额外的线速度(extraLinearSpeed):
- 增加额外的线速度会直接增加击中前的线动能,从而可能增加冲击力。
- 减少武器重量(weaponWeight):
- 虽然减少武器重量会减少击中前的线动能,但它也会使得武器更容易加速,从而可能在某些情况下增加冲击力。这需要与其他因素(如速度)的增加相结合来实现最佳效果。
- 调整武器质心(weaponCoM):
- 将质心适当向武器末端移动可以增加杠杆效应,但需要注意的是,这也可能使得武器更难以控制。找到最佳质心位置需要平衡冲击力和操控性。
- 减少武器惯性(weaponInertia):
- 减少武器惯性可以使得武器在旋转时更容易加速,从而可能增加冲击力。但这也取决于其他因素,如挥动速度和击中点的位置。
复制代码
|
|
评分
-
查看全部评分
|