allow lookin g in mouse direction
This commit is contained in:
BIN
src/assets/gfx/cursor.png
Normal file
BIN
src/assets/gfx/cursor.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 156 B |
40
src/assets/gfx/cursor.png.import
Normal file
40
src/assets/gfx/cursor.png.import
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dxyhsfq22vxfn"
|
||||||
|
path="res://.godot/imported/cursor.png-308a6879f38fa7a626935612b5aab578.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/gfx/cursor.png"
|
||||||
|
dest_files=["res://.godot/imported/cursor.png-308a6879f38fa7a626935612b5aab578.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
src/assets/gfx/ui/ELR_Corsshairs.png
Normal file
BIN
src/assets/gfx/ui/ELR_Corsshairs.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
40
src/assets/gfx/ui/ELR_Corsshairs.png.import
Normal file
40
src/assets/gfx/ui/ELR_Corsshairs.png.import
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bc0eg8xeht1m0"
|
||||||
|
path="res://.godot/imported/ELR_Corsshairs.png-1be59a5a0530c35e5b746d569963749c.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/gfx/ui/ELR_Corsshairs.png"
|
||||||
|
dest_files=["res://.godot/imported/ELR_Corsshairs.png-1be59a5a0530c35e5b746d569963749c.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
@@ -11,6 +11,17 @@ const BASE_CAMERA_ZOOM: float = 4.0
|
|||||||
const BASE_CAMERA_ZOOM_MOBILE: float = 5.5 # More zoomed in for mobile devices
|
const BASE_CAMERA_ZOOM_MOBILE: float = 5.5 # More zoomed in for mobile devices
|
||||||
const REFERENCE_ASPECT: float = 16.0 / 9.0
|
const REFERENCE_ASPECT: float = 16.0 / 9.0
|
||||||
|
|
||||||
|
# Mouse cursor system
|
||||||
|
var cursor_sprite: Sprite2D = null # Free movement cursor (frame 0)
|
||||||
|
var grid_cursor_sprite: Sprite2D = null # Grid-locked cursor (frame 1)
|
||||||
|
var cursor_layer: CanvasLayer = null
|
||||||
|
const CURSOR_LAYER_Z: int = 2000 # Very high Z index for cursor
|
||||||
|
var use_mouse_control: bool = true # Enable/disable mouse control
|
||||||
|
var camera_lerp_to_cursor: bool = false # Optional: lerp camera slightly toward cursor
|
||||||
|
const CURSOR_CAMERA_LERP_AMOUNT: float = 0.15 # How much camera lerps toward cursor (0.0 = none, 1.0 = full)
|
||||||
|
var cursor_pulse_time: float = 0.0 # Time accumulator for pulsing animation
|
||||||
|
const CURSOR_PULSE_SPEED: float = 3.0 # Speed of color pulse animation
|
||||||
|
|
||||||
# Fog of war
|
# Fog of war
|
||||||
const FOG_TILE_SIZE: int = 16
|
const FOG_TILE_SIZE: int = 16
|
||||||
const FOG_VIEW_RANGE_TILES: float = 10.0
|
const FOG_VIEW_RANGE_TILES: float = 10.0
|
||||||
@@ -130,6 +141,9 @@ func _ready():
|
|||||||
# Create inventory UI
|
# Create inventory UI
|
||||||
_create_inventory_ui()
|
_create_inventory_ui()
|
||||||
|
|
||||||
|
# Initialize mouse cursor system
|
||||||
|
_init_mouse_cursor()
|
||||||
|
|
||||||
# Generate dungeon on host only
|
# Generate dungeon on host only
|
||||||
# Only generate if we're the server (not just "no multiplayer peer")
|
# Only generate if we're the server (not just "no multiplayer peer")
|
||||||
# This prevents clients from generating their own dungeon before connecting
|
# This prevents clients from generating their own dungeon before connecting
|
||||||
@@ -1512,7 +1526,11 @@ func _check_tab_visibility():
|
|||||||
return window_visible
|
return window_visible
|
||||||
return true # Always visible on non-web platforms
|
return true # Always visible on non-web platforms
|
||||||
|
|
||||||
func _process(_delta):
|
func _process(delta):
|
||||||
|
# Update mouse cursor
|
||||||
|
if use_mouse_control:
|
||||||
|
_update_mouse_cursor(delta)
|
||||||
|
|
||||||
# Check tab visibility for buffer overflow protection (clients only)
|
# Check tab visibility for buffer overflow protection (clients only)
|
||||||
if not multiplayer.is_server():
|
if not multiplayer.is_server():
|
||||||
var is_tab_visible = _check_tab_visibility()
|
var is_tab_visible = _check_tab_visibility()
|
||||||
@@ -1560,11 +1578,11 @@ func _process(_delta):
|
|||||||
|
|
||||||
# Update camera to follow local players
|
# Update camera to follow local players
|
||||||
_update_camera()
|
_update_camera()
|
||||||
_update_fog_of_war(_delta)
|
_update_fog_of_war(delta)
|
||||||
|
|
||||||
# Periodic cleanup of disconnected peers (server only)
|
# Periodic cleanup of disconnected peers (server only)
|
||||||
if multiplayer.is_server() and multiplayer.has_multiplayer_peer():
|
if multiplayer.is_server() and multiplayer.has_multiplayer_peer():
|
||||||
peer_cleanup_timer += _delta
|
peer_cleanup_timer += delta
|
||||||
if peer_cleanup_timer >= PEER_CLEANUP_INTERVAL:
|
if peer_cleanup_timer >= PEER_CLEANUP_INTERVAL:
|
||||||
peer_cleanup_timer = 0.0
|
peer_cleanup_timer = 0.0
|
||||||
_cleanup_disconnected_peers()
|
_cleanup_disconnected_peers()
|
||||||
@@ -1696,7 +1714,7 @@ func _update_camera():
|
|||||||
|
|
||||||
# Adjust zoom based on player spread (for split-screen effect)
|
# Adjust zoom based on player spread (for split-screen effect)
|
||||||
if local_players.size() > 1:
|
if local_players.size() > 1:
|
||||||
var max_distance = 0.0a
|
var max_distance = 0.0
|
||||||
for player in local_players:
|
for player in local_players:
|
||||||
var distance = center.distance_to(player.position)
|
var distance = center.distance_to(player.position)
|
||||||
max_distance = max(max_distance, distance)
|
max_distance = max(max_distance, distance)
|
||||||
@@ -1708,6 +1726,121 @@ func _update_camera():
|
|||||||
# Always update zoom (for both single and multi-player)
|
# Always update zoom (for both single and multi-player)
|
||||||
camera.zoom = camera.zoom.lerp(Vector2.ONE * target_zoom, 0.05)
|
camera.zoom = camera.zoom.lerp(Vector2.ONE * target_zoom, 0.05)
|
||||||
|
|
||||||
|
# Optional: Lerp camera slightly toward cursor direction
|
||||||
|
if camera_lerp_to_cursor and use_mouse_control:
|
||||||
|
var cursor_world_pos = camera.get_global_mouse_position()
|
||||||
|
var cursor_offset = cursor_world_pos - center
|
||||||
|
var lerped_center = center + cursor_offset * CURSOR_CAMERA_LERP_AMOUNT
|
||||||
|
camera.position = camera.position.lerp(lerped_center, 0.1)
|
||||||
|
|
||||||
|
func _init_mouse_cursor():
|
||||||
|
# Create cursor layer with high Z index
|
||||||
|
cursor_layer = CanvasLayer.new()
|
||||||
|
cursor_layer.name = "MouseCursorLayer"
|
||||||
|
cursor_layer.layer = CURSOR_LAYER_Z
|
||||||
|
add_child(cursor_layer)
|
||||||
|
|
||||||
|
# Load cursor texture
|
||||||
|
var cursor_texture = load("res://assets/gfx/cursor.png")
|
||||||
|
if not cursor_texture:
|
||||||
|
push_error("GameWorld: Could not load cursor.png!")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Create free movement cursor sprite (frame 0)
|
||||||
|
cursor_sprite = Sprite2D.new()
|
||||||
|
cursor_sprite.name = "MouseCursorFree"
|
||||||
|
cursor_sprite.texture = cursor_texture
|
||||||
|
# Set up sprite sheet (32x16 with 2 frames of 16x16 each)
|
||||||
|
cursor_sprite.hframes = 2
|
||||||
|
cursor_sprite.vframes = 1
|
||||||
|
cursor_sprite.frame = 0 # Frame 0 = free movement
|
||||||
|
cursor_layer.add_child(cursor_sprite)
|
||||||
|
|
||||||
|
# Create grid-locked cursor sprite (frame 1)
|
||||||
|
grid_cursor_sprite = Sprite2D.new()
|
||||||
|
grid_cursor_sprite.name = "MouseCursorGrid"
|
||||||
|
grid_cursor_sprite.texture = cursor_texture
|
||||||
|
grid_cursor_sprite.hframes = 2
|
||||||
|
grid_cursor_sprite.vframes = 1
|
||||||
|
grid_cursor_sprite.frame = 1 # Frame 1 = grid-locked
|
||||||
|
grid_cursor_sprite.modulate.a = 0.5 # 50% opacity
|
||||||
|
cursor_layer.add_child(grid_cursor_sprite)
|
||||||
|
|
||||||
|
# Hide system cursor
|
||||||
|
Input.mouse_mode = Input.MOUSE_MODE_HIDDEN
|
||||||
|
|
||||||
|
func _update_mouse_cursor(delta: float):
|
||||||
|
if not use_mouse_control:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not cursor_sprite or not is_instance_valid(cursor_sprite):
|
||||||
|
return
|
||||||
|
|
||||||
|
if not grid_cursor_sprite or not is_instance_valid(grid_cursor_sprite):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update pulse time for grid cursor color animation
|
||||||
|
cursor_pulse_time += delta * CURSOR_PULSE_SPEED
|
||||||
|
|
||||||
|
# Get mouse position in viewport (screen space)
|
||||||
|
var mouse_pos = get_viewport().get_mouse_position()
|
||||||
|
|
||||||
|
# Convert to world position for game logic
|
||||||
|
var world_pos = camera.get_global_mouse_position()
|
||||||
|
|
||||||
|
# Scale cursors to match camera zoom level (so grid-locked cursor aligns with tiles)
|
||||||
|
# Each cursor frame is 16x16 pixels, and tiles are 16x16 pixels
|
||||||
|
# Scale by camera zoom to maintain 1:1 pixel ratio
|
||||||
|
var cursor_scale = camera.zoom.x # Use x zoom (should be same as y)
|
||||||
|
cursor_sprite.scale = Vector2.ONE * cursor_scale
|
||||||
|
grid_cursor_sprite.scale = Vector2.ONE * cursor_scale
|
||||||
|
|
||||||
|
# Check if we should show grid-locked cursor (when mouse is over game world tiles)
|
||||||
|
var show_grid_cursor = false
|
||||||
|
var grid_locked_world_pos = world_pos
|
||||||
|
if dungeon_tilemap_layer:
|
||||||
|
var tile_pos = dungeon_tilemap_layer.local_to_map(world_pos - dungeon_tilemap_layer.global_position)
|
||||||
|
var tile_data = dungeon_tilemap_layer.get_cell_source_id(tile_pos)
|
||||||
|
if tile_data >= 0: # Valid tile
|
||||||
|
show_grid_cursor = true
|
||||||
|
# Snap to tile center for world position calculation
|
||||||
|
grid_locked_world_pos = dungeon_tilemap_layer.map_to_local(tile_pos) + dungeon_tilemap_layer.global_position
|
||||||
|
# Convert grid-locked world position to screen position for cursor display
|
||||||
|
# Formula: (world_pos - camera.position) * camera.zoom + viewport_center
|
||||||
|
var viewport_size = get_viewport().get_visible_rect().size
|
||||||
|
var viewport_center = viewport_size / 2.0
|
||||||
|
var grid_locked_screen_pos = (grid_locked_world_pos - camera.position) * camera.zoom.x + viewport_center
|
||||||
|
grid_cursor_sprite.position = grid_locked_screen_pos
|
||||||
|
else:
|
||||||
|
grid_cursor_sprite.position = Vector2(-1000, -1000) # Hide off-screen
|
||||||
|
|
||||||
|
# Update free cursor position (always follows mouse)
|
||||||
|
cursor_sprite.position = mouse_pos
|
||||||
|
|
||||||
|
# Update grid cursor visibility and pulsing color
|
||||||
|
grid_cursor_sprite.visible = show_grid_cursor
|
||||||
|
if show_grid_cursor:
|
||||||
|
# Pulse color: oscillate between normal and brighter color
|
||||||
|
var pulse_value = (sin(cursor_pulse_time) + 1.0) / 2.0 # 0.0 to 1.0
|
||||||
|
# Interpolate between normal (1,1,1) and brighter (1.5, 1.2, 1.0) for a warm pulse
|
||||||
|
var base_color = Color(1.0, 1.0, 1.0)
|
||||||
|
var pulse_color = Color(1.5, 1.2, 1.0)
|
||||||
|
grid_cursor_sprite.modulate = base_color.lerp(pulse_color, pulse_value * 0.5) # 50% of the way to pulse color
|
||||||
|
grid_cursor_sprite.modulate.a = 0.5 # Keep opacity at 50%
|
||||||
|
|
||||||
|
# Update player facing direction based on mouse position (use world position)
|
||||||
|
if local_players.size() > 0:
|
||||||
|
var player = local_players[0] # Use first local player
|
||||||
|
if player and is_instance_valid(player) and player.is_local_player:
|
||||||
|
var player_pos = player.global_position
|
||||||
|
# Use grid-locked position if available, otherwise use free mouse position
|
||||||
|
var target_world_pos = grid_locked_world_pos if show_grid_cursor else world_pos
|
||||||
|
var mouse_direction = (target_world_pos - player_pos).normalized()
|
||||||
|
|
||||||
|
# Only update facing if mouse is far enough from player
|
||||||
|
if mouse_direction.length() > 0.1:
|
||||||
|
player._update_facing_from_mouse(mouse_direction)
|
||||||
|
|
||||||
func _init_fog_of_war():
|
func _init_fog_of_war():
|
||||||
if dungeon_data.is_empty() or not dungeon_data.has("map_size"):
|
if dungeon_data.is_empty() or not dungeon_data.has("map_size"):
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ var teleported_this_frame: bool = false # Flag to prevent position sync from ove
|
|||||||
# Input device (for local multiplayer)
|
# Input device (for local multiplayer)
|
||||||
var input_device: int = -1 # -1 for keyboard, 0+ for gamepad index
|
var input_device: int = -1 # -1 for keyboard, 0+ for gamepad index
|
||||||
var virtual_joystick_input: Vector2 = Vector2.ZERO # Virtual joystick input from mobile controls
|
var virtual_joystick_input: Vector2 = Vector2.ZERO # Virtual joystick input from mobile controls
|
||||||
|
var was_mouse_right_pressed: bool = false # Track previous mouse right button state
|
||||||
|
var was_mouse_left_pressed: bool = false # Track previous mouse left button state
|
||||||
|
|
||||||
# Interaction
|
# Interaction
|
||||||
var held_object = null
|
var held_object = null
|
||||||
@@ -1251,6 +1253,23 @@ func _get_direction_from_vector(vec: Vector2) -> int:
|
|||||||
else: # 292.5 to 337.5
|
else: # 292.5 to 337.5
|
||||||
return Direction.UP_RIGHT
|
return Direction.UP_RIGHT
|
||||||
|
|
||||||
|
# Update facing direction from mouse position (called by GameWorld)
|
||||||
|
func _update_facing_from_mouse(mouse_direction: Vector2):
|
||||||
|
# Only update if using keyboard input (not gamepad)
|
||||||
|
if input_device != -1:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Don't update if pushing (locked direction)
|
||||||
|
if is_pushing:
|
||||||
|
return
|
||||||
|
|
||||||
|
var new_direction = _get_direction_from_vector(mouse_direction) as Direction
|
||||||
|
|
||||||
|
# Update direction and cone light rotation if changed
|
||||||
|
if new_direction != current_direction:
|
||||||
|
current_direction = new_direction
|
||||||
|
_update_cone_light_rotation()
|
||||||
|
|
||||||
func _set_animation(anim_name: String):
|
func _set_animation(anim_name: String):
|
||||||
if current_animation != anim_name:
|
if current_animation != anim_name:
|
||||||
current_animation = anim_name
|
current_animation = anim_name
|
||||||
@@ -1684,6 +1703,7 @@ func _handle_input():
|
|||||||
last_movement_direction = input_vector.normalized()
|
last_movement_direction = input_vector.normalized()
|
||||||
|
|
||||||
# Update facing direction (except when pushing - locked direction)
|
# Update facing direction (except when pushing - locked direction)
|
||||||
|
# Note: Mouse control will override this if mouse is being used
|
||||||
var new_direction = current_direction
|
var new_direction = current_direction
|
||||||
if not is_pushing:
|
if not is_pushing:
|
||||||
new_direction = _get_direction_from_vector(input_vector) as Direction
|
new_direction = _get_direction_from_vector(input_vector) as Direction
|
||||||
@@ -1774,10 +1794,12 @@ func _handle_interactions():
|
|||||||
var grab_just_released = false
|
var grab_just_released = false
|
||||||
|
|
||||||
if input_device == -1:
|
if input_device == -1:
|
||||||
# Keyboard input
|
# Keyboard or Mouse input
|
||||||
grab_button_down = Input.is_action_pressed("grab")
|
var mouse_right_pressed = Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT)
|
||||||
grab_just_pressed = Input.is_action_just_pressed("grab")
|
grab_button_down = Input.is_action_pressed("grab") or mouse_right_pressed
|
||||||
grab_just_released = Input.is_action_just_released("grab")
|
grab_just_pressed = Input.is_action_just_pressed("grab") or (mouse_right_pressed and not was_mouse_right_pressed)
|
||||||
|
grab_just_released = Input.is_action_just_released("grab") or (not mouse_right_pressed and was_mouse_right_pressed)
|
||||||
|
was_mouse_right_pressed = mouse_right_pressed
|
||||||
|
|
||||||
# DEBUG: Log button states if there's a conflict
|
# DEBUG: Log button states if there's a conflict
|
||||||
if grab_just_pressed and grab_just_released:
|
if grab_just_pressed and grab_just_released:
|
||||||
@@ -1907,8 +1929,10 @@ func _handle_interactions():
|
|||||||
# Handle attack input
|
# Handle attack input
|
||||||
var attack_just_pressed = false
|
var attack_just_pressed = false
|
||||||
if input_device == -1:
|
if input_device == -1:
|
||||||
# Keyboard
|
# Keyboard or Mouse
|
||||||
attack_just_pressed = Input.is_action_just_pressed("attack")
|
var mouse_left_pressed = Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)
|
||||||
|
attack_just_pressed = Input.is_action_just_pressed("attack") or (mouse_left_pressed and not was_mouse_left_pressed)
|
||||||
|
was_mouse_left_pressed = mouse_left_pressed
|
||||||
else:
|
else:
|
||||||
# Gamepad (X button)
|
# Gamepad (X button)
|
||||||
attack_just_pressed = Input.is_joy_button_pressed(input_device, JOY_BUTTON_X)
|
attack_just_pressed = Input.is_joy_button_pressed(input_device, JOY_BUTTON_X)
|
||||||
|
|||||||
Reference in New Issue
Block a user