using UnityEngine;
public class HeadBob
{
#region Variables
HeadBobData m_data;
float m_xScroll;
float m_yScroll;
bool m_resetted;
Vector3 m_finalOffset;
float m_currentStateHeight = 0f;
#endregion
#region Properties
public Vector3 FinalOffset => m_finalOffset;
public bool Resetted => m_resetted;
public float CurrentStateHeight
{
get => m_currentStateHeight;
set => m_currentStateHeight = value;
}
#endregion
#region Custom Methods
public HeadBob(HeadBobData _data, float _moveBackwardsMultiplier, float _moveSideMultiplier)
{
m_data = _data;
m_data.MoveBackwardsFrequencyMultiplier = _moveBackwardsMultiplier;
m_data.MoveSideFrequencyMultiplier = _moveSideMultiplier;
m_xScroll = 0f;
m_yScroll = 0f;
m_resetted = false;
m_finalOffset = Vector3.zero;
}
public void ScrollHeadBob(bool _running, bool _crouching, Vector2 _input)
{
m_resetted = false;
float _amplitudeMultiplier;
float _frequencyMultiplier;
float _additionalMultiplier; // when moving backwards or to sides
_amplitudeMultiplier = _running ? m_data.runAmplitudeMultiplier : 1f;
_amplitudeMultiplier = _crouching ? m_data.crouchAmplitudeMultiplier : _amplitudeMultiplier;
_frequencyMultiplier = _running ? m_data.runFrequencyMultiplier : 1f;
_frequencyMultiplier = _crouching ? m_data.crouchFrequencyMultiplier : _frequencyMultiplier;
_additionalMultiplier = _input.y == -1 ? m_data.MoveBackwardsFrequencyMultiplier : 1f;
_additionalMultiplier = _input.x != 0 & _input.y == 0 ? m_data.MoveSideFrequencyMultiplier : _additionalMultiplier;
m_xScroll += Time.deltaTime * m_data.xFrequency * _frequencyMultiplier; // you can also multiply this by _additionalMultiplier but it looks unnatural a bit;
m_yScroll += Time.deltaTime * m_data.yFrequency * _frequencyMultiplier;
float _xValue;
float _yValue;
_xValue = m_data.xCurve.Evaluate(m_xScroll);
_yValue = m_data.yCurve.Evaluate(m_yScroll);
Debug.Log(_yValue);
m_finalOffset.x = _xValue * m_data.xAmplitude * _amplitudeMultiplier * _additionalMultiplier;
m_finalOffset.y = _yValue * m_data.yAmplitude * _amplitudeMultiplier * _additionalMultiplier;
}
public void ResetHeadBob()
{
m_resetted = true;
m_xScroll = 0f;
m_yScroll = 0f;
m_finalOffset = Vector3.zero;
}
#endregion
}
I normally find that animation events work better for footsteps as they match the animation. If you want to trigger them on those graph locations then it would probably be ok to check for the change in slope. For example, you know the last and current position to get the slope. If the slope changes from positive to negative or negative to positive then trigger a footstep.
hey, thank you for answer. This is a first person controller so no model and animation events.
I thought exactly like you, trigger accordding to curve value but i search better solutions if there is one.
In my game I added a animation curve to my animations. When the curve changed sign a footstep sound was played. You can do the same but in your case when the curve direction changes.
Edit: a bit off topic but events doesnt blend between animations like a curve does so I do not agree with the event statement earlier.
You’re using this FirstPersonController?
That’s a bit of a problematic situation. You’d need a reference to the HeadBob
object and its _xValue
value, to ensure that the footstep sounds always remain in sync with the head bob animation.
Although, I guess that’s not really an issue in practice, since everything gets reset whenever the player stops giving movement input.
So, you could just copy-paste the code for how _xValue
gets calculated from HeadBob.ScrollHeadBob
, and play a sound whenever it crosses 0.5f and 1.5f, and it should be fine in that sense.
But, even then, you could not access some necessary private state to make that happen…
public sealed class FirstPersonControllerWithFootstepSounds : FirstPersonController
{
[SerializeField] MovementInputData m_movementInputData;
[SerializeField] HeadBobData m_headBobData;
float m_xScroll;
float m_xValueLastFrame;
protected override void HandleHeadBob()
{
base.HandleHeadBob();
if(m_movementInputData.HasInput
&& m_isGrounded // <- private
&& !m_hitWall // <- private
&& !m_duringCrouchAnimation) // <- private
{
var frequencyMultiplier = m_movementInputData.IsRunning ? m_headBobData.runFrequencyMultiplier : 1f;
frequencyMultiplier = m_movementInputData.IsCrouching ? m_headBobData.crouchFrequencyMultiplier : _frequencyMultiplier;
m_xScroll += Time.deltaTime * m_headBobData.xFrequency * _frequencyMultiplier;
var xValue = m_headBobData.xCurve.Evaluate(m_xScroll);
if(xValue >= 0.5f && (m_xValueLastFrame < 0.5f || m_xValueLastFrame > 1.5f))
{
PlayFootstepSound();
}
else if(xValue >= 1.5f && m_xValueLastFrame < 1.5f)
{
PlayFootstepSound();
}
m_xValueLastFrame = xValue;
}
void PlayFootstepSound() => ...;
}
So it looks like you’re going to need to do some modifications to the source code to implement this.
My experience is you don’t necessarily need phase synchronization with the head bobbing.
You just need the rate of sound to match the rate of bobbing.
Assuming your bobbing is driven by linear flat distance, drive your footsteps the same way.
I also dislike deep integration for stuff like this: you do all the integration and then a month later you decide to use a different FPS controller anyway and now you have to do it all over. UGH.
Here’s a quick script that doesn’t require hardly any integration: just drop this script on the moving player, give it some AudioSources for the footsteps and you’re done.
If you change controllers in the future, this script can remain the same.
How it works: it observes the linear flat distance traveled by the Transform, decides when to clank.
using UnityEngine;
// @kurtdekker - super-simple footstep clanker, minimal integration
//
// Two steps to use:
// - add this to your moving player character
// - add and drag in some Audiosources for foosteps
//
// optional: disable this script when you jump and you're airborne
public class FootstepObserver : MonoBehaviour
{
[Header( "Give a few different ones:")]
public AudioSource[] Footsteps;
[Header( "How much distance constitutes one step.")]
public float DistancePerStep = 2.0f;
Vector3 currentPosition;
float accumulatedDistance;
void RecordPlayerPosition()
{
Vector3 position = transform.position;
// considers only X / Y movement
position.y = 0;
currentPosition = position;
}
private void OnEnable()
{
RecordPlayerPosition();
}
void PlayFootstep()
{
int which = Random.Range(0, Footsteps.Length);
AudioSource footstep = Footsteps[which];
// optional vary volume an pitch a bit if you like:
footstep.volume = Random.Range(0.8f, 1.2f);
footstep.pitch = Random.Range(0.8f, 1.2f);
footstep.Play();
}
void Update()
{
Vector3 last = currentPosition;
RecordPlayerPosition();
float distance = Vector3.Distance(last, currentPosition);
accumulatedDistance += distance;
// time to clank?
if (accumulatedDistance > DistancePerStep)
{
accumulatedDistance = 0;
PlayFootstep();
}
}
}
Integration looks like:
If you want that character controller above (BasicFPCC), here you go:
That one has run, walk, jump, slide, crouch… it’s crazy-nutty!!