diff --git a/Content/Blueprints/BP_MainCharacter.ts b/Content/Blueprints/BP_MainCharacter.ts index 61d8b96..4b67a07 100644 --- a/Content/Blueprints/BP_MainCharacter.ts +++ b/Content/Blueprints/BP_MainCharacter.ts @@ -10,6 +10,7 @@ import { Cast } from '#root/UE/Cast.ts'; import type { Controller } from '#root/UE/Controller.ts'; import { EnhancedInputLocalPlayerSubsystem } from '#root/UE/EnhancedInputLocalPlayerSubsystem.ts'; import type { Float } from '#root/UE/Float.ts'; +import { MathLibrary } from '#root/UE/MathLibrary.ts'; import { Pawn } from '#root/UE/Pawn.ts'; import type { PlayerController } from '#root/UE/PlayerController.ts'; import { Rotator } from '#root/UE/Rotator.ts'; @@ -94,14 +95,43 @@ export class BP_MainCharacter extends Pawn { /** * Process movement input for ground-based movement - * @param actionValueX - Horizontal movement input value (-1 to 1) - * @param actionValueY - Vertical movement input value (-1 to 1) + * @param ActionValueX - Horizontal movement input value (-1 to 1) + * @param ActionValueY - Vertical movement input value (-1 to 1) */ EnhancedInputActionMoveTriggered( - actionValueX: Float, - actionValueY: Float + ActionValueX: Float, + ActionValueY: Float ): void { - this.CurrentMovementInput = new Vector(actionValueY, actionValueX, 0); + const CalculateResultMovementInputVector = ( + rightVector: Vector, + forwardVector: Vector, + actionValueX: Float, + actionValueY: Float + ): Vector => { + const vec1 = new Vector( + rightVector.X * actionValueX, + rightVector.Y * actionValueX, + 0 + ); + const vec2 = new Vector( + forwardVector.X * actionValueY, + forwardVector.Y * actionValueY, + 0 + ); + + return new Vector(vec1.X + vec2.X, vec1.Y + vec2.Y, 0); + }; + + this.CurrentMovementInput = CalculateResultMovementInputVector( + MathLibrary.GetRightVector( + this.GetControlRotation().roll, + 0, + this.GetControlRotation().yaw + ), + MathLibrary.GetForwardVector(0, 0, this.GetControlRotation().yaw), + ActionValueX, + ActionValueY + ); } /** @@ -167,7 +197,7 @@ export class BP_MainCharacter extends Pawn { this.CurrentMovementInput, DeltaTime ); - this.ApplyMovementToActor(); + this.ApplyMovementAndRotation(); } // ════════════════════════════════════════════════════════════════════════════════════════ @@ -178,7 +208,9 @@ export class BP_MainCharacter extends Pawn { * Apply calculated movement velocity to actor position * @category Movement Application */ - private ApplyMovementToActor(): void { + private ApplyMovementAndRotation(): void { + this.SetActorRotation(this.MovementComponent.CurrentRotation); + const CalculateNewLocation = ( currentLocation: Vector, velocity: Vector diff --git a/Content/Blueprints/BP_MainCharacter.uasset b/Content/Blueprints/BP_MainCharacter.uasset index c017d89..e024211 100644 --- a/Content/Blueprints/BP_MainCharacter.uasset +++ b/Content/Blueprints/BP_MainCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:daac43daf46dcf7eabbf3658ecd54948c15a1fe7f8feda5e99e1f75e26a0db45 -size 346311 +oid sha256:dd2a02d508f7f7bc9e8bcad821bddc6338eb61e99125dcc1944695094ac08c6a +size 347749 diff --git a/Content/Debug/Components/AC_DebugHUD.uasset b/Content/Debug/Components/AC_DebugHUD.uasset index 62cd1c2..86cc407 100644 --- a/Content/Debug/Components/AC_DebugHUD.uasset +++ b/Content/Debug/Components/AC_DebugHUD.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4267143968587e5701d84f79400c8e00ba0a243a2465eb2bdd575d7e12416519 -size 1025955 +oid sha256:38ec7c9136f33e6b544c7fd4bc2b11f22e349b72f722bc0df3811e4ed9167fb2 +size 1051885 diff --git a/Content/Movement/Components/AC_Movement.ts b/Content/Movement/Components/AC_Movement.ts index 8a1519c..ad653f4 100644 --- a/Content/Movement/Components/AC_Movement.ts +++ b/Content/Movement/Components/AC_Movement.ts @@ -8,6 +8,7 @@ import type { S_MovementConstants } from '#root/Movement/Structs/S_MovementConst import { ActorComponent } from '#root/UE/ActorComponent.ts'; import type { Float } from '#root/UE/Float.ts'; import { MathLibrary } from '#root/UE/MathLibrary.ts'; +import { Rotator } from '#root/UE/Rotator.ts'; import { Vector } from '#root/UE/Vector.ts'; /** @@ -132,6 +133,9 @@ export class AC_Movement extends ActorComponent { if (this.IsInitialized) { this.InputMagnitude = MathLibrary.VectorLength(InputVector); + this.TargetRotation = this.CalculateTargetRotation(InputVector); + this.UpdateCharacterRotation(DeltaTime); + // Only process movement on walkable surfaces if (this.IsSurfaceWalkable(this.CurrentSurface) && this.IsGrounded) { this.ProcessGroundMovement(InputVector, DeltaTime); @@ -252,6 +256,93 @@ export class AC_Movement extends ActorComponent { ); } + /** + * Calculate target rotation based on movement direction + * Determines what rotation character should have based on movement + * @param MovementDirection - Camera-relative movement direction from Blueprint + * @returns Target rotation for character + * @pure true + * @category Character Rotation + */ + public CalculateTargetRotation(MovementDirection: Vector): Rotator { + const TargetYaw = ( + movementDirectionX: Float, + movementDirectionY: Float + ): Float => + MathLibrary.RadiansToDegrees( + MathLibrary.Atan2(movementDirectionY, movementDirectionX) + ); + + if (MathLibrary.VectorLength(MovementDirection) < 0.01) { + // No movement, maintain current target rotation + return this.TargetRotation; + } + + // Character should remain level (pitch = 0, roll = 0, only yaw changes) + return new Rotator( + 0, + TargetYaw(MovementDirection.X, MovementDirection.Y), + 0 + ); + } + + /** + * Update character rotation towards target + * Uses instant rotation approach similar to Zelda: BotW + * @param DeltaTime - Time since last frame + * @category Character Rotation + */ + public UpdateCharacterRotation(DeltaTime: Float): void { + if (this.ShouldRotateToMovement) { + const ShouldRotate = (): boolean => + this.CurrentSpeed >= this.MinSpeedForRotation; + + if (ShouldRotate()) { + let yawDifference = this.CurrentRotation.yaw - this.TargetRotation.yaw; + + if (yawDifference > 180) { + yawDifference = yawDifference - 360; + } else if (yawDifference < -180) { + yawDifference = yawDifference + 360; + } + + const NewYaw = ( + currentYaw: Float, + clampedRotationAmount: Float, + coef: Float + ): Float => currentYaw + clampedRotationAmount * coef; + + const ClampedRotationAmount = (deltaTime: Float): Float => + MathLibrary.Min( + this.RotationSpeed * deltaTime, + MathLibrary.abs(yawDifference) + ); + + this.RotationDelta = MathLibrary.abs(yawDifference); + + if (this.RotationDelta > 1) { + this.IsCharacterRotating = true; + + this.CurrentRotation = new Rotator( + 0, + NewYaw( + this.CurrentRotation.yaw, + ClampedRotationAmount(DeltaTime), + yawDifference > 0 ? -1 : 1 + ), + 0 + ); + } else { + this.IsCharacterRotating = false; + this.CurrentRotation = this.TargetRotation; + } + } else { + this.IsCharacterRotating = false; + this.RotationDelta = 0.0; + } + } + } + /** * Initialize movement system with angle conversion * Converts degree thresholds to radians for runtime performance @@ -359,4 +450,56 @@ export class AC_Movement extends ActorComponent { * @category Movement State */ public CurrentSpeed: Float = 0.0; + + /** + * Rotation speed in degrees per second + * Controls how fast character rotates towards movement direction + * @category Character Rotation Config + * @instanceEditable true + */ + public RotationSpeed: Float = 720.0; + + /** + * Should character rotate when moving + * Allows disabling rotation system if needed + * @category Character Rotation Config + * @instanceEditable true + */ + public ShouldRotateToMovement: boolean = true; + + /** + * Minimum movement speed to trigger rotation + * Prevents character from rotating during very slow movement + * @category Character Rotation Config + * @instanceEditable true + */ + public MinSpeedForRotation: Float = 50.0; + + /** + * Current character rotation + * Used for smooth interpolation to target rotation + * @category Character Rotation State + */ + public CurrentRotation: Rotator = new Rotator(0, 0, 0); + + /** + * Target character rotation + * Calculated based on movement direction and camera orientation + * @category Character Rotation State + */ + public TargetRotation: Rotator = new Rotator(0, 0, 0); + + /** + * Whether character is currently rotating + * Used for animation and debug purposes + * @category Character Rotation State + */ + public IsCharacterRotating: boolean = false; + + /** + * Angular difference between current and target rotation + * Used for determining rotation completion and debug display + * @category Character Rotation State + */ + public RotationDelta: Float = 0.0; } diff --git a/Content/Movement/Components/AC_Movement.uasset b/Content/Movement/Components/AC_Movement.uasset index b4a2b44..afbd32f 100644 --- a/Content/Movement/Components/AC_Movement.uasset +++ b/Content/Movement/Components/AC_Movement.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:39c43ef35c676cc50695885cbbef5fcc90388d39b8bb15afa8e52df2890d7916 -size 338671 +oid sha256:b5c9971eb006daf9e4e9d94a6bffcc024c6e7615cacdcf5fae7598a00e371c4a +size 534988 diff --git a/Content/UE/Actor.ts b/Content/UE/Actor.ts index b7fd09f..4c765c2 100644 --- a/Content/UE/Actor.ts +++ b/Content/UE/Actor.ts @@ -1,6 +1,7 @@ // UE/Actor.ts import { Name } from '#root/UE/Name.ts'; +import { Rotator } from '#root/UE/Rotator.ts'; import { UEObject } from '#root/UE/UEObject.ts'; import { Vector } from '#root/UE/Vector.ts'; @@ -18,6 +19,14 @@ export class Actor extends UEObject { // Implementation for setting actor location } + public SetActorRotation( + NewRotation: Rotator = new Rotator(), + TeleportPhysics: boolean = false + ): void { + console.log(NewRotation, TeleportPhysics); + // Implementation for setting actor rotation + } + public GetActorLocation(): Vector { return new Vector(); // Placeholder implementation } diff --git a/Content/UE/MathLibrary.ts b/Content/UE/MathLibrary.ts index 791260d..f5c4cff 100644 --- a/Content/UE/MathLibrary.ts +++ b/Content/UE/MathLibrary.ts @@ -35,6 +35,18 @@ class MathLibraryClass extends BlueprintFunctionLibrary { return (Degrees * Math.PI) / 180; } + /** + * Convert radians to degrees + * @param Radians - Angle in radians + * @returns Angle in degrees + * @example + * // Convert π/2 radians to degrees + * RadiansToDegrees(Math.PI / 2) // returns 90 + */ + public RadiansToDegrees(Radians: Float): Float { + return (Radians * 180) / Math.PI; + } + /** * Calculate sine of angle in radians * @param Value - Angle in radians @@ -62,6 +74,16 @@ class MathLibraryClass extends BlueprintFunctionLibrary { return Math.acos(Value); } + /** + * Calculate arctangent2 of Y and X + * @param Y - Y coordinate + * @param X - X coordinate + * @returns Angle in radians (-π to π) + */ + public Atan2(Y: Float, X: Float): Float { + return Math.atan2(Y, X); + } + /** * Calculate dot product of two vectors * @param Vector1 - First vector @@ -207,6 +229,69 @@ class MathLibraryClass extends BlueprintFunctionLibrary { return new Vector(0, 0, 0); } + + /** + * Get the minimum of two float values + * @param A - First value + * @param B - Second value + * @returns Minimum value + * @example + * // Minimum of 3 and 5 + * Min(3, 5) // returns 3 + */ + public Min(A: Float, B: Float): Float { + return Math.min(A, B); + } + + /** + * Get right vector from roll, pitch, yaw angles + * @param Roll - Rotation around forward axis in radians + * @param Pitch - Rotation around right axis in radians + * @param Yaw - Rotation around up axis in radians + * @returns Right direction vector + * @example + * // Right vector for no rotation + * GetRightVector(0, 0, 0) // returns Vector(1,0,0) + */ + public GetRightVector( + Roll: Float = 0, + Pitch: Float = 0, + Yaw: Float = 0 + ): Vector { + const CP = this.Cos(Pitch); + const SP = this.Sin(Pitch); + const CY = this.Cos(Yaw); + const SY = this.Sin(Yaw); + + console.log(Roll); + + return new Vector(CP * CY, CP * SY, SP); + } + + /** + * Get forward vector from roll, pitch, yaw angles + * @param Roll - Rotation around forward axis in radians + * @param Pitch - Rotation around right axis in radians + * @param Yaw - Rotation around up axis in radians + * @returns Forward direction vector + * @example + * // Forward vector for no rotation + * GetForwardVector(0, 0, 0) // returns Vector(1,0,0) + */ + public GetForwardVector( + Roll: Float = 0, + Pitch: Float = 0, + Yaw: Float = 0 + ): Vector { + const CP = this.Cos(Pitch); + const SP = this.Sin(Pitch); + const CY = this.Cos(Yaw); + const SY = this.Sin(Yaw); + + console.log(Roll); + + return new Vector(CP * CY, CP * SY, SP); + } } /** diff --git a/Content/UE/Pawn.ts b/Content/UE/Pawn.ts index 1a217ce..364965d 100644 --- a/Content/UE/Pawn.ts +++ b/Content/UE/Pawn.ts @@ -3,6 +3,7 @@ import { Actor } from '#root/UE/Actor.ts'; import { Controller } from '#root/UE/Controller.ts'; import { Name } from '#root/UE/Name.ts'; +import { Rotator } from '#root/UE/Rotator.ts'; import { UEObject } from '#root/UE/UEObject.ts'; export class Pawn extends Actor { @@ -13,4 +14,8 @@ export class Pawn extends Actor { public GetController(): Controller { return new Controller(this); } + + public GetControlRotation(): Rotator { + return new Rotator(); + } }