forked from LeenkxTeam/LNXSDK
fixed properties numbering, comments and LNXfactor to LNXFloat
This commit is contained in:
@ -6,102 +6,162 @@ import iron.object.Object;
|
||||
import kha.System;
|
||||
import kha.FastFloat;
|
||||
|
||||
/**
|
||||
* MouseLookNode - FPS-style mouse look camera controller
|
||||
*
|
||||
* This node provides smooth, resolution-independent mouse look functionality for
|
||||
* first-person perspective controls. It supports separate body and head objects,
|
||||
* allowing for realistic FPS camera movement where the body rotates horizontally
|
||||
* and the head/camera rotates vertically.
|
||||
*
|
||||
* Key Features:
|
||||
* - Resolution-adaptive scaling for consistent feel across different screen sizes
|
||||
* - Configurable axis orientations (X, Y, Z as front)
|
||||
* - Optional mouse cursor locking and hiding
|
||||
* - Invertible X/Y axes
|
||||
* - Rotation capping/limiting for both horizontal and vertical movement
|
||||
* - Smoothing support for smoother camera movement
|
||||
* - Physics integration with automatic rigid body synchronization
|
||||
* - Support for both local and world space head rotation
|
||||
*/
|
||||
class MouseLookNode extends LogicNode {
|
||||
public var property0: String;
|
||||
public var property1: Bool;
|
||||
public var property2: Bool;
|
||||
public var property3: Bool;
|
||||
public var property4: Bool;
|
||||
public var property5: Bool;
|
||||
public var property7: Bool;
|
||||
// Configuration properties (set from Blender node interface)
|
||||
public var property0: String; // Front axis: "X", "Y", or "Z"
|
||||
public var property1: Bool; // Hide Locked: auto-lock mouse cursor
|
||||
public var property2: Bool; // Invert X: invert horizontal mouse movement
|
||||
public var property3: Bool; // Invert Y: invert vertical mouse movement
|
||||
public var property4: Bool; // Cap Left/Right: limit horizontal rotation
|
||||
public var property5: Bool; // Cap Up/Down: limit vertical rotation
|
||||
public var property6: Bool; // Head Local Space: use local space for head rotation
|
||||
|
||||
var smoothX: Float = 0.0;
|
||||
var smoothY: Float = 0.0;
|
||||
var maxHorizontal: Float = Math.PI;
|
||||
var maxVertical: Float = Math.PI / 2;
|
||||
var currentHorizontal: Float = 0.0;
|
||||
var currentVertical: Float = 0.0;
|
||||
// Smoothing state variables - maintain previous frame values for interpolation
|
||||
var smoothX: Float = 0.0; // Smoothed horizontal mouse delta
|
||||
var smoothY: Float = 0.0; // Smoothed vertical mouse delta
|
||||
|
||||
// Rotation limits (in radians)
|
||||
var maxHorizontal: Float = Math.PI; // Maximum horizontal rotation (180 degrees)
|
||||
var maxVertical: Float = Math.PI / 2; // Maximum vertical rotation (90 degrees)
|
||||
|
||||
// Current rotation tracking for capping calculations
|
||||
var currentHorizontal: Float = 0.0; // Accumulated horizontal rotation
|
||||
var currentVertical: Float = 0.0; // Accumulated vertical rotation
|
||||
|
||||
// Resolution scaling reference - base resolution for consistent sensitivity
|
||||
var baseResolutionWidth: Float = 1920.0;
|
||||
|
||||
static inline var BASE_SCALE: Float = 1500.0;
|
||||
static var RADIAN_SCALING_FACTOR: Float = Math.PI * 50.0 / 180.0;
|
||||
// Sensitivity scaling constants
|
||||
static inline var BASE_SCALE: Float = 1500.0; // Base sensitivity scale factor
|
||||
static var RADIAN_SCALING_FACTOR: Float = Math.PI * 50.0 / 180.0; // Degrees to radians conversion with sensitivity scaling
|
||||
|
||||
public function new(tree: LogicTree) {
|
||||
super(tree);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main execution function called every frame when the node is active
|
||||
*
|
||||
* Input connections:
|
||||
* [0] - Action trigger (not used in current implementation)
|
||||
* [1] - Body Object: the main object that rotates horizontally
|
||||
* [2] - Head Object: optional object that rotates vertically (typically camera)
|
||||
* [3] - Sensitivity: mouse sensitivity multiplier
|
||||
* [4] - Smoothing: movement smoothing factor (0.0 = no smoothing, 0.99 = maximum smoothing)
|
||||
*/
|
||||
override function run(from: Int) {
|
||||
// Get input values from connected nodes
|
||||
var bodyObject: Object = inputs[1].get();
|
||||
var headObject: Object = inputs[2].get();
|
||||
var sensitivity: FastFloat = inputs[3].get();
|
||||
var smoothing: FastFloat = inputs[4].get();
|
||||
|
||||
// Early exit if no body object is provided
|
||||
if (bodyObject == null) {
|
||||
runOutput(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get mouse input state
|
||||
var mouse = Input.getMouse();
|
||||
|
||||
// Handle automatic mouse cursor locking for FPS controls
|
||||
if (property1) {
|
||||
if (mouse.started() && !mouse.locked) {
|
||||
mouse.lock();
|
||||
mouse.lock(); // Center and hide cursor, enable unlimited movement
|
||||
}
|
||||
}
|
||||
|
||||
// Only process mouse look when cursor is locked or mouse button is held
|
||||
// This prevents unwanted camera movement when UI elements are being used
|
||||
if (!mouse.locked && !mouse.down()) {
|
||||
runOutput(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get raw mouse movement delta (pixels moved since last frame)
|
||||
var deltaX: Float = mouse.movementX;
|
||||
var deltaY: Float = mouse.movementY;
|
||||
|
||||
if (property2) deltaX = -deltaX;
|
||||
if (property3) deltaY = -deltaY;
|
||||
// Apply axis inversion if configured
|
||||
if (property2) deltaX = -deltaX; // Invert horizontal movement
|
||||
if (property3) deltaY = -deltaY; // Invert vertical movement
|
||||
|
||||
// Always apply resolution-adaptive scaling
|
||||
// Calculate resolution-adaptive scaling to maintain consistent sensitivity
|
||||
// across different screen resolutions. Higher resolutions will have proportionally
|
||||
// higher scaling to compensate for increased pixel density.
|
||||
var resolutionMultiplier: Float = System.windowWidth() / baseResolutionWidth;
|
||||
|
||||
// Apply movement smoothing if enabled
|
||||
// This creates a weighted average between current and previous movement values
|
||||
// to reduce jittery camera movement, especially useful for low framerates
|
||||
if (smoothing > 0.0) {
|
||||
var smoothingFactor: Float = Math.min(smoothing, 0.99);
|
||||
var smoothingFactor: Float = Math.min(smoothing, 0.99); // Cap smoothing to prevent complete freeze
|
||||
smoothX = smoothX * smoothingFactor + deltaX * (1.0 - smoothingFactor);
|
||||
smoothY = smoothY * smoothingFactor + deltaY * (1.0 - smoothingFactor);
|
||||
deltaX = smoothX;
|
||||
deltaY = smoothY;
|
||||
}
|
||||
|
||||
var horizontalAxis = new Vec4();
|
||||
var verticalAxis = new Vec4();
|
||||
// Define rotation axes based on the configured front axis
|
||||
// These determine which 3D axes are used for horizontal and vertical rotation
|
||||
var horizontalAxis = new Vec4(); // Axis for left/right body rotation
|
||||
var verticalAxis = new Vec4(); // Axis for up/down head rotation
|
||||
|
||||
switch (property0) {
|
||||
case "X":
|
||||
horizontalAxis.set(0, 0, 1);
|
||||
verticalAxis.set(0, 1, 0);
|
||||
case "Y":
|
||||
case "X": // X-axis forward (e.g., for side-scrolling or specific orientations)
|
||||
horizontalAxis.set(0, 0, 1); // Z-axis for horizontal rotation
|
||||
verticalAxis.set(0, 1, 0); // Y-axis for vertical rotation
|
||||
case "Y": // Y-axis forward (most common for 3D games)
|
||||
#if lnx_yaxisup
|
||||
horizontalAxis.set(0, 0, 1);
|
||||
verticalAxis.set(1, 0, 0);
|
||||
// Y-up coordinate system (Blender default)
|
||||
horizontalAxis.set(0, 0, 1); // Z-axis for horizontal rotation
|
||||
verticalAxis.set(1, 0, 0); // X-axis for vertical rotation
|
||||
#else
|
||||
horizontalAxis.set(0, 0, 1);
|
||||
verticalAxis.set(1, 0, 0);
|
||||
// Z-up coordinate system
|
||||
horizontalAxis.set(0, 0, 1); // Z-axis for horizontal rotation
|
||||
verticalAxis.set(1, 0, 0); // X-axis for vertical rotation
|
||||
#end
|
||||
case "Z":
|
||||
horizontalAxis.set(0, 1, 0);
|
||||
verticalAxis.set(1, 0, 0);
|
||||
case "Z": // Z-axis forward (top-down or specific orientations)
|
||||
horizontalAxis.set(0, 1, 0); // Y-axis for horizontal rotation
|
||||
verticalAxis.set(1, 0, 0); // X-axis for vertical rotation
|
||||
}
|
||||
|
||||
// Always apply resolution-adaptive scaling
|
||||
// Calculate final sensitivity scaling combining base scale and resolution adaptation
|
||||
var finalScale: Float = BASE_SCALE * resolutionMultiplier;
|
||||
|
||||
// Apply user-defined sensitivity multiplier
|
||||
deltaX *= sensitivity;
|
||||
deltaY *= sensitivity;
|
||||
|
||||
// Convert pixel movement to rotation angles (radians)
|
||||
// Negative values ensure natural movement direction (moving mouse right rotates right)
|
||||
var horizontalRotation: Float = (-deltaX / finalScale) * RADIAN_SCALING_FACTOR;
|
||||
var verticalRotation: Float = (-deltaY / finalScale) * RADIAN_SCALING_FACTOR;
|
||||
|
||||
// Apply horizontal rotation capping if enabled
|
||||
// This prevents the character from rotating beyond specified limits
|
||||
if (property4) {
|
||||
currentHorizontal += horizontalRotation;
|
||||
// Clamp rotation to maximum horizontal range and adjust current frame rotation
|
||||
if (currentHorizontal > maxHorizontal) {
|
||||
horizontalRotation -= (currentHorizontal - maxHorizontal);
|
||||
currentHorizontal = maxHorizontal;
|
||||
@ -111,8 +171,11 @@ class MouseLookNode extends LogicNode {
|
||||
}
|
||||
}
|
||||
|
||||
// Apply vertical rotation capping if enabled
|
||||
// This prevents looking too far up or down (like human neck limitations)
|
||||
if (property5) {
|
||||
currentVertical += verticalRotation;
|
||||
// Clamp rotation to maximum vertical range and adjust current frame rotation
|
||||
if (currentVertical > maxVertical) {
|
||||
verticalRotation -= (currentVertical - maxVertical);
|
||||
currentVertical = maxVertical;
|
||||
@ -122,38 +185,49 @@ class MouseLookNode extends LogicNode {
|
||||
}
|
||||
}
|
||||
|
||||
// Apply horizontal rotation to body object (character turning left/right)
|
||||
if (horizontalRotation != 0.0) {
|
||||
bodyObject.transform.rotate(horizontalAxis, horizontalRotation);
|
||||
|
||||
// Synchronize physics rigid body if present
|
||||
// This ensures physics simulation stays in sync with visual transform
|
||||
#if lnx_physics
|
||||
var rigidBody = bodyObject.getTrait(leenkx.trait.physics.RigidBody);
|
||||
if (rigidBody != null) rigidBody.syncTransform();
|
||||
#end
|
||||
}
|
||||
|
||||
// Apply vertical rotation to head object (camera looking up/down)
|
||||
if (headObject != null && verticalRotation != 0.0) {
|
||||
if (property7) {
|
||||
// Local space rotation (recommended for FPS)
|
||||
if (property6) {
|
||||
// Local space rotation - recommended when head is a child of body
|
||||
// This prevents gimbal lock and rotation inheritance issues
|
||||
headObject.transform.rotate(verticalAxis, verticalRotation);
|
||||
} else {
|
||||
// World space rotation
|
||||
// World space rotation - uses head object's current right vector
|
||||
// More accurate for independent head objects but can cause issues with parenting
|
||||
var headVerticalAxis = headObject.transform.world.right();
|
||||
headObject.transform.rotate(headVerticalAxis, verticalRotation);
|
||||
}
|
||||
|
||||
// Synchronize head physics rigid body if present
|
||||
#if lnx_physics
|
||||
var headRigidBody = headObject.getTrait(leenkx.trait.physics.RigidBody);
|
||||
if (headRigidBody != null) headRigidBody.syncTransform();
|
||||
#end
|
||||
} else if (headObject == null && verticalRotation != 0.0) {
|
||||
// Fallback: if no separate head object, apply vertical rotation to body
|
||||
// This creates a simpler single-object camera control
|
||||
bodyObject.transform.rotate(verticalAxis, verticalRotation);
|
||||
|
||||
// Synchronize body physics rigid body
|
||||
#if lnx_physics
|
||||
var rigidBody = bodyObject.getTrait(leenkx.trait.physics.RigidBody);
|
||||
if (rigidBody != null) rigidBody.syncTransform();
|
||||
#end
|
||||
}
|
||||
|
||||
// Continue to next connected node in the logic tree
|
||||
runOutput(0);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user