目标

  • 使用WASD平移
  • 使用鼠标中键平移
  • 使用鼠标滚轮按照鼠标位置放缩
  • 平滑动画

准备工作

窗口尺寸设置为 1920 * 1080 ,加入一些输入映射,例如WASD对应"ui_xx",设置空格或者你喜欢的按键作为"ui_recovery"
新建Node2dSprite2DCarmea2d
取消Sprite2D居中,并把Carmea2d锚点模式设置为Fixed Top Left 导入一张图片作为Sprite2D的纹理,我这里使用Azgaar的奇幻地图生成器随机生成了一张


运行图片左上角对齐屏幕左上角即可。 给创建的Node2d节点添加GDScript脚本,写入以下代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@onready var camera = $Camera2D
@onready var sprite = $Sprite2D
var CamScale = Vector2(1.0, 1.0)
var CamPos = Vector2(0.0, 0.0)

func _ready() -> void:
    pass

func _input(event: InputEvent) -> void:
    pass

func _process(delta: float) -> void:
    pass

其中CamScale保存变换后的缩放倍率,CamPos保存变换后的锚点位置

缩放

按照鼠标位置缩放

设置的相机锚点为左上角,即(0,0),那么直接对相机的zoom参数改变会以(0,0)为中心缩放。那么如果要实现按照鼠标为中心缩放则需要在缩放的同时进行平移。那么如何进行平移呢?

代码如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
var scale_delta = 0.05
var mouse_position

func _input(event: InputEvent) -> void:
	mouse_position = get_viewport().get_mouse_position()
	if event is InputEventMouseButton:
		var BeforeScale = CamScale
		#滚轮缩放
		if event.button_index == MOUSE_BUTTON_WHEEL_UP:
			CamScale.x = exp(log(CamScale.x) + scale_delta)
			CamScale.y = exp(log(CamScale.y) + scale_delta)
		if event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
			CamScale.x = exp(log(CamScale.x) - scale_delta)
			CamScale.y = exp(log(CamScale.y) - scale_delta)
		var pscale = CamScale / BeforeScale
		if pscale != Vector2(1, 1):
			#按照鼠标位置缩放
			var delta_mouse_posion = (pscale * (position + mouse_position) - mouse_position) / CamScale
			CamPos += delta_mouse_posion
			camera.zoom = CamScale

平移

平移操作分为WASD平移鼠标中键平移,两者逻辑稍有不同。
我们需要设置变量来保存平移一次的距离,在脚本中加入如下代码

1
var offset = 20

WASD平移

如果是在_input()里判断某键按下,似乎长按时只会相应一次。于是在_process里使用Input.get_vector来完成WASD的平移,此处的参考链接忘记了,欢迎评论区补充,我会及时补上。代码如下所示

1
2
3
4
5
6
7
8
var offsets: Vector2

func _process(delta: float) -> void:
	# WASD平移
	var input_direction = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
	offsets = offset * input_direction / CamScale #当放大时,相机移动速度相较原图也是放大的,所以需要除以缩放倍数
	CamPos += offsets
	camera.position = CamPos

鼠标中键平移

鼠标中键平移实际上就是按下鼠标中键时记录位置,没有松开中键时当前鼠标位置与记录位置做差作为平移偏移量,并更新记录位置,代码如下所示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
var mouse_position
var isDrag = false
var startPos: Vector2

func _input(event: InputEvent) -> void:
	mouse_position = get_viewport().get_mouse_position()
	if event is InputEventMouseButton:
		if event.button_index == MOUSE_BUTTON_MIDDLE:
			if event.is_pressed():
				startPos = mouse_position
				isDrag = true
			else:
				isDrag = false

	# 鼠标中键拖动平移	
	if isDrag:
		CamPos += (mouse_position - startPos) * CamScale
		camera.position = CamPos
		startPos = mouse_position

相机锚点限制

有时我们需要设置平移限制来避免相机移动到地图之外去。
首先我们需要地图的大小,可以使用sprite.texture.get_size()获得,还需要获得当前视口大小,可以使用get_viewport_rect().end获得矩形的右下角的点。
当相机移动的最右(下)时,屏幕最右(下)侧可以看到地图的最右(下)侧。当缩放系数为1时,此时的式子为 $$相机位置 + 屏幕尺寸 = 图片尺寸$$ 当缩放系数改变时,只需要在屏幕尺寸除以一个缩放系数即可。

代码如下,在每次改变缩放大小时需要更新右侧和下侧的锚点限制。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var leftSide
var topSide
var rightSide
var bottomSide
var limit_offset: Vector2

func _ready() -> void:
	var temp = get_viewport_rect().end / CamScale
	limit_offset = Vector2.ZERO
	leftSide = - limit_offset.x
	topSide = - limit_offset.y
	# 缩放值改变时更新相机锚点限制
	rightSide = maxf(0, sprite.texture.get_size().x - temp.x + limit_offset.x)
	bottomSide = maxf(0, sprite.texture.get_size().y - temp.y + limit_offset.y)

......

func _process(delta: float) -> void:
    ......
	## 相机锚点限制
	CamPos.x = maxf(CamPos.x, 0)
	CamPos.y = maxf(CamPos.y, 0)
	CamPos.x = minf(CamPos.x, rightSide)
	CamPos.y = minf(CamPos.y, bottomSide)
	
	camera.position = CamPos

效果展示

完整代码

点击展开
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
extends Node2D

@onready var camera = $Camera2D
@onready var sprite = $Sprite2D
var CamScale = Vector2(1.0, 1.0)
var CamPos = Vector2(0.0, 0.0)

var offset = 20
var scale_delta = 0.05
var offsets: Vector2
var mouse_position
var isDrag = false
var startPos: Vector2
var leftSide
var topSide
var rightSide
var bottomSide
var limit_offset: Vector2

func _ready() -> void:
	var temp = get_viewport_rect().end / CamScale
	limit_offset = Vector2.ZERO
	leftSide = - limit_offset.x
	topSide = - limit_offset.y
	rightSide = maxf(0, sprite.texture.get_size().x - temp.x + limit_offset.x)
	bottomSide = maxf(0, sprite.texture.get_size().y - temp.y + limit_offset.y)

func _input(event: InputEvent) -> void:
	mouse_position = get_viewport().get_mouse_position()
	# 恢复初始视口
	if event.is_action_pressed("ui_recovery"):
		CamPos = Vector2(0.0, 0.0)
		CamScale = Vector2(1.0, 1.0)
		camera.zoom = CamScale
		# 缩放值改变时更新相机锚点限制
		var temp = get_viewport_rect().end / CamScale
		rightSide = maxf(0, sprite.texture.get_size().x - temp.x + limit_offset.x)
		bottomSide = maxf(0, sprite.texture.get_size().y - temp.y + limit_offset.y)
	if event is InputEventMouseButton:
		var BeforeScale = CamScale
		#滚轮缩放
		if event.button_index == MOUSE_BUTTON_WHEEL_UP:
			CamScale.x = exp(log(CamScale.x) + scale_delta)
			CamScale.y = exp(log(CamScale.y) + scale_delta)
		if event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
			CamScale.x = exp(log(CamScale.x) - scale_delta)
			CamScale.y = exp(log(CamScale.y) - scale_delta)
		# 鼠标中键拖动平移
		if event.button_index == MOUSE_BUTTON_MIDDLE:
			if event.is_pressed():
				startPos = mouse_position
				isDrag = true
			else:
				isDrag = false
		var pscale = CamScale / BeforeScale
		if pscale != Vector2(1, 1):
			#按照鼠标位置缩放
			var delta_mouse_posion = (pscale * (position + mouse_position) - mouse_position) / CamScale
			CamPos += delta_mouse_posion
			camera.zoom = CamScale
			# 缩放值改变时更新相机锚点限制
			var temp = get_viewport_rect().end / CamScale
			rightSide = maxf(0, sprite.texture.get_size().x - temp.x + limit_offset.x)
			bottomSide = maxf(0, sprite.texture.get_size().y - temp.y + limit_offset.y)
	# 鼠标中键拖动平移	
	if isDrag:
		CamPos += (mouse_position - startPos) * CamScale
		startPos = mouse_position

func _process(delta: float) -> void:
	# WASD平移
	var input_direction = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
	offsets = offset * input_direction / CamScale
	CamPos += offsets
	
	# 相机锚点限制
	CamPos.x = maxf(CamPos.x, 0)
	CamPos.y = maxf(CamPos.y, 0)
	CamPos.x = minf(CamPos.x, rightSide)
	CamPos.y = minf(CamPos.y, bottomSide)
	
	camera.position = CamPos

参考链接