Since starting development on a third person shooter/action adventure I have had a constant problem with the character controller. I've followed numerous YouTube tutorials from Brackeys, and Code Monkey. Nothing seemed to work. Then came Unitys' new TPS controller using the new input system. which - just - clicked! I started building it up from the provided base using guidance from Code Monkey. I was getting on well with the controller in its default state - that was until I realised I needed to integrate with Pixel Crushers Dialogue system, something I only noticed once I tested the game with a controller. The PC website has a addon that I attempted to use with the controller as it was - this didn't work and I am a person that is very focused on quick wins!
In the end the quickest win was to rebuild the controller in my own image! well, it does look very similar - why not it, works right?!
Here's a snippet;
private void HandleMovement()
{
//may need to adjust in the future to take in to account gravity calcs for falling animations.
float targetSpeed = isSprinting ? _sprintSpeed : _movementSpeed;
if (_moveDirection == Vector2.zero) targetSpeed = 0.0f;
float currentHorizontalSpeed = new Vector3(_characterController.velocity.x, 0.0f, _characterController.velocity.z).magnitude;
float speedOffset = 0.01f;
if (currentHorizontalSpeed < targetSpeed - speedOffset || currentHorizontalSpeed > targetSpeed + speedOffset)
{
_speed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed * _moveDirection.magnitude, Time.deltaTime * _rateOfSpeedChange);
_speed = Mathf.Round(_speed * 1000f) / 1000f;
}
else
{
_speed = targetSpeed;
}
if (!_standingInWater)
{
_animationBlend = Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * _rateOfSpeedChange);
if (_animationBlend < 0.001f)
{
_animationBlend = 0f;
}
}
else
{
_animationBlend = Mathf.Lerp(_animationBlend, targetSpeed/2, Time.deltaTime * _rateOfSpeedChange);
}
Vector3 direction = new Vector3(_moveDirection.x, 0f, _moveDirection.y).normalized;
if (direction.magnitude >= 0.1f)
{
float targetAngle = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg + cam.transform.eulerAngles.y;
float angle = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetAngle, ref turnSmoothVelocity, turnSmoothTime);
if (!_isAiming)
{
transform.rotation = Quaternion.Euler(0f, angle, 0f);
}
Vector3 moveDir = Quaternion.Euler(0f, targetAngle, 0f) * Vector3.forward;
_characterController.Move(moveDir.normalized * (_speed * Time.deltaTime) * (_standingInWater ? _waterSlowFactor: 1f));
}
_animator.SetFloat(_animSpeed, _animationBlend);
_animator.SetFloat(_animSpeedMultipler, _moveDirection.magnitude);
}
I've made a comment to add more code to take account for gravity in the future - something I do need to get round to, especially now my testing is involving climbing up and down stairs - floating doesn't look polished, does it!? haha! the other change from the default is adding a check for if the player is in water - this slows down the animation - like wading through mud - something else that needs a little more work on, but from an animation point of view.
I'm using Unity Events now in my input system as apposed to Sending Messages - and no doubt that I'll be able to integrate seamlessly with PC dialogue system - however I've now got a better understanding of interfaces, I'm repeating myself now.
Forgive the jerkiness of the movement - I'm putting it down to it not being a build. the video is to show off the movement of the camera around the player and the clamping of the camera in the vertical axis. it also shows (attempts to anyway) how the aiming works. at the moment its incorporated into the player controller script - I'll be dragging it out soon though - remember everyone, One script for one job ;-) here's a snippet of the aiming code;
private void HandleAiming()
{
//potentially to be moved out to separate class for handling aiming
if (_isAiming)
{
Vector3 worldAimTarget = centreWorldPosition;
worldAimTarget.y = transform.position.y;
Vector3 aimDirection = (worldAimTarget - transform.position).normalized;
Ray ray = Camera.main.ScreenPointToRay(_screenCentrePoint);
if (Physics.Raycast(ray, out RaycastHit rayCastHit, 999f))
{
centreWorldPosition = _aimTransform.position;
if (rayCastHit.transform.CompareTag("Enemy"))
{
var enemyhide = rayCastHit.transform.GetComponent<EnemyNPC>();
enemyhide.activeState = EnemyNPC.EnemyNPCState.Cover;
It probably does need a bit of re-work - its based on Code Monkeys aiming video but the IK isn't looking off into the distance and the hit point anymore - its looking at an actual transform placed 20 units in front of the main camera.
I think that'll do for now - one of my friends has already commented on how lengthy these have been! Until next week.
I think it's a good blog length with attractive art content forming up.
With a mostly flat level, no immediate need for footIK yet. ?