godot-karrot-starter-theme/Common/CameraController/CharacterCameraController.cs
2024-11-14 19:25:11 +01:00

154 lines
4.8 KiB
C#

using System;
using System.Linq;
using Godot;
namespace KarrotStarterTemplate.Common.CameraController;
public enum ECameraControllerInputType
{
None,
TopDown,
FirstPerson,
ThirdPerson
}
[GlobalClass]
public partial class CharacterCameraController : Node3D
{
[ExportCategory("Nodes")]
[Export] public Node3D characterToFollow { get; set; }
[ExportCategory("Properties")]
[ExportGroup("Mouse input")]
[Export] public ECameraControllerInputType inputType = ECameraControllerInputType.None;
[Export] public float mouseSensitivity { get; set; } = .5f;
[Export] public Vector2 cameraTiltLimit { get; set; } = new(-45f, 45f);
[ExportGroup("Player")]
[Export] public bool followPlayer = true;
[Export(PropertyHint.Range, "0, 1")] public float followDelay { get; set; } = .5f;
[Export] public bool rotatePlayer = true;
[Export(PropertyHint.Range, "0, 1")] public float rotateDelay { get; set; } = .5f;
private Camera3D _camera; // todo
private SpringArm3D _springArm;
private Vector2 _mouseInput = Vector2.Zero;
public Camera3D camera => _camera;
public SpringArm3D springArm => _springArm;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
if (Engine.IsEditorHint())
{
TopLevel = true;
return;
}
characterToFollow ??= (CharacterBody3D)GetParent();
_springArm = GetChildren().OfType<SpringArm3D>().First();
_camera = _springArm.GetChildren().OfType<Camera3D>().First();
GlobalPosition = characterToFollow.GlobalPosition;
// Input.SetMouseMode(Input.MouseModeEnum.Captured);
}
public override void _UnhandledInput(InputEvent @event)
{
if (Engine.IsEditorHint())
{
return;
}
if (inputType == ECameraControllerInputType.None)
{
return;
}
if (@event is InputEventMouseMotion mouseMotion)
{
_mouseInput = -mouseMotion.Relative * mouseSensitivity;
}
}
public override void _PhysicsProcess(double delta)
{
if (Engine.IsEditorHint())
{
return;
}
if (followPlayer)
{
float weight = Mathf.Clamp(20 * (1 - followDelay) * (float)delta, 0,1 );
GlobalPosition = followDelay > 0 ? GlobalPosition.Lerp(characterToFollow.GlobalPosition, weight) : characterToFollow.GlobalPosition;
}
if (rotatePlayer)
{
float weight = Mathf.Clamp(20 * (1 - rotateDelay) * (float)delta, 0,1 );
Vector3 v = characterToFollow.GlobalRotation;
switch (inputType)
{
case ECameraControllerInputType.None:
break;
case ECameraControllerInputType.TopDown:
var (x, y, z) = GetMouseIntersectedDirection();
characterToFollow.GlobalRotation = new Vector3(v.X, Mathf.LerpAngle(v.Y, -Mathf.Atan2(-x, z), weight) , v.Z);
break;
case ECameraControllerInputType.FirstPerson:
case ECameraControllerInputType.ThirdPerson:
characterToFollow.GlobalRotation = new Vector3(v.X, Mathf.LerpAngle(v.Y, _springArm.GlobalRotation.Y, weight) , v.Z);
break;
}
}
switch (inputType)
{
case ECameraControllerInputType.FirstPerson:
case ECameraControllerInputType.ThirdPerson:
_springArm.Rotation += new Vector3(_mouseInput.Y * (float)delta, 0f, 0f);
Rotation += new Vector3(0f, _mouseInput.X * (float)delta, characterToFollow.GlobalPosition.Y);
var (x, y, z) = _springArm.RotationDegrees;
_springArm.RotationDegrees = new Vector3(Mathf.Clamp(x, cameraTiltLimit.X, cameraTiltLimit.Y), y, z);
_mouseInput = Vector2.Zero;
break;
}
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta)
{
if (Engine.IsEditorHint())
{
return;
}
}
public virtual Vector3 GetMouseIntersectedPosition()
{
var position2D = GetViewport().GetMousePosition();
var dropPlane = new Plane(new Vector3(0, 10f, 0), 0f);
var position3D =
dropPlane.IntersectsRay(camera.ProjectRayOrigin(position2D), camera.ProjectRayNormal(position2D));
return position3D ?? Vector3.Zero;
}
public virtual Vector3 GetMouseIntersectedDirection()
{
return (characterToFollow.GlobalPosition - GetMouseIntersectedPosition()).Normalized();
}
}