第十六章:2D变换与坐标系
"理解坐标系统是精确控制游戏对象的前提。"
在2D游戏开发中,坐标变换是基础中的基础。本章将深入讲解Godot的坐标系统、变换操作、坐标空间转换等核心概念。
16.1 坐标系统基础
16.1.1 Godot的2D坐标系
Godot 2D坐标系:
- 原点(0, 0)在左上角
- X轴向右为正
- Y轴向下为正(注意:与数学坐标系相反)
(0,0) ────────────► X+
│
│
│
▼
Y+
16.1.2 坐标空间类型
# 局部坐标(Local)- 相对于父节点
# 全局坐标(Global)- 相对于场景原点
# 视口坐标(Viewport)- 相对于视口
# 屏幕坐标(Screen)- 相对于窗口
extends Node2D
func show_coordinates():
# 局部坐标
print("局部位置:", position)
print("局部旋转:", rotation)
print("局部缩放:", scale)
# 全局坐标
print("全局位置:", global_position)
print("全局旋转:", global_rotation)
print("全局缩放:", global_scale)
16.1.3 Transform2D
# Transform2D是2D变换的核心数据结构
# 包含:位置、旋转、缩放
extends Node2D
func understand_transform():
# 获取变换矩阵
var t: Transform2D = transform
var gt: Transform2D = global_transform
# Transform2D结构
# [x轴向量, y轴向量, 原点位置]
# x: Vector2 - X轴方向和缩放
# y: Vector2 - Y轴方向和缩放
# origin: Vector2 - 位置
print("X轴:", t.x)
print("Y轴:", t.y)
print("原点:", t.origin)
# 从Transform2D提取信息
var pos = t.origin
var rot = t.get_rotation()
var scl = t.get_scale()
16.2 位置操作
16.2.1 设置位置
extends Node2D
func set_positions():
# 设置局部位置
position = Vector2(100, 200)
position.x = 150
position.y = 250
# 设置全局位置
global_position = Vector2(500, 300)
# 相对移动
position += Vector2(10, 0) # 向右移动10像素
# 移动到目标
var target = Vector2(200, 200)
position = position.move_toward(target, 5.0) # 每次移动5像素
16.2.2 平滑移动
extends Node2D
var target_position: Vector2
func _process(delta: float):
# 线性插值移动
position = position.lerp(target_position, 0.1)
# 带速度的移动
var speed = 200.0
var direction = position.direction_to(target_position)
var distance = position.distance_to(target_position)
if distance > 5.0:
position += direction * speed * delta
# 使用Tween平滑移动
func move_to_smooth(target: Vector2, duration: float = 0.5):
var tween = create_tween()
tween.tween_property(self, "position", target, duration)
tween.set_trans(Tween.TRANS_SINE)
tween.set_ease(Tween.EASE_IN_OUT)
16.3 旋转操作
16.3.1 旋转基础
extends Node2D
func rotation_basics():
# rotation使用弧度
rotation = PI / 4 # 45度
# rotation_degrees使用角度
rotation_degrees = 45
# 全局旋转
global_rotation = PI / 2
global_rotation_degrees = 90
# 旋转速度
rotation += 2.0 * delta # 每秒2弧度
# 常用角度转换
var degrees = rad_to_deg(rotation)
var radians = deg_to_rad(45)
16.3.2 朝向目标
extends Node2D
var target: Node2D
func _process(delta: float):
if target:
look_at_target()
# 立即朝向
func look_at_target():
look_at(target.global_position)
# 平滑转向
func rotate_toward_target(speed: float, delta: float):
var target_angle = global_position.angle_to_point(target.global_position)
global_rotation = lerp_angle(global_rotation, target_angle, speed * delta)
# angle_to_point返回从当前点到目标点的角度
# 注意方向与look_at相反
# 正确的平滑朝向
func smooth_look_at(target_pos: Vector2, lerp_weight: float = 0.1):
var direction = target_pos - global_position
var target_rotation = direction.angle()
rotation = lerp_angle(rotation, target_rotation, lerp_weight)
16.3.3 环绕旋转
extends Node2D
var pivot_point: Vector2 = Vector2.ZERO
var orbit_radius: float = 100.0
var orbit_speed: float = 2.0
var current_angle: float = 0.0
func _process(delta: float):
# 环绕旋转
current_angle += orbit_speed * delta
position.x = pivot_point.x + cos(current_angle) * orbit_radius
position.y = pivot_point.y + sin(current_angle) * orbit_radius
# 使用Transform2D旋转
func rotate_around_point(point: Vector2, angle: float):
var offset = global_position - point
offset = offset.rotated(angle)
global_position = point + offset
16.4 缩放操作
16.4.1 缩放基础
extends Node2D
func scale_basics():
# 均匀缩放
scale = Vector2(2, 2) # 放大2倍
# 非均匀缩放
scale = Vector2(2, 1) # 只横向放大
# 全局缩放
global_scale = Vector2(1, 1)
# 镜像(翻转)
scale.x = -1 # 水平翻转
scale.y = -1 # 垂直翻转
16.4.2 缩放动画
extends Sprite2D
func pulse_effect():
var tween = create_tween().set_loops()
tween.tween_property(self, "scale", Vector2(1.2, 1.2), 0.3)
tween.tween_property(self, "scale", Vector2(1.0, 1.0), 0.3)
func pop_in():
scale = Vector2.ZERO
var tween = create_tween()
tween.tween_property(self, "scale", Vector2(1.2, 1.2), 0.15)
tween.tween_property(self, "scale", Vector2(1.0, 1.0), 0.1)
func squash_and_stretch():
# 跳跃前压扁
var tween = create_tween()
tween.tween_property(self, "scale", Vector2(1.3, 0.7), 0.1)
tween.tween_property(self, "scale", Vector2(0.7, 1.3), 0.1) # 跳跃拉伸
tween.tween_property(self, "scale", Vector2(1.0, 1.0), 0.1)
16.5 坐标空间转换
16.5.1 局部与全局转换
extends Node2D
func coordinate_conversion():
# 局部坐标转全局坐标
var local_point = Vector2(10, 20)
var global_point = to_global(local_point)
# 全局坐标转局部坐标
var global_pos = Vector2(500, 300)
var local_pos = to_local(global_pos)
# 使用Transform2D转换
var world_point = global_transform * local_point
var local_from_world = global_transform.affine_inverse() * world_point
16.5.2 视口坐标转换
extends Node2D
func viewport_conversion():
# 获取鼠标位置
var mouse_screen = get_viewport().get_mouse_position() # 视口坐标
var mouse_world = get_global_mouse_position() # 世界坐标
var mouse_local = get_local_mouse_position() # 局部坐标
# 考虑相机变换
var camera = get_viewport().get_camera_2d()
if camera:
var canvas_transform = get_canvas_transform()
var world_pos = canvas_transform.affine_inverse() * mouse_screen
func screen_to_world(screen_pos: Vector2) -> Vector2:
var canvas_transform = get_canvas_transform()
return canvas_transform.affine_inverse() * screen_pos
func world_to_screen(world_pos: Vector2) -> Vector2:
var canvas_transform = get_canvas_transform()
return canvas_transform * world_pos
16.5.3 节点间坐标转换
extends Node2D
func convert_between_nodes(from_node: Node2D, to_node: Node2D, point: Vector2) -> Vector2:
# 先转为全局坐标,再转为目标节点的局部坐标
var global_point = from_node.to_global(point)
return to_node.to_local(global_point)
# 示例:获取玩家在敌人视角中的位置
func get_player_relative_position() -> Vector2:
var player = get_node("/root/Main/Player")
return to_local(player.global_position)
16.6 方向与距离
16.6.1 方向计算
extends Node2D
var target: Node2D
func direction_calculations():
# 获取朝向目标的方向向量(单位向量)
var direction = global_position.direction_to(target.global_position)
# 计算角度
var angle = global_position.angle_to_point(target.global_position)
# 从角度获取方向
var dir_from_angle = Vector2.from_angle(rotation)
# 常用方向常量
var up = Vector2.UP # (0, -1)
var down = Vector2.DOWN # (0, 1)
var left = Vector2.LEFT # (-1, 0)
var right = Vector2.RIGHT # (1, 0)
func get_forward_direction() -> Vector2:
# 获取当前朝向的前方向量
return Vector2.RIGHT.rotated(rotation)
func get_right_direction() -> Vector2:
# 获取右方向
return Vector2.DOWN.rotated(rotation)
16.6.2 距离计算
extends Node2D
func distance_calculations():
var target_pos = Vector2(200, 200)
# 欧几里得距离
var dist = position.distance_to(target_pos)
# 平方距离(更快,用于比较)
var dist_sq = position.distance_squared_to(target_pos)
# 检查是否在范围内
var range = 100.0
if dist_sq < range * range: # 使用平方距离避免开方
print("在范围内")
# 查找最近的敌人
func find_nearest_enemy() -> Node2D:
var enemies = get_tree().get_nodes_in_group("enemies")
var nearest: Node2D = null
var min_dist_sq = INF
for enemy in enemies:
var dist_sq = global_position.distance_squared_to(enemy.global_position)
if dist_sq < min_dist_sq:
min_dist_sq = dist_sq
nearest = enemy
return nearest
16.7 Transform2D进阶
16.7.1 创建变换
extends Node2D
func create_transforms():
# 单位变换
var identity = Transform2D.IDENTITY
# 从角度创建
var rotated = Transform2D(PI / 4, Vector2.ZERO) # 旋转45度
# 完整构造
var t = Transform2D(
Vector2(1, 0), # X轴
Vector2(0, 1), # Y轴
Vector2(100, 50) # 原点
)
# 从位置、旋转、缩放创建
var custom = Transform2D()
custom = custom.scaled(Vector2(2, 2))
custom = custom.rotated(PI / 4)
custom.origin = Vector2(100, 100)
16.7.2 变换操作
extends Node2D
func transform_operations():
var t = Transform2D.IDENTITY
# 平移
t = t.translated(Vector2(100, 50))
# 旋转(绕原点)
t = t.rotated(PI / 4)
# 缩放(从原点)
t = t.scaled(Vector2(2, 2))
# 逆变换
var inverse = t.affine_inverse()
# 变换组合(注意顺序)
var combined = t1 * t2 # 先应用t2,再应用t1
# 应用变换到点
var point = Vector2(10, 10)
var transformed_point = t * point
16.7.3 变换插值
extends Node2D
var start_transform: Transform2D
var end_transform: Transform2D
var interpolation: float = 0.0
func _ready():
start_transform = global_transform
end_transform = Transform2D(PI / 2, Vector2(500, 300))
func _process(delta: float):
interpolation += delta * 0.5
interpolation = clamp(interpolation, 0.0, 1.0)
global_transform = start_transform.interpolate_with(end_transform, interpolation)
16.8 实用工具类
16.8.1 坐标工具
# coordinate_utils.gd
class_name CoordinateUtils
# 将角度归一化到 [-PI, PI]
static func normalize_angle(angle: float) -> float:
while angle > PI:
angle -= TAU
while angle < -PI:
angle += TAU
return angle
# 计算两个角度之间的最短旋转
static func shortest_angle_distance(from: float, to: float) -> float:
var diff = fmod(to - from + PI, TAU) - PI
return diff
# 检查点是否在矩形内
static func is_point_in_rect(point: Vector2, rect: Rect2) -> bool:
return rect.has_point(point)
# 检查点是否在圆内
static func is_point_in_circle(point: Vector2, center: Vector2, radius: float) -> bool:
return point.distance_squared_to(center) <= radius * radius
# 获取圆上的点
static func point_on_circle(center: Vector2, radius: float, angle: float) -> Vector2:
return center + Vector2(cos(angle), sin(angle)) * radius
# 随机圆内点
static func random_point_in_circle(center: Vector2, radius: float) -> Vector2:
var angle = randf() * TAU
var r = sqrt(randf()) * radius
return center + Vector2(cos(angle), sin(angle)) * r
# 网格对齐
static func snap_to_grid(point: Vector2, grid_size: Vector2) -> Vector2:
return Vector2(
snappedf(point.x, grid_size.x),
snappedf(point.y, grid_size.y)
)
16.8.2 路径跟随
# path_follower.gd
extends Node2D
@export var path: Path2D
@export var speed: float = 100.0
@export var loop: bool = true
var _progress: float = 0.0
var _path_follow: PathFollow2D
func _ready():
if path:
_setup_path_follow()
func _setup_path_follow():
_path_follow = PathFollow2D.new()
_path_follow.rotates = true
_path_follow.loop = loop
path.add_child(_path_follow)
func _process(delta: float):
if _path_follow:
_progress += speed * delta
_path_follow.progress = _progress
global_position = _path_follow.global_position
global_rotation = _path_follow.global_rotation
16.9 实际案例
16.9.1 相机跟随系统
# camera_follow.gd
extends Camera2D
@export var target: Node2D
@export var smoothing: float = 5.0
@export var look_ahead: float = 50.0
@export var dead_zone: Vector2 = Vector2(20, 20)
var _target_position: Vector2
func _physics_process(delta: float):
if not target:
return
var target_pos = target.global_position
# 添加前瞻
if target is CharacterBody2D:
var velocity = target.velocity
target_pos += velocity.normalized() * look_ahead
# 死区处理
var diff = target_pos - global_position
if abs(diff.x) < dead_zone.x:
target_pos.x = global_position.x
if abs(diff.y) < dead_zone.y:
target_pos.y = global_position.y
# 平滑跟随
global_position = global_position.lerp(target_pos, smoothing * delta)
16.9.2 视野检测
# vision_cone.gd
extends Area2D
@export var view_angle: float = 60.0 # 度
@export var view_distance: float = 300.0
func can_see(target: Node2D) -> bool:
var to_target = target.global_position - global_position
var distance = to_target.length()
# 距离检查
if distance > view_distance:
return false
# 角度检查
var forward = Vector2.RIGHT.rotated(global_rotation)
var angle_to_target = forward.angle_to(to_target)
if abs(rad_to_deg(angle_to_target)) > view_angle / 2:
return false
# 射线检查(遮挡)
var space = get_world_2d().direct_space_state
var query = PhysicsRayQueryParameters2D.create(
global_position,
target.global_position,
1 # collision_mask
)
query.exclude = [self]
var result = space.intersect_ray(query)
if result.is_empty():
return true
return result.collider == target
本章小结
本章深入学习了Godot的2D变换与坐标系统:
- 坐标系统基础:Godot坐标系、坐标空间类型
- 位置操作:设置位置、平滑移动
- 旋转操作:旋转基础、朝向目标、环绕旋转
- 缩放操作:缩放基础、缩放动画
- 坐标空间转换:局部/全局/视口坐标转换
- 方向与距离:方向计算、距离计算
- Transform2D进阶:创建、操作、插值
- 实用工具:坐标工具、路径跟随、相机跟随
下一章将学习2D物理系统。
上一章:精灵与纹理
下一章:2D物理系统