attack flow - breaking and blocking input, roll improvements

This commit is contained in:
2025-09-10 12:33:13 +02:00
parent 9d6c237088
commit 058920055b
9 changed files with 1219 additions and 491 deletions

View File

@@ -105633,13 +105633,13 @@ MonoBehaviour:
triggerDieBehaviour: 0
m_ignoreTriggers: 1
useAnimationBasedRotation: 0
strafeRollInputThreshold: 0.3
strafeRollForwardAnim: Roll_Forward
strafeRollBackwardAnim: Roll_Backward
strafeRollLeftAnim: Roll_Left
strafeRollRightAnim: Roll_Right
strafeRollLeftCorrectionAngle: 70.6
strafeRollRightCorrectionAngle: 70
strafeRollBackwardCorrectionAngle: 0
--- !u!114 &9202663235077955828
MonoBehaviour:
m_ObjectHideFlags: 0

View File

@@ -772,8 +772,7 @@ MonoBehaviour:
ignoreDefense: 0
activeRagdoll: 0
senselessTime: 0
resetAttackTrigger: 1
resetTriggerBeforeTime: 0.5
blockInputBeforeTime: 0.5
unlockRotationTime: 0.5
lerpPositionTowardsTarget: 1
maxLerpDistance: 3.5
@@ -781,8 +780,8 @@ MonoBehaviour:
stoppingDistance: 1.2
useComboTimingWindow: 1
comboWindowStartTime: 0.7
comboWindowDuration: 0.4
comboWindowTimeScale: 0.2
comboWindowDuration: 0.3
comboWindowTimeScale: 0.4
useAttackTimeScale: 0
maxTargetDistance: 3
lowHealthTh: 10
@@ -791,7 +790,7 @@ MonoBehaviour:
attackTimeScaleEnd: 0.37
rotatePlayerTowardsTarget: 1
degreeThreshold: 100
debug: 0
debug: 1
--- !u!1101 &-8051095333111886674
AnimatorStateTransition:
m_ObjectHideFlags: 1
@@ -2749,8 +2748,7 @@ MonoBehaviour:
ignoreDefense: 0
activeRagdoll: 0
senselessTime: 0
resetAttackTrigger: 0
resetTriggerBeforeTime: 0.3
blockInputBeforeTime: 0.5
unlockRotationTime: 0.7
lerpPositionTowardsTarget: 1
maxLerpDistance: 3.5
@@ -2758,8 +2756,8 @@ MonoBehaviour:
stoppingDistance: 1.2
useComboTimingWindow: 1
comboWindowStartTime: 0.6
comboWindowDuration: 0.4
comboWindowTimeScale: 0.2
comboWindowDuration: 0.3
comboWindowTimeScale: 0.4
useAttackTimeScale: 1
maxTargetDistance: 3
lowHealthTh: 30
@@ -4567,6 +4565,7 @@ MonoBehaviour:
tags:
- IsRolling
- IgnoreIK
- LockRotation
--- !u!206 &-2581317115762456654
BlendTree:
m_ObjectHideFlags: 1
@@ -5216,8 +5215,7 @@ MonoBehaviour:
ignoreDefense: 0
activeRagdoll: 0
senselessTime: 0
resetAttackTrigger: 0
resetTriggerBeforeTime: 0.3
blockInputBeforeTime: 0.5
unlockRotationTime: 0.7
lerpPositionTowardsTarget: 1
maxLerpDistance: 3.5
@@ -5225,8 +5223,8 @@ MonoBehaviour:
stoppingDistance: 1.2
useComboTimingWindow: 1
comboWindowStartTime: 0.6
comboWindowDuration: 0.4
comboWindowTimeScale: 0.2
comboWindowDuration: 0.3
comboWindowTimeScale: 0.4
useAttackTimeScale: 1
maxTargetDistance: 3
lowHealthTh: 10
@@ -5738,8 +5736,7 @@ MonoBehaviour:
ignoreDefense: 0
activeRagdoll: 0
senselessTime: 0
resetAttackTrigger: 0
resetTriggerBeforeTime: 0.5
blockInputBeforeTime: 0.5
unlockRotationTime: 0.7
lerpPositionTowardsTarget: 0
maxLerpDistance: 3.5
@@ -26561,15 +26558,14 @@ MonoBehaviour:
ignoreDefense: 1
activeRagdoll: 0
senselessTime: 0
resetAttackTrigger: 0
resetTriggerBeforeTime: 0.6
blockInputBeforeTime: 1
unlockRotationTime: 0.7
lerpPositionTowardsTarget: 1
maxLerpDistance: 3.5
positionLerpSpeed: 2
stoppingDistance: 1.2
useComboTimingWindow: 0
comboWindowStartTime: 0.9
comboWindowStartTime: 1
comboWindowDuration: 0.5
comboWindowTimeScale: 0.3
useAttackTimeScale: 0
@@ -26580,7 +26576,7 @@ MonoBehaviour:
attackTimeScaleEnd: 0.58
rotatePlayerTowardsTarget: 1
degreeThreshold: 100
debug: 0
debug: 1
--- !u!1101 &542493047717198179
AnimatorStateTransition:
m_ObjectHideFlags: 1
@@ -47984,8 +47980,7 @@ MonoBehaviour:
ignoreDefense: 0
activeRagdoll: 0
senselessTime: 0
resetAttackTrigger: 0
resetTriggerBeforeTime: 0.5
blockInputBeforeTime: 0.5
unlockRotationTime: 0.7
lerpPositionTowardsTarget: 0
maxLerpDistance: 3.5
@@ -48139,8 +48134,7 @@ MonoBehaviour:
ignoreDefense: 0
activeRagdoll: 0
senselessTime: 0
resetAttackTrigger: 1
resetTriggerBeforeTime: 0.5
blockInputBeforeTime: 0.5
unlockRotationTime: 0.7
lerpPositionTowardsTarget: 0
maxLerpDistance: 3.5
@@ -48542,8 +48536,7 @@ MonoBehaviour:
ignoreDefense: 1
activeRagdoll: 0
senselessTime: 0
resetAttackTrigger: 0
resetTriggerBeforeTime: 0.6
blockInputBeforeTime: 0.5
unlockRotationTime: 0.7
lerpPositionTowardsTarget: 1
maxLerpDistance: 3.5
@@ -49431,8 +49424,7 @@ MonoBehaviour:
ignoreDefense: 1
activeRagdoll: 0
senselessTime: 0
resetAttackTrigger: 0
resetTriggerBeforeTime: 0.6
blockInputBeforeTime: 0.5
unlockRotationTime: 0.7
lerpPositionTowardsTarget: 1
maxLerpDistance: 3.5

View File

@@ -9838,7 +9838,6 @@ GameObject:
- component: {fileID: 7898212980114005163}
- component: {fileID: 8020189233111417037}
- component: {fileID: 8020706684658151899}
- component: {fileID: 7789765392435096873}
m_Layer: 0
m_Name: Particles
m_TagString: Untagged
@@ -14484,21 +14483,6 @@ ParticleSystemRenderer:
m_MeshWeighting2: 1
m_MeshWeighting3: 1
m_MaskInteraction: 0
--- !u!114 &7789765392435096873
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7893307381764779335}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: dded6759b4b430e4283cf015251490b7, type: 3}
m_Name:
m_EditorClassIdentifier:
target: {fileID: 0}
Force: 1
DistanceRelative: 0
--- !u!1 &7893317824759632779
GameObject:
m_ObjectHideFlags: 0

View File

@@ -16396,6 +16396,11 @@ PrefabInstance:
propertyPath: m_Layer
value: 5
objectReference: {fileID: 0}
- target: {fileID: 1385765897516864307, guid: 851e8e61247888340bdec90fc8aa37f5,
type: 3}
propertyPath: openCloseWindow
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1494204689157949844, guid: 851e8e61247888340bdec90fc8aa37f5,
type: 3}
propertyPath: m_Layer
@@ -17420,7 +17425,17 @@ PrefabInstance:
- target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5,
type: 3}
propertyPath: strafeRollLeftCorrectionAngle
value: 70.6
value: -20
objectReference: {fileID: 0}
- target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5,
type: 3}
propertyPath: strafeRollRightCorrectionAngle
value: -10
objectReference: {fileID: 0}
- target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5,
type: 3}
propertyPath: strafeRollBackwardCorrectionAngle
value: 180
objectReference: {fileID: 0}
- target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5,
type: 3}
@@ -18565,12 +18580,12 @@ PrefabInstance:
- target: {fileID: 5657452459766331955, guid: 851e8e61247888340bdec90fc8aa37f5,
type: 3}
propertyPath: m_AnchorMax.x
value: 1
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5657452459766331955, guid: 851e8e61247888340bdec90fc8aa37f5,
type: 3}
propertyPath: m_AnchorMax.y
value: 1
value: 0
objectReference: {fileID: 0}
- target: {fileID: 5671630659928648966, guid: 851e8e61247888340bdec90fc8aa37f5,
type: 3}

View File

@@ -81,8 +81,11 @@ namespace Beyond
private PlayerAttribute brightnessAttribute;
private PlayerAttribute maturityAttribute;
private MagicAttacks m_magicAttacks;
private bMeleeCombatInput m_meleeCombatInput;
public MagicAttacks Magic => m_magicAttacks;
public bMeleeCombatInput MeleeCombatInput => m_meleeCombatInput;
public PlayerAttribute MaturityAttribute
{
get
@@ -186,6 +189,7 @@ namespace Beyond
m_meleeManager = GetComponent<vMeleeManager>();
m_magicAttacks = GetComponent<MagicAttacks>();
m_autoTargetting = GetComponent<AutoTargetting>();
m_meleeCombatInput = GetComponent<bMeleeCombatInput>();
}
@@ -867,10 +871,10 @@ namespace Beyond
public void PlaySingleSound(AudioClip clipToPlay, bool destroyAfterPlaying = true)
{
// Opcjonalne opóŸnienie na starcie poziomu, aby unikn¹æ "spamowania" dŸwiêkami
// Opcjonalne op<EFBFBD>nienie na starcie poziomu, aby unikn<EFBFBD><EFBFBD> "spamowania" d<EFBFBD>wi<EFBFBD>kami
if (Time.timeSinceLevelLoad < 0.5f && clipToPlay != null)
{
// Mo¿esz chcieæ to odkomentowaæ, jeœli dŸwiêki na starcie s¹ problemem
// Mo<EFBFBD>esz chcie<EFBFBD> to odkomentowa<EFBFBD>, je<EFBFBD>li d<EFBFBD>wi<EFBFBD>ki na starcie s<EFBFBD> problemem
// return;
}
@@ -885,28 +889,28 @@ namespace Beyond
return;
}
// Instancjonowanie prefabu dŸwiêkowego
// Upewnij siê, ¿e prefab 'audioSource' ma komponent AudioSource
// Instancjonowanie prefabu d<EFBFBD>wi<EFBFBD>kowego
// Upewnij si<EFBFBD>, <EFBFBD>e prefab 'audioSource' ma komponent AudioSource
GameObject audioObjectInstance = Instantiate(this.audioSource, transform.position, transform.rotation);
AudioSource sourceComponent = audioObjectInstance.GetComponent<AudioSource>();
if (sourceComponent != null)
{
// PlayOneShot jest dobre dla efektów, nie przerywa innych dŸwiêków na tym samym source,
// jeœli s¹ one odtwarzane przez .Play() i nie u¿ywaj¹ tego samego kana³u.
// PlayOneShot jest dobre dla efekt<EFBFBD>w, nie przerywa innych d<EFBFBD>wi<EFBFBD>k<EFBFBD>w na tym samym source,
// je<EFBFBD>li s<EFBFBD> one odtwarzane przez .Play() i nie u<EFBFBD>ywaj<EFBFBD> tego samego kana<EFBFBD>u.
sourceComponent.PlayOneShot(clipToPlay);
if (destroyAfterPlaying)
{
// Niszczymy obiekt GameObject zawieraj¹cy AudioSource po zakoñczeniu odtwarzania klipu.
// Dodajemy ma³y bufor czasowy, aby upewniæ siê, ¿e dŸwiêk zd¹¿y siê odtworzyæ w ca³oœci.
// Niszczymy obiekt GameObject zawieraj<EFBFBD>cy AudioSource po zako<EFBFBD>czeniu odtwarzania klipu.
// Dodajemy ma<EFBFBD>y bufor czasowy, aby upewni<EFBFBD> si<EFBFBD>, <EFBFBD>e d<EFBFBD>wi<EFBFBD>k zd<EFBFBD><EFBFBD>y si<EFBFBD> odtworzy<EFBFBD> w ca<EFBFBD>o<EFBFBD>ci.
Destroy(audioObjectInstance, clipToPlay.length + 0.1f);
}
}
else
{
Debug.LogWarning("The instantiated 'audioSource' prefab (from Player.cs) does not have an AudioSource component. Destroying instance.");
Destroy(audioObjectInstance); // Posprz¹taj, jeœli coœ posz³o nie tak
Destroy(audioObjectInstance); // Posprz<EFBFBD>taj, je<EFBFBD>li co<EFBFBD> posz<EFBFBD>o nie tak
}
}

View File

@@ -13,6 +13,11 @@ namespace Invector.vMelee
public class bMeleeAttackControl : StateMachineBehaviour
{
// --- MODIFICATION: Static variable to track the currently active attack state ---
private static int activeAttackInstanceId = 0;
private int myAttackInstanceId;
// --- END MODIFICATION ---
[Header("Damage Window")]
[Tooltip("NormalizedTime of Active Damage")]
public float startDamage = 0.05f;
@@ -37,10 +42,8 @@ namespace Invector.vMelee
public float senselessTime;
[Header("Attack Flow")]
[Tooltip("Check true in the last attack of your combo to reset the FSM attack triggers.")]
public bool resetAttackTrigger;
[Tooltip("Normalized time point to reset attack triggers. (Used only if Combo Timing Window is disabled)")]
public float resetTriggerBeforeTime = 0.5f;
[Tooltip("Normalized time point to start allowing the next attack input.")]
public float blockInputBeforeTime = 0.5f;
[Header("Combo & Movement")]
[Tooltip("Normalized time to unlock rotation, allowing the player to aim the next attack in a combo. Set to 1 to disable.")]
@@ -62,7 +65,6 @@ namespace Invector.vMelee
[Tooltip("How close the character should get to the target.")]
public float stoppingDistance = 1.2f;
// --- MODIFIED: Switched to a real-time duration model ---
[Header("Combo Timing Window")]
[Tooltip("Enable a special timing window at the end of the attack to chain the next combo hit.")]
public bool useComboTimingWindow = false;
@@ -76,7 +78,6 @@ namespace Invector.vMelee
[Tooltip("The time scale to use during the combo window for a slow-motion effect.")]
public float comboWindowTimeScale = 0.1f;
// --- END MODIFICATION ---
[Header("Slow Motion Settings")]
[Tooltip("Enable slow motion effect during this attack based on conditions below.")]
@@ -104,12 +105,17 @@ namespace Invector.vMelee
private bThirdPersonController _characterController;
private bool _isRotationLockedByThis;
private bool _comboWindowEffectTriggered; // --- NEW ---: Tracks if the slow-mo effect has been fired.
private bool _comboWindowEffectTriggered;
private Animator _animator;
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
if (_animator == null) _animator = animator;
// --- MODIFICATION: Assign a unique ID to this state instance ---
myAttackInstanceId = ++activeAttackInstanceId;
// --- END MODIFICATION ---
mFighter = animator.GetComponent<vIAttackListener>();
_characterController = animator.GetComponent<bThirdPersonController>();
@@ -122,7 +128,7 @@ namespace Invector.vMelee
isAttacking = true;
isActive = false;
m_hasScaledTime = false;
_comboWindowEffectTriggered = false; // --- NEW ---: Reset the flag on enter.
_comboWindowEffectTriggered = false;
if (_characterController != null)
{
@@ -134,12 +140,41 @@ namespace Invector.vMelee
mFighter.OnEnableAttack();
if (debug)
Debug.Log($"({damageType}) OnStateEnter. The trigger that started this state has been consumed by the Animator.");
Debug.Log($"({damageType}, ID: {myAttackInstanceId}) OnStateEnter. Now the authoritative state.");
// --- MODIFICATION: Immediately block input on enter ---
// This ensures the new state takes control right away.
BlockAttack(true);
// --- END MODIFICATION ---
if (attackTimeScaleStart < 0f) attackTimeScaleStart = startDamage;
if (attackTimeScaleEnd < 0f) attackTimeScaleEnd = endDamage;
}
void BlockAttack(bool block)
{
// --- MODIFICATION: Only allow the authoritative state to change the lock ---
if (myAttackInstanceId != activeAttackInstanceId)
{
if(debug) Debug.Log($"({damageType}, ID: {myAttackInstanceId}) Tried to change block but I am not the active instance ({activeAttackInstanceId}). Ignoring.");
return;
}
// --- END MODIFICATION ---
if (Player.Instance != null)
{
var meleeInput = Player.Instance.MeleeCombatInput;
if (meleeInput != null)
{
if (meleeInput.BlockAttack != block) // Only log/change if there is a change
{
if(debug) Debug.Log($"({damageType}, ID: {myAttackInstanceId}) Setting BlockAttack to: {block}");
meleeInput.BlockAttack = block;
}
}
}
}
override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
if (Player.Instance.ActiveWeaponTrail)
@@ -148,7 +183,6 @@ namespace Invector.vMelee
float currentNormalizedTime = stateInfo.normalizedTime % 1;
if (currentNormalizedTime == 0 && stateInfo.normalizedTime > 0.5f) currentNormalizedTime = 1f;
// --- NEW: Centralized combo logic management ---
ManageComboLogic(currentNormalizedTime);
if (_characterController != null && _characterController.lockRotation)
@@ -182,7 +216,7 @@ namespace Invector.vMelee
override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
if (debug) Debug.Log($"({damageType}) OnStateExit.");
if (debug) Debug.Log($"({damageType}, ID: {myAttackInstanceId}) OnStateExit.");
if (isActive)
{
@@ -196,67 +230,52 @@ namespace Invector.vMelee
isAttacking = false;
m_hasScaledTime = false;
// The TimeController now reliably handles resetting the time scale via its own coroutine.
// A manual reset here is no longer necessary and could cause conflicts.
/*
if (mFighter != null && resetAttackTrigger)
{
mFighter.ResetAttackTriggers();
if (debug) Debug.Log($"({damageType}) Final trigger reset on exit due to 'Reset Attack Trigger' flag.");
}
*/
if (_comboWindowEffectTriggered && TimeController.Instance != null)
{
_comboWindowEffectTriggered = false;
TimeController.Instance.Reset();
}
if (_characterController != null && _isRotationLockedByThis)
{
_characterController.lockRotation = false;
_isRotationLockedByThis = false;
}
// --- MODIFICATION: When exiting, ensure the attack is unblocked. ---
// This is a safety net. If this was the last attack in a combo,
// we need to make sure the input is unlocked for future actions.
BlockAttack(false);
// --- END MODIFICATION ---
}
/// <summary>
/// Manages the combo logic, including input window and time scale effects.
/// </summary>
private void ManageComboLogic(float currentNormalizedTime)
{
if (!useComboTimingWindow)
if (blockInputBeforeTime > 0f)
{
// Fallback to old logic if new system is disabled for this state
if (resetAttackTrigger && currentNormalizedTime >= resetTriggerBeforeTime)
if (currentNormalizedTime >= blockInputBeforeTime)
{
if (mFighter != null)
mFighter.ResetAttackTriggers();
BlockAttack(false); // Unlock input
}
else
{
BlockAttack(true); // Block input
}
return;
}
// --- FIX 2: Corrected window logic ---
if (!useComboTimingWindow) return;
bool isInsideWindow = currentNormalizedTime >= comboWindowStartTime;
if (isInsideWindow)
{
// --- We are INSIDE the combo window ---
// We STOP resetting the trigger to allow the player's input to register.
if (!_comboWindowEffectTriggered && TimeController.Instance != null)
{
_comboWindowEffectTriggered = true;
TimeController.Instance.SetTimeScaleForRealTimeSec(comboWindowTimeScale, comboWindowDuration, false);
if (debug) Debug.Log($"({damageType}) COMBO WINDOW OPEN. Accepting input. Animator mode set to UnscaledTime.");
if (debug) Debug.Log($"({damageType}) COMBO WINDOW OPEN. Accepting input.");
}
}
else
{
// --- We are OUTSIDE the combo window ---
// We continuously reset the trigger to consume any premature/late input.
if (mFighter != null)
mFighter.ResetAttackTriggers();
}
}
// --- Other methods remain unchanged ---

View File

@@ -4,6 +4,7 @@ using Invector.vEventSystems;
using Invector.vMelee;
using UnityEngine;
using System.Collections;
using UnityEngine.Android;
namespace Beyond
{
@@ -23,6 +24,8 @@ namespace Beyond
internal vMeleeManager meleeManager;
protected bool _isAttacking;
public bool BlockAttack { get; set; }
public bool isAttacking
{
get => _isAttacking || (cc != null && cc.IsAnimatorTag("Attack"));
@@ -97,6 +100,7 @@ namespace Beyond
{
Debug.LogWarning("bMeleeCombatInput: AutoTargetting component not found. Dash towards target will require manual aiming.");
}
BlockAttack = false;
}
protected override void LateUpdate()
@@ -241,7 +245,7 @@ namespace Beyond
public virtual bool MeleeAttackConditions()
{
if (meleeManager == null) meleeManager = GetComponent<vMeleeManager>();
return meleeManager != null && cc != null && cc.isGrounded && !cc.customAction && !cc.IsAnimatorTag("special") &&
return !BlockAttack && meleeManager != null && cc != null && cc.isGrounded && !cc.customAction && !cc.IsAnimatorTag("special") &&
!cc.isJumping && !cc.isCrouching && !cc.isRolling && !isEquipping &&
(cc.animator != null && !cc.animator.IsInTransition(cc.baseLayer)) && !isRotatingAndDashing;
}

View File

@@ -18,10 +18,6 @@ namespace Beyond
public bool useAnimationBasedRotation = false;
[Header("Beyond's Strafe Combat Settings")]
[Tooltip("The minimum horizontal input value to trigger a side roll instead of a forward/backward one.")]
[Range(0.1f, 1.0f)]
public float strafeRollInputThreshold = 0.3f;
[Tooltip("The name of the animation state to play when rolling forward while strafing. (This is now disabled)")]
public string strafeRollForwardAnim = "Roll_Forward"; // Kept for reference, but won't be used
[Tooltip("The name of the animation state to play when rolling backward while strafing.")]
@@ -31,15 +27,19 @@ namespace Beyond
[Tooltip("The name of the animation state to play when rolling right while strafing.")]
public string strafeRollRightAnim = "Roll_Right";
// --- UPDATED FIELDS FOR INDEPENDENT CONTROL ---
// --- UPDATED FIELDS FOR FINER CONTROL ---
[Header("Beyond's Strafe Roll Correction")]
[Tooltip("The fixed angle to rotate the character before a LEFT strafe roll.")]
[Range(0f, 90f)]
public float strafeRollLeftCorrectionAngle = 45f;
[Tooltip("An additional angle to apply for a LEFT strafe roll.")]
[Range(-90f, 90f)]
public float strafeRollLeftCorrectionAngle = -45f;
[Tooltip("The fixed angle to rotate the character before a RIGHT strafe roll.")]
[Range(0f, 90f)]
public float strafeRollRightCorrectionAngle = 70f;
[Tooltip("An additional angle to apply for a RIGHT strafe roll.")]
[Range(-90f, 90f)]
public float strafeRollRightCorrectionAngle = 45f;
[Tooltip("An additional angle to apply for a BACKWARD strafe roll. Use 0 for a standard 180-degree roll.")]
[Range(-180f, 180f)]
public float strafeRollBackwardCorrectionAngle = 0f;
// --- END OF UPDATED FIELDS ---
public bool GodMode
@@ -64,27 +64,21 @@ namespace Beyond
protected override void RollBehavior()
{
// If we are not strafing, use the default Invector roll behavior.
if (!isStrafing)
{
base.RollBehavior();
return;
}
// Custom Strafe Roll with Root Motion (no rotation logic needed here anymore)
if (!isRolling)
{
return;
}
Vector3 deltaPosition = new Vector3(animator.deltaPosition.x, 0f, animator.deltaPosition.z);
Vector3 v = (deltaPosition / Time.deltaTime) * (1f - stopMoveWeight);
if (rollUseGravity && animator.GetNormalizedTime(baseLayer) >= rollUseGravityTime)
{
v.y = _rigidbody.linearVelocity.y;
}
_rigidbody.linearVelocity = v;
}
@@ -120,7 +114,12 @@ namespace Beyond
}
}
// --- MODIFIED METHOD WITH TWO-ANGLE LOGIC ---
// --- FINAL ROBUST METHOD ---
// This logic now handles all specified cases:
// 1. Sets a base rotation facing the camera to ensure consistency.
// 2. Defaults to a backward roll if input is near-zero (fixing the lingering roll issue).
// 3. Never uses the forward roll; it triggers a backward roll instead.
// 4. Applies a unique correction angle for backward rolls as well as side rolls.
private void TriggerStrafeRoll(string forwardAnim, string backwardAnim, string leftAnim, string rightAnim)
{
OnRoll.Invoke();
@@ -129,42 +128,59 @@ namespace Beyond
currentStaminaRecoveryDelay = 2f;
string animToPlay;
float correctionAngle = 0f;
// Prioritize side rolls based on horizontal input.
if (Mathf.Abs(horizontalSpeed) > strafeRollInputThreshold)
// Determine the base "forward" direction from the camera, ignoring vertical tilt.
Vector3 baseForward = Vector3.Scale(Camera.main.transform.forward, new Vector3(1, 0, 1)).normalized;
if (baseForward == Vector3.zero) baseForward = transform.forward; // Fallback
// Set the character's rotation to this base direction. This provides a clean slate.
transform.rotation = Quaternion.LookRotation(baseForward);
// Check if input is negligible (joystick is centered).
// input.sqrMagnitude is more efficient than input.magnitude. 0.1*0.1=0.01.
if (input.sqrMagnitude < 0.01f)
{
float correction = 0f;
// Check if rolling right (positive horizontal speed)
// If no input, always perform a backward roll.
animToPlay = backwardAnim;
correctionAngle = strafeRollBackwardCorrectionAngle;
}
else
{
// If there IS input, determine the dominant direction.
if (Mathf.Abs(verticalSpeed) >= Mathf.Abs(horizontalSpeed))
{
// Vertical input is dominant.
// ALWAYS use the backward roll, regardless of forward or backward input.
animToPlay = backwardAnim;
correctionAngle = strafeRollBackwardCorrectionAngle;
}
else
{
// Horizontal input is dominant.
if (horizontalSpeed > 0)
{
animToPlay = rightAnim;
// For a right roll, we apply a negative rotation (turn left) to angle the trajectory forward.
correction = -strafeRollRightCorrectionAngle;
correctionAngle = strafeRollRightCorrectionAngle;
}
// Otherwise, rolling left
else
{
animToPlay = leftAnim;
// For a left roll, we apply a positive rotation (turn right) to angle the trajectory forward.
correction = strafeRollLeftCorrectionAngle;
correctionAngle = strafeRollLeftCorrectionAngle;
}
}
}
// Apply the calculated rotation instantly around the Y-axis.
if (correction != 0)
// Apply the chosen correction angle if it's not zero.
if (correctionAngle != 0f)
{
transform.Rotate(0, correction, 0);
}
}
// If horizontal input is not met, default to the backward roll.
else
{
animToPlay = backwardAnim;
transform.Rotate(0, correctionAngle, 0);
}
// Play the selected animation.
animator.CrossFadeInFixedTime(animToPlay, rollTransition, baseLayer);
}
// --- END OF MODIFIED METHOD ---
// --- END OF FINAL METHOD ---
public void OnEvadeStart() { if (!m_GodMode) isImmortal = true; }
public void OnEvadeEnd() { if (!m_GodMode) isImmortal = false; }