I have the code which fixed the cursed knockback issue in corner with colliders (I am doing Unity Essential tutorial) and I spent a lot of time to make and with help of AI Gemini, but there is a lot of issue… First the class is too larger and too wide spread the purpose. Second I got ‘null reference exception’ after the first start and stop of Game Mode on line 260 foreach (Vector3 direction in _directions)
, and many other problems, please help to refactor.
using UnityEngine;
using System.Collections.Generic;
// Program to control simple camera with collider on desired height, with help of Mr. Gemini (AI Google)
public class CombinedCameraController : MonoBehaviour
{
// FIELDS
[SerializeField]
float _mouseSensitivity = 2.0f;
[SerializeField]
float _rotationSpeed = 100.0f;
[SerializeField]
float _smoothTime = 0.5f;
[SerializeField]
float _moveSpeed = 3.0f;
[SerializeField]
float _raycastOffset = 0.2f;
[SerializeField]
float _moveDistanceOffset = 0.3f;
[SerializeField]
float _clampRotationDegree = 80f;
[SerializeField]
float _minMoveMagnitude = 0.0001f;
[SerializeField]
float _cameraRotationX = 0f;
[SerializeField]
float _playerRotationY = 0f;
[SerializeField]
float _triggerColliderDetectionRadius = 0.5f;
[SerializeField]
float _collisionCheckDistance = 0.01f;
[SerializeField]
float _pushBackPreventionDistance = 0.001f;
[SerializeField]
float _debugRayLength = 1.0f;
[SerializeField]
float _raycastDuration = 0.1f;
[SerializeField]
float _castColliderCoef = 100f;
[SerializeField]
float maxRaycastDistance = 5.0f;
[SerializeField]
int _stepViyalo = 15;
bool _isMoving;
Rigidbody _rigidbody;
Transform _playerTransform;
Quaternion _cameraInitialLocalRotation;
Vector3[] _directions;
Vector3 _lastMoveDirection;
Vector3 _lastRaycastOrigin;
RaycastHit _lastHit;
bool _hasHit;
// METHODS
// This method calls before the first frame
void Start()
{
_playerTransform = transform.parent;
_rigidbody = GetComponentInParent<Rigidbody>();
if (_playerTransform == null)
{
Debug.LogError("Parent Transform not found!");
enabled = false;
return;
}
if (_rigidbody == null)
{
Debug.Log("Initialization ERROR! Parent Rigidbody component cannot be found.");
enabled = false;
return;
}
_cameraInitialLocalRotation = transform.localRotation;
_rigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
//_rigidbody.isKinematic = true;
_rigidbody.useGravity = false;
_rigidbody.freezeRotation = true;
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
// This method calls per frame
void FixedUpdate()
{
HandlePlayerMovement();
PrepareDebugRays();
}
void Update()
{
HandlePlayerRotation();
HandleCameraRotation();
}
// Restricted to only left and right
public void HandlePlayerRotation()
{
// Player Rotation (Y-axis Rotation - A/D keys)
float horizontalInput = Input.GetAxis("Horizontal");
_playerRotationY += horizontalInput * _rotationSpeed * Time.deltaTime;
// Smooth player rotation using Quaternion.Slerp (Spherical-linear interpolation)
Quaternion targetPlayerRotation = Quaternion.Euler(0f, _playerRotationY, 0f);
_playerTransform.localRotation = Quaternion.Slerp(_playerTransform.localRotation, targetPlayerRotation, _smoothTime);
}
// Only back and forward
public void HandlePlayerMovement()
{
float movePerFrame = _moveSpeed * Time.fixedDeltaTime;
float verticalInput = Input.GetAxis("Vertical");
Vector3 moveDirection = _playerTransform.forward * movePerFrame * verticalInput;
_lastMoveDirection = moveDirection;
_lastRaycastOrigin = _playerTransform.position + _lastMoveDirection.normalized * _collisionCheckDistance;
_isMoving = moveDirection.magnitude > _minMoveMagnitude;
if (_isMoving)
{
Vector3 adjustedDirection;
if (CheckCollision(moveDirection, out adjustedDirection))
{
_rigidbody.MovePosition(_rigidbody.position + adjustedDirection);
}
else
{
_rigidbody.MovePosition(_rigidbody.position + adjustedDirection);
//Debug.DrawRay(_playerTransform.position, moveDirection, Color.green, 0.1f);
}
}
}
// Only up and down
public void HandleCameraRotation()
{
// Mouse Look (Camera X-axis Rotation)
float mouseY = Input.GetAxis("Mouse Y") * _mouseSensitivity;
_cameraRotationX += mouseY;
_cameraRotationX = Mathf.Clamp(_cameraRotationX, -_clampRotationDegree, _clampRotationDegree);
// For smooth camera rotation using Quaternion.Slerp (THIS IS THE KEY by Mr. Gemini)
Quaternion targetCameraRotation = _cameraInitialLocalRotation * Quaternion.Euler(_cameraRotationX, 0f, 0f);
transform.localRotation = Quaternion.Slerp(transform.localRotation, targetCameraRotation, _smoothTime);
}
public bool CheckCollision(Vector3 moveDirection, out Vector3 projectedMove)
{
projectedMove = moveDirection;
Vector3[] raycastDirections = new Vector3[]
{
//moveDirection,
Quaternion.Euler(0, 15, 0) * moveDirection,
Quaternion.Euler(0, -15, 0) * moveDirection,
Quaternion.Euler(0, 45, 0) * moveDirection,
Quaternion.Euler(0, -45, 0) * moveDirection,
Quaternion.Euler(0, 90, 0) * moveDirection,
Quaternion.Euler(0, -90, 0) * moveDirection
};
float raycastDistance = moveDirection.magnitude + _collisionCheckDistance;
Vector3 raycastOrigin = _playerTransform.position + moveDirection.normalized * _collisionCheckDistance;
List<RaycastHit> validHits = new List<RaycastHit>();
foreach (Vector3 direction in raycastDirections)
{
RaycastHit[] allHits = Physics.RaycastAll(raycastOrigin, direction, raycastDistance);
foreach (RaycastHit currentHit in allHits)
{
if ((currentHit.collider != null) && !currentHit.collider.isTrigger)
{
validHits.Add(currentHit);
}
}
}
if (validHits.Count > 0)
{
Vector3 combinedNormal = Vector3.zero;
float closestHitDistance = validHits[0].distance;
foreach (RaycastHit hit in validHits)
{
float weight = 1.0f - (hit.distance / maxRaycastDistance);
combinedNormal += hit.normal * weight;
if (hit.distance < closestHitDistance)
{
closestHitDistance = hit.distance;
}
float moveDistance = closestHitDistance - _collisionCheckDistance - _pushBackPreventionDistance;
if (moveDistance <= _minMoveMagnitude) continue;
}
combinedNormal.Normalize();
projectedMove = Vector3.ProjectOnPlane(projectedMove, combinedNormal);
return true;
}
return false;
}
public Collider[] HandleTriggerDetection()
{
Collider[] hitColliders = Physics.OverlapSphere(transform.position, _triggerColliderDetectionRadius);
foreach (Collider collider in hitColliders)
{
if (collider.isTrigger)
{
Debug.Log("Trigger entered: " + collider.name);
}
}
return hitColliders;
}
public void PrepareDebugRays()
{
_directions = CreateArrayOfDirectionsAroundYAxis(_stepViyalo, _lastMoveDirection);
}
public void OnDrawGizmosSelected()
{
foreach (Vector3 direction in _directions)
{
_hasHit = Physics.Raycast(_lastRaycastOrigin, direction, out _lastHit, direction.magnitude + _collisionCheckDistance);
if (_hasHit)
{
Gizmos.color = Color.red;
Gizmos.DrawRay(_lastRaycastOrigin, direction.normalized * _lastHit.distance);
Gizmos.color = Color.blue;
Gizmos.DrawRay(_lastHit.point, _lastHit.normal);
}
else if (_lastMoveDirection.magnitude > _minMoveMagnitude)
{
Gizmos.color = Color.green;
Gizmos.DrawRay(_playerTransform.position, direction.normalized * _moveSpeed);
}
}
}
public Vector3[] CreateArrayOfDirectionsAroundYAxis(int stepGrad, Vector3 direction)
{
int length = 360 / stepGrad;
int currentDirection = stepGrad;
float yRotation;
Vector3[] directions = new Vector3[length];
for (int i = 0; i < (directions.Length - 1); i++)
{
yRotation = currentDirection + (stepGrad * i);
directions[i] = Quaternion.Euler(0, yRotation, 0) * direction;
directions[i + 1] = Quaternion.Euler(0, -yRotation, 0) * direction;
currentDirection += stepGrad;
}
return directions;
}
}