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 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
|
||||
const FOG_TILE_SIZE: int = 16
|
||||
const FOG_VIEW_RANGE_TILES: float = 10.0
|
||||
@@ -130,6 +141,9 @@ func _ready():
|
||||
# Create inventory UI
|
||||
_create_inventory_ui()
|
||||
|
||||
# Initialize mouse cursor system
|
||||
_init_mouse_cursor()
|
||||
|
||||
# Generate dungeon on host only
|
||||
# Only generate if we're the server (not just "no multiplayer peer")
|
||||
# This prevents clients from generating their own dungeon before connecting
|
||||
@@ -1512,7 +1526,11 @@ func _check_tab_visibility():
|
||||
return window_visible
|
||||
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)
|
||||
if not multiplayer.is_server():
|
||||
var is_tab_visible = _check_tab_visibility()
|
||||
@@ -1560,11 +1578,11 @@ func _process(_delta):
|
||||
|
||||
# Update camera to follow local players
|
||||
_update_camera()
|
||||
_update_fog_of_war(_delta)
|
||||
_update_fog_of_war(delta)
|
||||
|
||||
# Periodic cleanup of disconnected peers (server only)
|
||||
if multiplayer.is_server() and multiplayer.has_multiplayer_peer():
|
||||
peer_cleanup_timer += _delta
|
||||
peer_cleanup_timer += delta
|
||||
if peer_cleanup_timer >= PEER_CLEANUP_INTERVAL:
|
||||
peer_cleanup_timer = 0.0
|
||||
_cleanup_disconnected_peers()
|
||||
@@ -1696,7 +1714,7 @@ func _update_camera():
|
||||
|
||||
# Adjust zoom based on player spread (for split-screen effect)
|
||||
if local_players.size() > 1:
|
||||
var max_distance = 0.0a
|
||||
var max_distance = 0.0
|
||||
for player in local_players:
|
||||
var distance = center.distance_to(player.position)
|
||||
max_distance = max(max_distance, distance)
|
||||
@@ -1708,6 +1726,121 @@ func _update_camera():
|
||||
# Always update zoom (for both single and multi-player)
|
||||
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():
|
||||
if dungeon_data.is_empty() or not dungeon_data.has("map_size"):
|
||||
return
|
||||
|
||||
@@ -23,6 +23,8 @@ var teleported_this_frame: bool = false # Flag to prevent position sync from ove
|
||||
# Input device (for local multiplayer)
|
||||
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 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
|
||||
var held_object = null
|
||||
@@ -1251,6 +1253,23 @@ func _get_direction_from_vector(vec: Vector2) -> int:
|
||||
else: # 292.5 to 337.5
|
||||
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):
|
||||
if current_animation != anim_name:
|
||||
current_animation = anim_name
|
||||
@@ -1684,6 +1703,7 @@ func _handle_input():
|
||||
last_movement_direction = input_vector.normalized()
|
||||
|
||||
# Update facing direction (except when pushing - locked direction)
|
||||
# Note: Mouse control will override this if mouse is being used
|
||||
var new_direction = current_direction
|
||||
if not is_pushing:
|
||||
new_direction = _get_direction_from_vector(input_vector) as Direction
|
||||
@@ -1774,10 +1794,12 @@ func _handle_interactions():
|
||||
var grab_just_released = false
|
||||
|
||||
if input_device == -1:
|
||||
# Keyboard input
|
||||
grab_button_down = Input.is_action_pressed("grab")
|
||||
grab_just_pressed = Input.is_action_just_pressed("grab")
|
||||
grab_just_released = Input.is_action_just_released("grab")
|
||||
# Keyboard or Mouse input
|
||||
var mouse_right_pressed = Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT)
|
||||
grab_button_down = Input.is_action_pressed("grab") or mouse_right_pressed
|
||||
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
|
||||
if grab_just_pressed and grab_just_released:
|
||||
@@ -1907,8 +1929,10 @@ func _handle_interactions():
|
||||
# Handle attack input
|
||||
var attack_just_pressed = false
|
||||
if input_device == -1:
|
||||
# Keyboard
|
||||
attack_just_pressed = Input.is_action_just_pressed("attack")
|
||||
# Keyboard or Mouse
|
||||
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:
|
||||
# Gamepad (X button)
|
||||
attack_just_pressed = Input.is_joy_button_pressed(input_device, JOY_BUTTON_X)
|
||||
|
||||
Reference in New Issue
Block a user