[code] add character rotation to movement component

main
Nikolay Petrov 2025-09-23 20:07:23 +05:00
parent 01ef4abe50
commit df3deef577
8 changed files with 287 additions and 13 deletions

View File

@ -10,6 +10,7 @@ import { Cast } from '#root/UE/Cast.ts';
import type { Controller } from '#root/UE/Controller.ts'; import type { Controller } from '#root/UE/Controller.ts';
import { EnhancedInputLocalPlayerSubsystem } from '#root/UE/EnhancedInputLocalPlayerSubsystem.ts'; import { EnhancedInputLocalPlayerSubsystem } from '#root/UE/EnhancedInputLocalPlayerSubsystem.ts';
import type { Float } from '#root/UE/Float.ts'; import type { Float } from '#root/UE/Float.ts';
import { MathLibrary } from '#root/UE/MathLibrary.ts';
import { Pawn } from '#root/UE/Pawn.ts'; import { Pawn } from '#root/UE/Pawn.ts';
import type { PlayerController } from '#root/UE/PlayerController.ts'; import type { PlayerController } from '#root/UE/PlayerController.ts';
import { Rotator } from '#root/UE/Rotator.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 * Process movement input for ground-based movement
* @param actionValueX - Horizontal movement input value (-1 to 1) * @param ActionValueX - Horizontal movement input value (-1 to 1)
* @param actionValueY - Vertical movement input value (-1 to 1) * @param ActionValueY - Vertical movement input value (-1 to 1)
*/ */
EnhancedInputActionMoveTriggered( EnhancedInputActionMoveTriggered(
actionValueX: Float, ActionValueX: Float,
actionValueY: Float ActionValueY: Float
): void { ): 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, this.CurrentMovementInput,
DeltaTime DeltaTime
); );
this.ApplyMovementToActor(); this.ApplyMovementAndRotation();
} }
// ════════════════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════════════════
@ -178,7 +208,9 @@ export class BP_MainCharacter extends Pawn {
* Apply calculated movement velocity to actor position * Apply calculated movement velocity to actor position
* @category Movement Application * @category Movement Application
*/ */
private ApplyMovementToActor(): void { private ApplyMovementAndRotation(): void {
this.SetActorRotation(this.MovementComponent.CurrentRotation);
const CalculateNewLocation = ( const CalculateNewLocation = (
currentLocation: Vector, currentLocation: Vector,
velocity: Vector velocity: Vector

BIN
Content/Blueprints/BP_MainCharacter.uasset (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

View File

@ -8,6 +8,7 @@ import type { S_MovementConstants } from '#root/Movement/Structs/S_MovementConst
import { ActorComponent } from '#root/UE/ActorComponent.ts'; import { ActorComponent } from '#root/UE/ActorComponent.ts';
import type { Float } from '#root/UE/Float.ts'; import type { Float } from '#root/UE/Float.ts';
import { MathLibrary } from '#root/UE/MathLibrary.ts'; import { MathLibrary } from '#root/UE/MathLibrary.ts';
import { Rotator } from '#root/UE/Rotator.ts';
import { Vector } from '#root/UE/Vector.ts'; import { Vector } from '#root/UE/Vector.ts';
/** /**
@ -132,6 +133,9 @@ export class AC_Movement extends ActorComponent {
if (this.IsInitialized) { if (this.IsInitialized) {
this.InputMagnitude = MathLibrary.VectorLength(InputVector); this.InputMagnitude = MathLibrary.VectorLength(InputVector);
this.TargetRotation = this.CalculateTargetRotation(InputVector);
this.UpdateCharacterRotation(DeltaTime);
// Only process movement on walkable surfaces // Only process movement on walkable surfaces
if (this.IsSurfaceWalkable(this.CurrentSurface) && this.IsGrounded) { if (this.IsSurfaceWalkable(this.CurrentSurface) && this.IsGrounded) {
this.ProcessGroundMovement(InputVector, DeltaTime); 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 * Initialize movement system with angle conversion
* Converts degree thresholds to radians for runtime performance * Converts degree thresholds to radians for runtime performance
@ -359,4 +450,56 @@ export class AC_Movement extends ActorComponent {
* @category Movement State * @category Movement State
*/ */
public CurrentSpeed: Float = 0.0; 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;
} }

Binary file not shown.

View File

@ -1,6 +1,7 @@
// UE/Actor.ts // UE/Actor.ts
import { Name } from '#root/UE/Name.ts'; import { Name } from '#root/UE/Name.ts';
import { Rotator } from '#root/UE/Rotator.ts';
import { UEObject } from '#root/UE/UEObject.ts'; import { UEObject } from '#root/UE/UEObject.ts';
import { Vector } from '#root/UE/Vector.ts'; import { Vector } from '#root/UE/Vector.ts';
@ -18,6 +19,14 @@ export class Actor extends UEObject {
// Implementation for setting actor location // 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 { public GetActorLocation(): Vector {
return new Vector(); // Placeholder implementation return new Vector(); // Placeholder implementation
} }

View File

@ -35,6 +35,18 @@ class MathLibraryClass extends BlueprintFunctionLibrary {
return (Degrees * Math.PI) / 180; 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 * Calculate sine of angle in radians
* @param Value - Angle in radians * @param Value - Angle in radians
@ -62,6 +74,16 @@ class MathLibraryClass extends BlueprintFunctionLibrary {
return Math.acos(Value); 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 * Calculate dot product of two vectors
* @param Vector1 - First vector * @param Vector1 - First vector
@ -207,6 +229,69 @@ class MathLibraryClass extends BlueprintFunctionLibrary {
return new Vector(0, 0, 0); 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);
}
} }
/** /**

View File

@ -3,6 +3,7 @@
import { Actor } from '#root/UE/Actor.ts'; import { Actor } from '#root/UE/Actor.ts';
import { Controller } from '#root/UE/Controller.ts'; import { Controller } from '#root/UE/Controller.ts';
import { Name } from '#root/UE/Name.ts'; import { Name } from '#root/UE/Name.ts';
import { Rotator } from '#root/UE/Rotator.ts';
import { UEObject } from '#root/UE/UEObject.ts'; import { UEObject } from '#root/UE/UEObject.ts';
export class Pawn extends Actor { export class Pawn extends Actor {
@ -13,4 +14,8 @@ export class Pawn extends Actor {
public GetController(): Controller { public GetController(): Controller {
return new Controller(this); return new Controller(this);
} }
public GetControlRotation(): Rotator {
return new Rotator();
}
} }