Drag-scrolling of a Camera2D works on Windows, doesn't quite on Android

Godot Version

v4.3.stable.official [77dcf97d8]

Question

I have a code below which an updated version of RTS Camera2d - and for the most part, it works really well. That is - until I try this on an Android OS.

On Android, keyboard scrolling works as expected but the drag-scrolling will always reset the camera, so that when I start dragging, it kinda snaps to the center of that camera and drags from there.

Here’s a video showing the problem on Android: https://www.youtube.com/watch?v=rpK8PnMFLTs

And here is my code:

# Copyright 2017 Kamil Lewan <carmel4a97@gmail.com>
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

extends Camera2D

var debug_mode: bool = true

# Camera control settings:
# key - by keyboard
# drag - by clicking mouse button, left mouse button by default;
# edge - by moving mouse to the window edge
# wheel - zoom in/out by mouse wheel
var key: bool = true
var drag: bool = true
var edge: bool = false if ( OS.has_feature( "android" ) or OS.has_feature( "mobile" ) or OS.has_feature( "ios" ) or OS.has_feature( "web_android" ) or OS.has_feature( "web_ios" ) ) else true

# Camera speed in px/s.
var camera_speed: int = 450 

# Camera speed in px/s when the SHIFT key is pressed - i.e. TURBO.
var camera_speed_turbo: int = 1000 

# Value meaning how near to the window edge (in px) the mouse must be,
# to move a view.
var camera_margin: int = 50

# Vector of camera's movement / second.
var camera_movement = Vector2()

# Previous mouse position used to count delta of the mouse movement.
var _prev_mouse_pos = null

# Viewport rectangle - this is constant unless the window size changes, so we'll cache it.
@onready var viewport_rect = get_viewport_rect()

# INPUTS

# Left mouse button was or is pressed.
var __lmbk: bool = false
# SHIFT key was or is pressed.
var __shift: bool = false
# Whether left mouse button was released outside of the viewport,
# in which case we will ignore edge scrolling, as it would contradict
# the drag-scroll that just happened.
var __lmb_released_outside: bool = false
# Move camera by keys: left, top, right, bottom.
var __keys = [false, false, false, false]
# Mouse is in the window
var __mousein: bool = false

func _s( msg ):
	if debug_mode:
		print( msg )

func _ready():
	set_drag_horizontal_enabled(false)
	set_drag_vertical_enabled(false)
	#position_smoothing_enabled = true
	#position_smoothing_speed = 4

	# update viewport size on window change event	
	get_tree().root.connect( "size_changed", self._on_viewport_size_changed )

func _notification(what):
	if what == NOTIFICATION_WM_MOUSE_ENTER:
		__mousein = true
		__lmb_released_outside = false # reset this now that we're back in the window, so edge scrolling works again
	elif what == NOTIFICATION_WM_MOUSE_EXIT:
		__mousein = false

func _on_viewport_size_changed(): 
	viewport_rect = get_viewport_rect()

func _physics_process(delta):
	var curpos = get_local_mouse_position()
	var update_camera = false
	var actual_left_limit = viewport_rect.end.x / 2
	var actual_top_limit = viewport_rect.end.y / 2
	var current_camera_speed = camera_speed_turbo if __shift else camera_speed

	var can_scroll_left = ( position.x - ( current_camera_speed * delta ) > actual_left_limit )
	var can_scroll_top = ( position.y - ( current_camera_speed * delta ) > actual_top_limit )
	var can_scroll_right = ( position.x + ( viewport_rect.size.x / 2 ) < limit_right )
	var can_scroll_bottom = ( position.y + ( viewport_rect.size.y / 2 ) < limit_bottom )

	# Move camera by keys defined in InputMap (ui_left/top/right/bottom).
	if key:
		if __keys[0] and can_scroll_left:
			camera_movement.x -= current_camera_speed * delta
			update_camera = true
		if __keys[1] and can_scroll_top:
			camera_movement.y -= current_camera_speed * delta
			update_camera = true
		if __keys[2] and can_scroll_right:
			camera_movement.x += current_camera_speed * delta
			update_camera = true
		if __keys[3] and can_scroll_bottom:
			camera_movement.y += current_camera_speed * delta
			update_camera = true

	# no need for further calculations if we've already updated camera by using a keyboard
	if !update_camera:
		# When LMB is pressed, move camera by difference of mouse position
		if drag and __lmbk:
			camera_movement = _prev_mouse_pos - curpos
			# now check that we're not over the camera limits, and if so, reset those values over limits to their previous values
			if ( ( camera_movement.x < 0 and !can_scroll_left ) or ( camera_movement.x > 0 and !can_scroll_right ) ):
				camera_movement.x = 0
			if ( ( camera_movement.y < 0 and !can_scroll_top ) or ( camera_movement.y > 0 and !can_scroll_bottom ) ):
				camera_movement.y = 0
			update_camera = true

		# Move camera by mouse, when it's on the margin (defined by camera_margin).
		elif edge and !__lmb_released_outside:# and __mousein:
			var v = curpos + viewport_rect.size/2
			if viewport_rect.size.x - v.x <= camera_margin and can_scroll_right:
				camera_movement.x += current_camera_speed * delta
				update_camera = true
			if v.x <= camera_margin and can_scroll_left:
				camera_movement.x -= current_camera_speed * delta
				update_camera = true
			if viewport_rect.size.y - v.y <= camera_margin and can_scroll_bottom:
				camera_movement.y += current_camera_speed * delta
				update_camera = true
			if v.y <= camera_margin and can_scroll_top:
				camera_movement.y -= current_camera_speed * delta
				update_camera = true

	# Update position of the camera.
	if update_camera:
		position += camera_movement# * get_zoom()

		#var center = get_screen_center_position()
		#var current_target = get_target_position()
		#if current_target != center:
			#position = center

		# Set camera movement to zero
		camera_movement = Vector2(0,0)
		
	# Update old mouse position.
	_prev_mouse_pos = curpos

func _unhandled_input( event ):
	if event is InputEventMouseButton:
		if drag and event.button_index == MOUSE_BUTTON_LEFT:
			# Control by left mouse button.
			if event.pressed:
				__lmbk = true
				print("down, prev = ", _prev_mouse_pos, ", now = ", get_local_mouse_position())
			else:
				__lmbk = false
				print("up, prev = ", _prev_mouse_pos, ", now = ", get_local_mouse_position())
				if !__mousein:
					__lmb_released_outside = true
				else:
					__lmb_released_outside = false

	# SHIFT key press detection
	if event.is_action_pressed("turbo"):
		__shift = true
	if event.is_action_released("turbo"):
		__shift = false

	# Control by keyboard handled by InpuMap.
	if event.is_action_pressed("ui_left"):
		__keys[0] = true
	if event.is_action_pressed("ui_up"):
		__keys[1] = true
	if event.is_action_pressed("ui_right"):
		__keys[2] = true
	if event.is_action_pressed("ui_down"):
		__keys[3] = true
	if event.is_action_released("ui_left"):
		__keys[0] = false
	if event.is_action_released("ui_up"):
		__keys[1] = false
	if event.is_action_released("ui_right"):
		__keys[2] = false
	if event.is_action_released("ui_down"):
		__keys[3] = false

1 Like

I’ve since moved to use G.U.I.D.E. to control input from more devices than just keyboard or mouse, so this question is no longer relevant.

1 Like

How’s your experience with G.U.I.D.E? I’m also looking to migrate from the standard Godot way.

It took me a tiny little while to actually grasp the concepts beyond G.U.I.D.E. and after that, it was mostly a walk in the park. It solved my issue documented in this thread nicely, and that’s both on Desktops and Android devices (possibly also iPhones and iPads but I can’t tell because I don’t have one).

Also, the author is very responsive and helpful on GitHub, so I was able to solve a situation I was in (more here: Allow to link Scale XYZ values to a metadata field · Issue #17 · godotneers/G.U.I.D.E · GitHub).

I’d definitely give it a try and I can’t wait to test this on a joypad, once I get one, to see how well that works with G.U.I.D.E. :slight_smile:

Thanks for the update! I generally try to limit the number of plugins I use since their future is always in doubt, but G.U.I.D.E. looks like a winner. I watch the Godoteneers video series and it looks like the author is very active in the community and makes incredible content.

I probably check it out in a test project for a bit and then migrate it to my main game. I think the current Input Manager does work, but has some limitations that G.U.I.D.E. handles better.

2 Likes

Yes, I also watch his YouTube series and it’s one of the few people that’s super-easy to follow and cares for what he does and how. It’s another reason why I had no doubt in using his plugin in my project :slight_smile:

2 Likes