fixed properties numbering, comments and LNXfactor to LNXFloat

This commit is contained in:
2025-07-08 22:48:16 +02:00
parent e234c8615c
commit 57f0e937d0
2 changed files with 227 additions and 90 deletions

View File

@ -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);
}
}

View File

@ -2,95 +2,158 @@ from lnx.logicnode.lnx_nodes import *
class MouseLookNode(LnxLogicTreeNode):
"""Controls object rotation based on mouse movement for FPS-style camera control.
"""MouseLookNode - Blender UI interface for FPS-style mouse look camera controller
This class defines the Blender node interface for the MouseLookNode logic node.
It creates the visual node that appears in Blender's logic tree editor and
defines all the properties that configure the mouse look behavior.
The node provides controls for:
- Axis orientation configuration
- Mouse cursor behavior
- Movement inversion options
- Rotation limiting/capping
- Head rotation space behavior
Features:
- Built-in resolution-adaptive scaling for consistent feel across different screen resolutions
- Automatic physics synchronization for rigid bodies
- Support for both single-object and dual-object (body/head) setups
"""
bl_idname = 'LNMouseLookNode'
bl_label = 'Mouse Look'
lnx_section = 'mouse'
lnx_version = 1
# Front axis property
# Blender node identification
bl_idname = 'LNMouseLookNode' # Unique identifier for Blender's node system
bl_label = 'Mouse Look' # Display name in node menu and header
lnx_section = 'mouse' # Category section in node add menu
lnx_version = 1 # Node version for compatibility tracking
# Property 0: Front Axis Configuration
# Determines which 3D axis represents the "forward" direction of the character/camera
# This affects how horizontal and vertical rotations are applied to the objects
property0: HaxeEnumProperty(
'property0',
items=[('X', 'X Axis', 'X Axis as front'),
('Y', 'Y Axis', 'Y Axis as front'),
('Z', 'Z Axis', 'Z Axis as front')],
name='Front', default='Y')
items=[('X', 'X Axis', 'X Axis as front'), # X-forward (side-scrolling, specific orientations)
('Y', 'Y Axis', 'Y Axis as front'), # Y-forward (most common for 3D games)
('Z', 'Z Axis', 'Z Axis as front')], # Z-forward (top-down, specific orientations)
name='Front',
default='Y') # Y-axis is default as it's most common in 3D game development
# Hide Locked property
# Property 1: Automatic Mouse Cursor Management
# When enabled, automatically centers and locks the mouse cursor when mouse input starts
# This is essential for FPS games to prevent cursor from leaving game window
property1: HaxeBoolProperty(
'property1',
name='Hide Locked',
description='Automatically center and lock the mouse cursor',
default=True)
description='Automatically center and lock the mouse cursor when mouse input begins',
default=True) # Enabled by default for typical FPS behavior
# Invert X property
# Property 2: Horizontal Movement Inversion
# Allows users to invert horizontal mouse movement (left becomes right, right becomes left)
# Some players prefer inverted controls for consistency with flight simulators
property2: HaxeBoolProperty(
'property2',
name='Invert X',
description='Invert horizontal mouse movement',
default=False)
description='Invert horizontal mouse movement - moving mouse right turns character left',
default=False) # Most players expect non-inverted horizontal movement
# Invert Y property
# Property 3: Vertical Movement Inversion
# Allows users to invert vertical mouse movement (up becomes down, down becomes up)
# More commonly used than horizontal inversion, especially by flight sim players
property3: HaxeBoolProperty(
'property3',
name='Invert Y',
description='Invert vertical mouse movement',
default=False)
description='Invert vertical mouse movement - moving mouse up looks down',
default=False) # Most players expect non-inverted vertical movement
# Cap Left/Right property
# Property 4: Horizontal Rotation Limiting
# Prevents the character from rotating beyond specified horizontal limits
# Useful for fixed-perspective games or when character shouldn't turn completely around
property4: HaxeBoolProperty(
'property4',
name='Cap Left / Right',
description='Limit horizontal rotation',
default=False)
description='Limit horizontal rotation to prevent full 360-degree turns',
default=False) # Disabled by default - most FPS games allow full horizontal rotation
# Cap Up/Down property
# Property 5: Vertical Rotation Limiting
# Prevents looking too far up or down, simulating human neck movement limitations
# Essential for realistic FPS games to prevent disorienting over-rotation
property5: HaxeBoolProperty(
'property5',
name='Cap Up / Down',
description='Limit vertical rotation',
default=True)
description='Limit vertical rotation to simulate natural neck movement (±90 degrees)',
default=True) # Enabled by default for realistic FPS behavior
# Head rotation space toggle
property7: HaxeBoolProperty(
'property7',
# Property 6: Head Rotation Space Mode
# Controls whether head rotation uses local or world space coordinates
# Critical for preventing rotation issues when head object is child of body object
property6: HaxeBoolProperty(
'property6',
name='Head Local Space',
description='Enable it if the Head is child of the Body to avoid weird rotation',
default=False)
description='Use local space for head rotation - enable when Head is child of Body to avoid gimbal lock',
default=False) # Disabled by default, enable when using parent-child object relationships
def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'In')
self.add_input('LnxNodeSocketObject', 'Body')
self.add_input('LnxNodeSocketObject', 'Head')
self.add_input('LnxFloatSocket', 'Sensitivity', default_value=0.5)
self.add_input('LnxFactorSocket', 'Smoothing', default_value=0.0)
"""Initialize the node's input and output sockets
This method is called when the node is first created in Blender.
It defines all the connection points (sockets) that other nodes can connect to.
Input Sockets:
- Action In: Execution flow input (when this node should run)
- Body: The main character/player object that rotates horizontally
- Head: Optional camera/head object that rotates vertically (can be child of Body)
- Sensitivity: Mouse sensitivity multiplier (0.5 = half sensitivity, 2.0 = double sensitivity)
- Smoothing: Movement smoothing factor (0.0 = no smoothing, higher = more smoothing)
Output Sockets:
- Action Out: Execution flow output (continues to next node after processing)
"""
# Execution flow input - connects from previous logic node
self.add_input('LnxNodeSocketAction', 'In')
# Object inputs - require 3D objects from the scene
self.add_input('LnxNodeSocketObject', 'Body') # Main character object (required)
self.add_input('LnxNodeSocketObject', 'Head') # Camera/head object (optional)
# Numeric inputs with sensible defaults
self.add_input('LnxFloatSocket', 'Sensitivity', default_value=0.5) # Medium sensitivity
self.add_input('LnxFloatSocket', 'Smoothing', default_value=0.0) # No smoothing by default
# Execution flow output - connects to next logic node
self.add_output('LnxNodeSocketAction', 'Out')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0', text='Front')
layout.prop(self, 'property1', text='Hide Locked')
"""Draw the node's user interface in Blender's logic tree editor
# Invert XY section
col = layout.column(align=True)
col.label(text="Invert XY:")
row = col.row(align=True)
row.prop(self, 'property2', text='X', toggle=True)
row.prop(self, 'property3', text='Y', toggle=True)
This method creates the visual controls that appear on the node in Blender.
It organizes properties into logical groups for better usability.
# Cap rotations section
col = layout.column(align=True)
col.prop(self, 'property4', text='Cap Left / Right')
col.prop(self, 'property5', text='Cap Up / Down')
Args:
context: Blender context (current scene, selected objects, etc.)
layout: UI layout object for arranging interface elements
"""
# Head behavior section
col = layout.column(align=True)
col.prop(self, 'property7', text='Head Local Space')
# Basic configuration section
layout.prop(self, 'property0', text='Front') # Front axis dropdown
layout.prop(self, 'property1', text='Hide Locked') # Mouse locking checkbox
# Movement inversion controls section
# Group X and Y inversion together for logical organization
col = layout.column(align=True) # Create aligned column
col.label(text="Invert XY:") # Section header
row = col.row(align=True) # Create horizontal row within column
row.prop(self, 'property2', text='X', toggle=True) # X inversion toggle button
row.prop(self, 'property3', text='Y', toggle=True) # Y inversion toggle button
# Rotation limiting controls section
# Group rotation caps together since they're related functionality
col = layout.column(align=True) # Create new aligned column
col.prop(self, 'property4', text='Cap Left / Right') # Horizontal capping checkbox
col.prop(self, 'property5', text='Cap Up / Down') # Vertical capping checkbox
# Advanced head behavior section
# Separate advanced option that affects technical behavior
col = layout.column(align=True) # Create new aligned column
col.prop(self, 'property6', text='Head Local Space') # Head rotation space checkbox