BulletHell/Assets/BulletHellTemplate/Core/Character/AdvancedCharacterController.cs

232 lines
8.3 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace BulletHellTemplate
{
/// <summary>
/// Handles advanced character movement, including walking, jumping, and being pushed.
/// Requires a CharacterController component to function correctly.
/// </summary>
[RequireComponent(typeof(CharacterController))]
public class AdvancedCharacterController : MonoBehaviour
{
public float moveSpeed = 5f; // Speed at which the character moves
public float pushForce = 10f; // Force applied when being pushed
public float rotationSpeed = 720f; // Speed of character rotation
public float jumpHeight = 2f; // Height the character can jump
public float gravity = 9.81f; // Gravity applied to the character
public float maxFallVelocity = 40f; // Maximum velocity when falling
private CharacterController characterController; // The CharacterController component
private Vector3 moveDirection = Vector3.zero; // Current direction of movement
private Vector3 pushDirection = Vector3.zero; // Direction of any applied push force
private float verticalVelocity; // Vertical velocity for gravity and jumping
private bool isJumping = false; // Indicates if the character is currently jumping
private bool isMovementStopped = false;
[HideInInspector]public bool CanRotateWhileStopped { get; private set; } = false;
void Start()
{
// Initialize the CharacterController component
characterController = GetComponent<CharacterController>();
}
void Update()
{
// Apply gravity and move the character each frame
ApplyGravity();
MoveCharacter();
}
/// <summary>
/// Moves the character in a specified direction.
/// </summary>
/// <param name="direction">Direction vector for movement.</param>
public void Move(Vector3 direction)
{
if (GameplayManager.Singleton.IsPaused())
{
moveDirection = Vector3.zero;
return;
}
if (isMovementStopped)
{
if (CanRotateWhileStopped && direction.magnitude > 0)
{
float targetAngle = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg;
float angle = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetAngle, ref rotationSpeed, 0.1f);
transform.rotation = Quaternion.Euler(0, angle, 0);
}
moveDirection = Vector3.zero;
return;
}
if (direction.magnitude > 0)
{
float targetAngle = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg;
float angle = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetAngle, ref rotationSpeed, 0.1f);
transform.rotation = Quaternion.Euler(0, angle, 0);
moveDirection = direction.normalized * moveSpeed;
}
else
{
moveDirection = Vector3.zero;
}
}
/// <summary>
/// Makes the character jump if grounded.
/// </summary>
public void Jump()
{
if (characterController.isGrounded)
{
isJumping = true;
verticalVelocity = Mathf.Sqrt(2 * jumpHeight * gravity);
}
}
/// <summary>
/// Applies a force to push the character.
/// </summary>
/// <param name="force">The force to apply.</param>
public void Push(Vector3 force)
{
pushDirection = force;
}
/// <summary>
/// Alters the character's movement speed.
/// </summary>
/// <param name="newSpeed">The new speed value.</param>
public void AlterSpeed(float newSpeed)
{
moveSpeed = newSpeed;
}
/// <summary>
/// Gets the current speed of the character.
/// </summary>
/// <returns>The magnitude of the character's movement velocity.</returns>
public float GetCurrentSpeed()
{
Vector3 horizontalVelocity = new Vector3(moveDirection.x + pushDirection.x, 0, moveDirection.z + pushDirection.z);
return horizontalVelocity.magnitude;
}
private void ApplyGravity()
{
if (characterController.isGrounded)
{
if (!isJumping)
{
verticalVelocity = -gravity * Time.deltaTime;
}
else
{
verticalVelocity -= gravity * Time.deltaTime;
isJumping = false;
}
}
else
{
verticalVelocity -= gravity * Time.deltaTime;
if (verticalVelocity < -maxFallVelocity)
{
verticalVelocity = -maxFallVelocity;
}
}
}
private void MoveCharacter()
{
if (isMovementStopped)
{
characterController.Move(Vector3.zero);
return;
}
Vector3 velocity = moveDirection + pushDirection;
velocity.y = verticalVelocity;
if (GameplayManager.Singleton.IsPaused())
{
characterController.Move(Vector3.zero);
}
else
{
characterController.Move(velocity * Time.deltaTime);
}
// Gradually reduce push force over time
pushDirection = Vector3.Lerp(pushDirection, Vector3.zero, Time.deltaTime * pushForce);
}
/// <summary>
/// Stops the movement of the character temporarily, allowing optional rotation.
/// </summary>
/// <param name="allowRotation">Whether rotation is allowed while movement is stopped.</param>
public void StopMovement(bool allowRotation = false)
{
isMovementStopped = true;
CanRotateWhileStopped = allowRotation;
}
/// <summary>
/// Resumes the movement of the character.
/// </summary>
public void ResumeMovement()
{
isMovementStopped = false;
CanRotateWhileStopped = false;
}
/// <summary>
/// Performs a dash in the specified direction while ensuring that the character does not pass through walls.
/// The dash increases the movement speed temporarily, locks the movement direction, and ensures the character faces the dash direction.
/// </summary>
/// <param name="dashSpeed">The speed to dash at.</param>
/// <param name="dashDuration">The duration of the dash in seconds.</param>
/// <param name="direction">The direction vector in which to dash, typically coming from the joystick input.</param>
public IEnumerator Dash(Vector3 direction, float dashSpeed, float dashDuration)
{
// Normalize the dash direction
Vector3 dashDirection = direction.normalized;
// Rotate character to face dash direction
if (dashDirection.magnitude > 0)
{
float targetAngle = Mathf.Atan2(dashDirection.x, dashDirection.z) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0, targetAngle, 0); // Instantly rotate to dash direction
}
// Store the original speed and temporarily disable normal movement
float originalSpeed = moveSpeed;
moveSpeed = 0f;
float elapsedTime = 0f;
// Perform the dash by temporarily setting the dash speed
while (elapsedTime < dashDuration)
{
// Apply dash movement in the locked dash direction using CharacterController
Vector3 dashMovement = dashDirection * dashSpeed * Time.deltaTime;
characterController.Move(dashMovement);
// Increase the elapsed time
elapsedTime += Time.deltaTime;
yield return null;
}
// After the dash, return to the original movement speed
moveSpeed = originalSpeed;
}
}
}