using Godot; namespace Cirno.Scripts.Misc; public partial class CameraController3D : Camera3D { public static CameraController3D Instance { get; private set; } [Export] public bool EnableSmoothing = true; [Export] public bool FollowTargeting = true; [Export] public bool SnapCamera = true; // [Export] public float CameraSnapStep = 0.02f; // 0 = disabled [Export] public Vector3 DefaultCameraRotation = new Vector3(-36f, 45f, 0f); [Export] public float SmoothTime = 0.2f; [Export] public float MaxAimOffsetDistance = 2.0f; [Export] public float AimLerpSpeed = 8.0f; [Export] public float AimDeadzone = 0.2f; [Export] public Vector3 CameraOffset = new Vector3(8, 8.5f, 8); //new Vector3(0, 12, -12); // Relative to target [Export] public StringName AimUpName = "aim_up"; [Export] public StringName AimDownName = "aim_down"; [Export] public StringName AimLeftName = "aim_left"; [Export] public StringName AimRightName = "aim_right"; [Export] public NodePath TargetPath; private CameraTarget3D _target; private Vector3 _currentPosition = Vector3.Zero; private Vector3 _currentAimOffset = Vector3.Zero; public override void _Ready() { Instance = this; RotationDegrees = DefaultCameraRotation; Projection = ProjectionType.Orthogonal; _target = GetNode(TargetPath); if (_target == null) { GD.PushError("Camera target not found."); return; } _currentPosition = GlobalPosition; // Set fixed isometric angle once: -45° X tilt, 45° Y pan } public void RegisterTarget(CameraTarget3D target, bool jump = true) { // assert(not _active_target) _target = target; if (jump) { //_currentPosition = _activeTarget.GlobalPosition; } } public override void _Process(double delta) { if (_target is null) return; float dt = (float)delta; Vector3 targetPos = _target.GlobalTransform.Origin; // Aim offset if (FollowTargeting) { Vector3 desiredOffset = GetAimOffsetWorldSpace(); _currentAimOffset = _currentAimOffset.Lerp(desiredOffset, AimLerpSpeed * dt); if (_currentAimOffset.Length() > MaxAimOffsetDistance) _currentAimOffset = _currentAimOffset.Normalized() * MaxAimOffsetDistance; } else { _currentAimOffset = Vector3.Zero; } // Final target position Vector3 targetWithOffset = targetPos + _currentAimOffset; Vector3 desiredCameraPos = targetWithOffset + CameraOffset; if (EnableSmoothing) { float smoothingFactor = 1f - Mathf.Exp(-dt / SmoothTime); _currentPosition = _currentPosition.Lerp(desiredCameraPos, smoothingFactor); } else { _currentPosition = desiredCameraPos; } // if (CameraSnapStep > 0f) // { // _currentPosition = Snap(_currentPosition, CameraSnapStep); // } if (SnapCamera) { GlobalPosition = SnapToPixelGrid(_currentPosition); } else { GlobalPosition = _currentPosition; } //GlobalPosition = _currentPosition; // No LookAt or dynamic rotation — angle is fixed } private Vector3 GetAimOffsetWorldSpace() { Vector2 stickDir = new Vector2( Input.GetActionStrength(AimRightName) - Input.GetActionStrength(AimLeftName), Input.GetActionStrength(AimDownName) - Input.GetActionStrength(AimUpName) ); float stickLen = stickDir.Length(); if (stickLen > AimDeadzone) { float scaled = (stickLen - AimDeadzone) / (1f - AimDeadzone); Vector2 aimDir2D = stickDir.Normalized() * Mathf.Clamp(scaled, 0f, 1f); return new Vector3(aimDir2D.X, 0, aimDir2D.Y); } // Mouse fallback Vector2 mousePos = GetViewport().GetMousePosition(); Vector3 rayOrigin = ProjectRayOrigin(mousePos); Vector3 rayDir = ProjectRayNormal(mousePos) * 1000f; var plane = new Plane(Vector3.Up, 0); var hit = plane.IntersectsRay(rayOrigin, rayDir); if (hit is Vector3 hitPoint) { Vector3 offset = hitPoint - _target.GlobalTransform.Origin; offset.Y = 0; float dist = offset.Length(); if (dist > 0.01f) { float scaled = Mathf.Clamp((dist - AimDeadzone) / (10f - AimDeadzone), 0f, 1f); return offset.Normalized() * scaled; } } return Vector3.Zero; } private Vector3 SnapToPixelGrid(Vector3 worldPos) { var viewport = GetViewport(); float viewportHeight = viewport.GetVisibleRect().Size.Y; // World units per screen pixel float unitsPerPixel = (Size * 2f) / viewportHeight; return new Vector3( Mathf.Round(worldPos.X / unitsPerPixel) * unitsPerPixel, Mathf.Round(worldPos.Y / unitsPerPixel) * unitsPerPixel, Mathf.Round(worldPos.Z / unitsPerPixel) * unitsPerPixel ); } // private static float Snap(float value, float step) // { // return Mathf.Round(value / step) * step; // } // // private static Vector3 Snap(Vector3 v, float step) // { // return new Vector3( // Snap(v.X, step), // Snap(v.Y, step), // Snap(v.Z, step) // ); // } }