323 lines
12 KiB
C#
323 lines
12 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
|
|
/*Simple player movement controller, based on character controller component,
|
|
with footstep system based on check the current texture of the component*/
|
|
namespace Raygeas
|
|
{
|
|
public class PlayerController : MonoBehaviour
|
|
{
|
|
//Variables for footstep system list
|
|
[System.Serializable]
|
|
public class GroundLayer
|
|
{
|
|
public string layerName;
|
|
public Texture2D[] groundTextures;
|
|
public AudioClip[] footstepSounds;
|
|
}
|
|
|
|
[Header("Movement")]
|
|
|
|
[Tooltip("Basic controller speed")]
|
|
[SerializeField] private float walkSpeed = 5.0f;
|
|
|
|
[Tooltip("Running controller speed")]
|
|
[SerializeField] private float runMultiplier = 3.0f;
|
|
|
|
[Tooltip("Force of the jump with which the controller rushes upwards")]
|
|
[SerializeField] private float jumpForce = 5.0f;
|
|
|
|
[Tooltip("Gravity, pushing down controller when it jumping")]
|
|
[SerializeField] private float gravity = -9.81f;
|
|
|
|
[Header("Mouse Look")]
|
|
[SerializeField] private Camera playerCamera;
|
|
[SerializeField] private float mouseSensivity = 1.0f;
|
|
[SerializeField] private float mouseVerticalClamp = 90.0f;
|
|
|
|
[Header("Input System (optional)")]
|
|
[Tooltip("Move action (Vector2). If assigned, used instead of Input.GetAxis(\"Horizontal/Vertical\").")]
|
|
[SerializeField] private InputActionReference moveAction;
|
|
[Tooltip("Look action (Vector2). If assigned, used instead of Input.GetAxis(\"Mouse X/Y\").")]
|
|
[SerializeField] private InputActionReference lookAction;
|
|
[Tooltip("Jump action (Button). If assigned, used instead of jumpKey.")]
|
|
[SerializeField] private InputActionReference jumpAction;
|
|
[Tooltip("Run action (Button). If assigned, used instead of runKey.")]
|
|
[SerializeField] private InputActionReference runAction;
|
|
|
|
[Header("Footsteps")]
|
|
[Tooltip("Footstep source")]
|
|
[SerializeField] private AudioSource footstepSource;
|
|
[SerializeField] [Range(0f, 1f)] private float walkFootstepVolume = 0.4f;
|
|
[SerializeField] [Range(0f, 1f)] private float runFootstepVolume = 0.8f;
|
|
|
|
[Tooltip("Distance for ground texture checker")]
|
|
[SerializeField] private float groundCheckDistance = 1.5f;
|
|
|
|
[Tooltip("Footsteps playing rate")]
|
|
[SerializeField] [Range(1f, 4f)] private float footstepRate = 1f;
|
|
|
|
[Tooltip("Footstep rate when player running")]
|
|
[SerializeField] [Range(1f, 4f)] private float runningFootstepRate = 1.5f;
|
|
|
|
[Tooltip("Add textures for this layer and add sounds to be played for this texture")]
|
|
public List<GroundLayer> groundLayers = new List<GroundLayer>();
|
|
|
|
//Private movement variables
|
|
private float _horizontalMovement;
|
|
private float _verticalMovement;
|
|
private float _currentSpeed;
|
|
private Vector3 _moveDirection;
|
|
private Vector3 _velocity;
|
|
private CharacterController _characterController;
|
|
private bool _isRunning;
|
|
|
|
//Private mouselook variables
|
|
private float _verticalRotation;
|
|
private float _yAxis;
|
|
private float _xAxis;
|
|
private bool _activeRotation;
|
|
|
|
//Private footstep system variables
|
|
private Terrain[] _terrains;
|
|
private Dictionary<Terrain, TerrainData> _terrainDataMap = new();
|
|
private Dictionary<Terrain, TerrainLayer[]> _terrainLayersMap = new();
|
|
private AudioClip _previousClip;
|
|
private Texture2D _currentTexture;
|
|
private RaycastHit _groundHit;
|
|
private float _nextFootstep;
|
|
|
|
private void Awake()
|
|
{
|
|
GetTerrainData();
|
|
_characterController = GetComponent<CharacterController>();
|
|
Cursor.visible = false;
|
|
Cursor.lockState = CursorLockMode.Locked;
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
if (moveAction != null && moveAction.action != null) moveAction.action.Enable();
|
|
if (lookAction != null && lookAction.action != null) lookAction.action.Enable();
|
|
if (jumpAction != null && jumpAction.action != null) jumpAction.action.Enable();
|
|
if (runAction != null && runAction.action != null) runAction.action.Enable();
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
if (moveAction != null && moveAction.action != null) moveAction.action.Disable();
|
|
if (lookAction != null && lookAction.action != null) lookAction.action.Disable();
|
|
if (jumpAction != null && jumpAction.action != null) jumpAction.action.Disable();
|
|
if (runAction != null && runAction.action != null) runAction.action.Disable();
|
|
}
|
|
|
|
//Getting all terrain data for footstep system
|
|
private void GetTerrainData()
|
|
{
|
|
_terrains = Terrain.activeTerrains;
|
|
foreach (Terrain terrain in _terrains)
|
|
{
|
|
_terrainDataMap[terrain] = terrain.terrainData;
|
|
_terrainLayersMap[terrain] = terrain.terrainData.terrainLayers;
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
Movement();
|
|
MouseLook();
|
|
GroundChecker();
|
|
}
|
|
|
|
private void FixedUpdate()
|
|
{
|
|
SpeedCheck();
|
|
}
|
|
|
|
//Character controller movement
|
|
private void Movement()
|
|
{
|
|
if (_characterController.isGrounded && _velocity.y < 0)
|
|
{
|
|
_velocity.y = -2f;
|
|
}
|
|
|
|
bool jumpPressed = false;
|
|
if (jumpAction != null && jumpAction.action != null)
|
|
{
|
|
jumpPressed = jumpAction.action.triggered;
|
|
}
|
|
|
|
if (jumpPressed && _characterController.isGrounded)
|
|
{
|
|
_velocity.y = Mathf.Sqrt(jumpForce * -2f * gravity);
|
|
}
|
|
|
|
if (moveAction != null && moveAction.action != null)
|
|
{
|
|
Vector2 mv = moveAction.action.ReadValue<Vector2>();
|
|
_horizontalMovement = mv.x;
|
|
_verticalMovement = mv.y;
|
|
}
|
|
|
|
_moveDirection = transform.forward * _verticalMovement + transform.right * _horizontalMovement;
|
|
|
|
if (runAction != null && runAction.action != null)
|
|
{
|
|
_isRunning = runAction.action.ReadValue<float>() > 0.5f;
|
|
}
|
|
|
|
_currentSpeed = walkSpeed * (_isRunning ? runMultiplier : 1f);
|
|
_characterController.Move(_moveDirection * _currentSpeed * Time.deltaTime);
|
|
|
|
_velocity.y += gravity * Time.deltaTime;
|
|
_characterController.Move(_velocity * Time.deltaTime);
|
|
|
|
}
|
|
|
|
private void MouseLook()
|
|
{
|
|
if (lookAction != null && lookAction.action != null)
|
|
{
|
|
Vector2 look = lookAction.action.ReadValue<Vector2>();
|
|
_xAxis = look.x;
|
|
_yAxis = look.y;
|
|
}
|
|
|
|
_verticalRotation += -_yAxis * mouseSensivity;
|
|
_verticalRotation = Mathf.Clamp(_verticalRotation, -mouseVerticalClamp, mouseVerticalClamp);
|
|
playerCamera.transform.localRotation = Quaternion.Euler(_verticalRotation, 0, 0);
|
|
transform.rotation *= Quaternion.Euler(0, _xAxis * mouseSensivity, 0);
|
|
}
|
|
|
|
//Playing footstep sound when controller moves and grounded
|
|
private void SpeedCheck()
|
|
{
|
|
if (_characterController.isGrounded && (_horizontalMovement != 0 || _verticalMovement != 0))
|
|
{
|
|
float currentFootstepRate = (_isRunning ? runningFootstepRate : footstepRate);
|
|
|
|
if (_nextFootstep >= 100f)
|
|
{
|
|
{
|
|
PlayFootstep();
|
|
_nextFootstep = 0;
|
|
}
|
|
}
|
|
_nextFootstep += (currentFootstepRate * walkSpeed);
|
|
}
|
|
}
|
|
|
|
|
|
//Check where the controller is now and identify the texture of the component
|
|
private void GroundChecker()
|
|
{
|
|
Ray checkerRay = new Ray(transform.position + (Vector3.up * 0.1f), Vector3.down);
|
|
|
|
if (Physics.Raycast(checkerRay, out _groundHit, groundCheckDistance))
|
|
{
|
|
foreach (Terrain terrain in _terrains)
|
|
{
|
|
if (_groundHit.collider.gameObject == terrain.gameObject)
|
|
{
|
|
_currentTexture = _terrainLayersMap[terrain][GetTerrainTexture(terrain, transform.position)].diffuseTexture;
|
|
break;
|
|
}
|
|
}
|
|
if (_groundHit.collider.GetComponent<Renderer>())
|
|
{
|
|
_currentTexture = GetRendererTexture();
|
|
}
|
|
}
|
|
}
|
|
|
|
//Play a footstep sound depending on the specific texture
|
|
private void PlayFootstep()
|
|
{
|
|
for (int i = 0; i < groundLayers.Count; i++)
|
|
{
|
|
for (int k = 0; k < groundLayers[i].groundTextures.Length; k++)
|
|
{
|
|
if (_currentTexture == groundLayers[i].groundTextures[k])
|
|
{
|
|
footstepSource.PlayOneShot(RandomClip(groundLayers[i].footstepSounds));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Returns an audio clip from an array, prevents a newly played clip from being repeated and randomize pitch
|
|
private AudioClip RandomClip(AudioClip[] clips)
|
|
{
|
|
int attempts = 2;
|
|
footstepSource.pitch = Random.Range(0.9f, 1.1f);
|
|
|
|
if (_isRunning)
|
|
{
|
|
footstepSource.volume = runFootstepVolume;
|
|
}
|
|
else
|
|
{
|
|
footstepSource.volume = walkFootstepVolume;
|
|
}
|
|
|
|
AudioClip selectedClip = clips[Random.Range(0, clips.Length)];
|
|
|
|
while (selectedClip == _previousClip && attempts > 0)
|
|
{
|
|
selectedClip = clips[Random.Range(0, clips.Length)];
|
|
|
|
attempts--;
|
|
}
|
|
_previousClip = selectedClip;
|
|
return selectedClip;
|
|
}
|
|
|
|
//Return an array of textures depending on location of the controller on terrain
|
|
private float[] GetTerrainTexturesArray(Terrain terrain, Vector3 controllerPosition)
|
|
{
|
|
TerrainData terrainData = _terrainDataMap[terrain];
|
|
Vector3 terrainPosition = terrain.transform.position;
|
|
|
|
int positionX = (int)(((controllerPosition.x - terrainPosition.x) / terrainData.size.x) * terrainData.alphamapWidth);
|
|
int positionZ = (int)(((controllerPosition.z - terrainPosition.z) / terrainData.size.z) * terrainData.alphamapHeight);
|
|
|
|
float[,,] layerData = terrainData.GetAlphamaps(positionX, positionZ, 1, 1);
|
|
|
|
float[] texturesArray = new float[layerData.GetUpperBound(2) + 1];
|
|
for (int n = 0; n < texturesArray.Length; ++n)
|
|
{
|
|
texturesArray[n] = layerData[0, 0, n];
|
|
}
|
|
return texturesArray;
|
|
}
|
|
|
|
//Returns the zero index of the prevailing texture based on the controller location on terrain
|
|
private int GetTerrainTexture(Terrain terrain, Vector3 controllerPosition)
|
|
{
|
|
float[] array = GetTerrainTexturesArray(terrain, controllerPosition);
|
|
float maxArray = 0;
|
|
int maxArrayIndex = 0;
|
|
|
|
for (int n = 0; n < array.Length; ++n)
|
|
{
|
|
if (array[n] > maxArray)
|
|
{
|
|
maxArrayIndex = n;
|
|
maxArray = array[n];
|
|
}
|
|
}
|
|
return maxArrayIndex;
|
|
}
|
|
|
|
//Returns the current main texture of renderer where the controller is located now
|
|
private Texture2D GetRendererTexture()
|
|
{
|
|
Texture2D texture;
|
|
texture = (Texture2D)_groundHit.collider.gameObject.GetComponent<Renderer>().material.mainTexture;
|
|
return texture;
|
|
}
|
|
}
|
|
}
|