Player movement and camera

This commit is contained in:
Marco 2025-06-11 15:28:26 +02:00
commit a324f2e347
43 changed files with 1777 additions and 316 deletions

View file

@ -16,24 +16,29 @@ public partial class CreateEmitterPattern : AttackPattern
[Export] public double LifeTime { get; set; } = 10d;
public override IPatternMachine MakeMachine(Node2D parent)
public override IPatternMachine MakeMachine(Node parent)
{
return new EmitterPatternMachine(this, parent);
}
public class EmitterPatternMachine(CreateEmitterPattern pattern, Node2D parent) : IPatternMachine
public class EmitterPatternMachine(CreateEmitterPattern pattern, Node parent) : IPatternMachine
{
private bool _active = false;
public Node2D Parent => parent;
public Node Parent => parent;
public AutonomousBulletEmitter Emitter { get; private set; }
public void Start()
{
if (parent is not Node2D parent2d)
{
return;
}
Emitter = pattern.CreateAsChild
? parent.CreateChild<AutonomousBulletEmitter>(pattern.Prefab,
parent.GlobalPosition + pattern.SpawnOffset)
: parent.CreateSibling<AutonomousBulletEmitter>(pattern.Prefab,
parent.GlobalPosition + pattern.SpawnOffset);
? parent2d.CreateChild<AutonomousBulletEmitter>(pattern.Prefab,
parent2d.GlobalPosition + pattern.SpawnOffset)
: parent2d.CreateSibling<AutonomousBulletEmitter>(pattern.Prefab,
parent2d.GlobalPosition + pattern.SpawnOffset);
Emitter.Script = pattern.Script;
Emitter.EmitOnStart = true;
Emitter.LifeTime = pattern.LifeTime;

View file

@ -14,14 +14,14 @@ public partial class MovementPattern : AttackPattern
[Export] public Tween.EaseType easeType = Tween.EaseType.InOut;
[Export] public AttackPattern shootingPattern;
public override IPatternMachine MakeMachine(Node2D parent)
public override IPatternMachine MakeMachine(Node parent)
{
return new MovementPatternMachine(this, parent);
}
public class MovementPatternMachine(MovementPattern pattern, Node2D parent) : IPatternMachine
public class MovementPatternMachine(MovementPattern pattern, Node parent) : IPatternMachine
{
public Node2D Parent => parent;
public Node Parent => parent;
public MovementPattern Pattern { get; } = pattern;
private IPatternMachine _machine;
@ -39,10 +39,11 @@ public partial class MovementPattern : AttackPattern
Boss = boss;
tween = parent.CreateTween();
isComplete = false;
Vector2 targetPosition = (Boss?.HomePosition ?? boss.ParentObject.GlobalPosition) + Pattern.relativeTargetPosition;
Vector2 targetPosition = (Boss?.HomePosition ?? parent.GlobalPosition) + Pattern.relativeTargetPosition;
tween.TweenProperty(Parent, "position", targetPosition, Pattern.moveDuration)
tween.TweenProperty(boss.ParentObject, "position", targetPosition, Pattern.moveDuration)
.SetTrans(Pattern.transitionType)
.SetEase(Pattern.easeType)
.Finished += () => isComplete = true;

View file

@ -16,14 +16,14 @@ public partial class NodeMovementPattern : AttackPattern
// [Export] private Tween.TransitionType transitionType = Tween.TransitionType.Linear;
[Export] public GTweens.Easings.Easing EaseType { get; private set; } = Easing.Linear;
public override IPatternMachine MakeMachine(Node2D parent)
public override IPatternMachine MakeMachine(Node parent)
{
return new NodeMovementPatternMachine(this, parent);
}
public class NodeMovementPatternMachine(NodeMovementPattern pattern, Node2D parent) : IPatternMachine
public class NodeMovementPatternMachine(NodeMovementPattern pattern, Node parent) : IPatternMachine
{
public Node2D Parent => parent;
public Node Parent => parent;
private GTween _tween;
private bool isComplete = false;
@ -37,11 +37,12 @@ public partial class NodeMovementPattern : AttackPattern
return;
}
_tween?.Complete();
isComplete = false;
Vector2 targetPosition = (scriptHost?.HomePosition ?? Parent.GlobalPosition) + pattern.relativeTargetPosition;
Vector2 targetPosition = (scriptHost?.HomePosition ?? scriptHost.ParentObject.GlobalPosition) + pattern.relativeTargetPosition;
_tween = GTweenSequenceBuilder.New()
.Append(scriptHost.ParentObject.TweenGlobalPosition(targetPosition, pattern.moveDuration))

View file

@ -112,14 +112,15 @@ public partial class SpiralPattern : AttackPattern
// };
}
public override IPatternMachine MakeMachine(Node2D parent)
public override IPatternMachine MakeMachine(Node parent)
{
return new SpiralPatternMachine(this, parent);
}
public class SpiralPatternMachine(SpiralPattern pattern, Node2D parent) : IPatternMachine
public class SpiralPatternMachine(SpiralPattern pattern, Node parent) : IPatternMachine
{
public Node2D Parent => parent;
public Node Parent => parent;
public IScriptHost ScriptHost { get; private set; }
private double timer;
private double burstTimer;
//private double _burstRateTimer;
@ -131,6 +132,8 @@ public partial class SpiralPattern : AttackPattern
public void Start()
{
ScriptHost = Parent as IScriptHost;
timer = 0;
_burstBullets = pattern.ShotsPerBurst;
burstTimer = pattern.burstInterval; // start immediately
@ -231,21 +234,21 @@ public partial class SpiralPattern : AttackPattern
{
if (pattern._predictPlayer && GameManager.Instance.PlayerVelocity.HasValue)
{
var predictedDirection = MathFunctions.PredictInterceptPosition(Parent.GlobalPosition,
var predictedDirection = MathFunctions.PredictInterceptPosition(ScriptHost.ParentObject.GlobalPosition,
GameManager.Instance.PlayerPosition.Value, GameManager.Instance.PlayerVelocity.Value,
pattern.BulletResource.BulletSpeed);
if (predictedDirection.HasValue)
{
direction = (predictedDirection.Value - Parent.GlobalPosition).Normalized();
direction = (predictedDirection.Value - ScriptHost.ParentObject.GlobalPosition).Normalized();
}
}
else
{
direction = (GameManager.Instance.PlayerPosition.Value - Parent.GlobalPosition).Normalized();
direction = (GameManager.Instance.PlayerPosition.Value - ScriptHost.ParentObject.GlobalPosition).Normalized();
}
}
var bullet = pattern.MakeBullet(Parent.GlobalPosition + pattern.EmitterOffset, pattern.bulletCount,
var bullet = pattern.MakeBullet(ScriptHost.ParentObject.GlobalPosition + pattern.EmitterOffset, pattern.bulletCount,
pattern.spread, angleOffset);
bullet.Direction = direction;

View file

@ -43,11 +43,17 @@ public partial class KeyboardInputProvider : InputProvider
public override void _Ready()
{
CallDeferred(MethodName.DelayedRegisterGameManager);
}
private void DelayedRegisterGameManager()
{
if (GameManager.Instance is null)
{
GD.Print("No GameManager found for keyboard inputprovider");
return;
}
GameManager.Instance.GameStateChange += InstanceOnGameStateChange;
_enabled = true;
}
@ -107,9 +113,11 @@ public partial class KeyboardInputProvider : InputProvider
//if (camera == null) return Vector2.Zero; // Ensure there's a valid camera
//Vector2 mouseScreenPos = GetViewport().get_local_mouse_position();
Vector2 mouseWorldPos = this.GetGlobalMousePosition();
if (GameManager.Instance is null) return Vector2.Zero;
Vector2 mouseWorldPos = DisplayServer.MouseGetPosition();// GameManager.Instance.GetGlobalMousePosition();
return mouseWorldPos - this.GlobalPosition; // Get direction vector
return mouseWorldPos - GameManager.Instance.PlayerPosition.Value; // Get direction vector
}
public override bool GetActionJustPressed(string action)

View file

@ -81,52 +81,7 @@ public partial class Active : BaseState<PlayerState, CharacterBody3D>
{
base.ProcessState(delta);
_movementDirection = _inputProvider.GetMovementInput().Normalized();
_isStrafing = _inputProvider.GetStrafePressed();
// Toggle visibility of the hitbox sprite based on strafing
_hitboxSpriteProvider.SetVisibility(_isStrafing);
var rightStickInput = _inputProvider.GetAimInput().Normalized();
// Update Facing Direction
// if (!_isStrafing)
// {
if (rightStickInput.Length() > 0.1f) // If the right stick is moved
{
FacingDirection = rightStickInput;
}
else if (_movementDirection != Vector2.Zero) // Fall back to movement direction
{
FacingDirection = _movementDirection;
}
// }
_animationProvider.SetAnimationSpeed(MainObject.Velocity);
_animationProvider.SetAnimation(FacingDirection);
HandleWeaponSwitch();
_weaponProvider.Update(delta);
//_crosshairProvider.UpdatePosition(FacingDirection);
HandleShoot();
HandleInteraction();
// FindInteractable();
// _crosshair.Position = CalculateCrosshairPosition();
if (_inputProvider.GetInventoryJustPressed())
{
GameManager.Instance.ChangeState(GameState.Inventory);
}
if (_inputProvider.GetPauseJustPressed())
{
//CallDeferred(MethodName.PauseDeferred);
PauseDeferred();
}
}
private void PauseDeferred()
@ -134,32 +89,5 @@ public partial class Active : BaseState<PlayerState, CharacterBody3D>
GameManager.Instance.Pause();
}
// private void HandleShoot()
// {
// if (_inputProvider.GetReloadJustPressed())
// {
// _weaponProvider.Reload();
// return;
// }
//
// if (!_inputProvider.GetShootPressed()) return;
// _weaponProvider.Shoot(this.FacingDirection);
// }
//
// private void HandleInteraction()
// {
// _activationProvider.HandleInteraction();
// }
//
// private void HandleWeaponSwitch()
// {
// if (_inputProvider.GetWeaponNextJustPressed())
// {
// _weaponProvider.NextWeapon();
// }
// else if (_inputProvider.GetWeaponPreviousJustPressed())
// {
// _weaponProvider.PreviousWeapon();
// }
// }
}

View file

@ -32,6 +32,6 @@ public partial class Init : BaseState<PlayerState, CharacterBody3D>
private async Task AutoSwitchToStart()
{
await Task.Delay(500);
//StateMachine.SetState(PlayerState.Active);
StateMachine.SetState(PlayerState.Active);
}
}

View file

@ -12,6 +12,8 @@ public partial class IsoMovementModule : ModuleBase<PlayerState, CharacterBody3D
[Export] public int StrafeSpeed { get; set; } = 35;
[Export] public float Acceleration = 8f;
[Export] public float Deceleration = 8f;
[Export] public float Gravity = -9.8f;
[Export] public float FallSpeed = 20f;
private bool _isStrafing;
private float _accelerationPerSecond;
@ -41,16 +43,38 @@ public partial class IsoMovementModule : ModuleBase<PlayerState, CharacterBody3D
public override void Process(double delta)
{
var movementInput = _inputProvider.GetMovementInput();
_isStrafing = _inputProvider.GetStrafePressed();
var rightStickInput = _inputProvider.GetAimInput().Normalized();
// Update Facing Direction
// if (!_isStrafing)
// {
if (rightStickInput.Length() > 0.1f) // If the right stick is moved
{
PlayerStorage.FacingDirection = rightStickInput;
}
else if (movementInput != Vector2.Zero) // Fall back to movement direction
{
PlayerStorage.FacingDirection = movementInput;
}
// }
var rotatedMovementDirection = movementInput.Rotated(Mathf.DegToRad(-45f));
PlayerStorage.MovementDirection = new Vector3(rotatedMovementDirection.X, 0, rotatedMovementDirection.Y);
}
public override void PhysicsProcess(double delta)
{
var frameVelocity = MainObject.Velocity;
if (_isStrafing)
{
// Instant movement at strafe speed
MainObject.Velocity = PlayerStorage.MovementDirection * StrafeSpeed;
frameVelocity = PlayerStorage.MovementDirection * StrafeSpeed;
}
else
{
@ -58,16 +82,22 @@ public partial class IsoMovementModule : ModuleBase<PlayerState, CharacterBody3D
if (PlayerStorage.MovementDirection != Vector3.Zero)
{
MainObject.Velocity = MainObject.Velocity.MoveToward(targetVelocity, Acceleration * (float)delta);
frameVelocity = frameVelocity.MoveToward(targetVelocity, Acceleration * (float)delta);
}
else
{
MainObject.Velocity = MainObject.Velocity.MoveToward(Vector3.Zero, Deceleration * (float)delta);
frameVelocity = frameVelocity.MoveToward(Vector3.Zero, Deceleration * (float)delta);
}
}
//MainObject.Velocity += _movementDirection * MovementSpeed;
var velocityY = Mathf.Clamp(frameVelocity.Y + Gravity * (float)delta, -FallSpeed, FallSpeed);
frameVelocity.Y = velocityY;
MainObject.Velocity = frameVelocity;
MainObject.MoveAndSlide();
}
}

View file

@ -5,7 +5,7 @@ using Godot.Collections;
namespace Cirno.Scripts.Components.FSM;
public abstract partial class BaseState<TKey, TType> : Node2D, IState<TKey, TType>
public abstract partial class BaseState<TKey, TType> : Node, IState<TKey, TType>
where TKey : notnull
where TType : Node
{

View file

@ -84,7 +84,7 @@ public abstract partial class ElevatorMovementState : BaseState<ElevatorState, E
private async Task MovePlayerToCenter()
{
var tween = GTweenSequenceBuilder.New()
.Append(PlayerBody.TweenPosition(this.Position, 0.2f))
.Append(PlayerBody.TweenPosition(MainObject.Position, 0.2f))
.Build();
await tween.PlayAsync(CancellationToken.None);

View file

@ -78,7 +78,7 @@ public partial class Alert : EnemyStateBase
}
// if player is outside disengage range, change to idle (later on, search)
if (this.GlobalPosition.DistanceTo(GameManager.Instance.PlayerPosition.Value) >=
if (MainObject.GlobalPosition.DistanceTo(GameManager.Instance.PlayerPosition.Value) >=
StorageModule.Root.EnemyResource.PlayerDisengageRange)
{
StateMachine.SetState(EnemyState.Idle);

View file

@ -72,7 +72,7 @@ public partial class TurretAlert : EnemyStateBase
}
// if player is outside disengage range, change to idle (later on, search)
if (this.GlobalPosition.DistanceTo(GameManager.Instance.PlayerPosition.Value) >=
if (MainObject.GlobalPosition.DistanceTo(GameManager.Instance.PlayerPosition.Value) >=
StorageModule.Root.EnemyResource.PlayerDisengageRange)
{

View file

@ -0,0 +1,121 @@
using Godot;
namespace Cirno.Scripts.Misc;
public partial class CameraController3D : Camera3D
{
[Export] public bool EnableSmoothing = true;
[Export] public bool FollowTargeting = true;
[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(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 Node3D _target;
private Vector3 _currentPosition = Vector3.Zero;
private Vector3 _currentAimOffset = Vector3.Zero;
public override void _Ready()
{
_target = GetNode<Node3D>(TargetPath);
if (_target == null)
{
GD.PushError("Camera target not found.");
return;
}
_currentPosition = GlobalPosition;
// Set fixed isometric angle once: -45° X tilt, 45° Y pan
RotationDegrees = new Vector3(-45f, 45f, 0f);
Projection = ProjectionType.Orthogonal;
}
public override void _Process(double delta)
{
if (_target == 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;
}
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;
}
}

View file

@ -0,0 +1 @@
uid://ba0tf7ihw4hpp

View file

@ -36,10 +36,15 @@ public partial class FSMMovementPattern : AttackPattern
tween = Parent.CreateTween();
isComplete = false;
Vector2 targetPosition = (Boss?.HomePosition ?? Parent.GlobalPosition) + pattern.relativeTargetPosition;
if (parent is not Node2D parent2d)
{
return;
}
Vector2 targetPosition = (Boss?.HomePosition ?? parent2d.GlobalPosition) + pattern.relativeTargetPosition;
boss.ChangeSpriteDirection(-(Parent.GlobalPosition - targetPosition));
tween.TweenProperty(Parent, "global_position", targetPosition, pattern.moveDuration)
boss.ChangeSpriteDirection(-(parent2d.GlobalPosition - targetPosition));
tween.TweenProperty(parent2d, "global_position", targetPosition, pattern.moveDuration)
.SetTrans(pattern.transitionType)
.SetEase(pattern.easeType)
.Finished += () =>

View file

@ -35,11 +35,16 @@ public partial class SimpleMovementPattern : AttackPattern
Boss = boss;
tween = Parent.CreateTween();
isComplete = false;
if (parent is not Node2D parent2d)
{
return;
}
Vector2 targetPosition = (Boss?.HomePosition ?? Parent.GlobalPosition) + pattern.relativeTargetPosition;
Vector2 targetPosition = (Boss?.HomePosition ?? parent2d.GlobalPosition) + pattern.relativeTargetPosition;
boss.ChangeSpriteDirection(-(Parent.GlobalPosition - targetPosition));
tween.TweenProperty(Parent, "global_position", targetPosition, pattern.moveDuration)
boss.ChangeSpriteDirection(-(parent2d.GlobalPosition - targetPosition));
tween.TweenProperty(parent2d, "global_position", targetPosition, pattern.moveDuration)
.SetTrans(pattern.transitionType)
.SetEase(pattern.easeType)
.Finished += () =>