一、插值
线性插值是采用一次多项式上进行的插值计算,任意给定两个值A和B,那么在A和B之间的任意值可以定义为:P(t) = A * (1 - t) + B * t,0 <= t <= 1。
数学中用于线性拟合,游戏应用可以做出跟随效果(宠物跟随、npc跟随)
java
const FOLLOW_SPEED = 4.0
func _physics_process(delta):
var mouse_pos = get_local_mouse_position()
$Sprite2D.position = $Sprite2D.position.lerp(mouse_pos, delta * FOLLOW_SPEED)
二、贝塞尔
贝塞尔是插值的应用之一。贝塞尔曲线是为工业设计,是图形软件行业中的流行工具。不过在游戏中会出现曲线扭曲的情况,应用并不好。
1.二次贝塞尔:
我们首先使用 0 到 1 之间的值,在两个线段的每个顶点上逐步插值。当我们把 t 值从 0 变成 1 时,就得到了两个沿着线段移动的点。然后,我们插值 q0 和 q1,以获得沿着曲线移动的单点 r。
java
func _quadratic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, t: float):
var q0 = p0.lerp(p1, t)
var q1 = p1.lerp(p2, t)
var r = q0.lerp(q1, t)
return r
2.三次贝塞尔
同理三个线段的时候称为三次贝塞尔曲线。
java
func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float):
var q0 = p0.lerp(p1, t)
var q1 = p1.lerp(p2, t)
var q2 = p2.lerp(p3, t)
var r0 = q0.lerp(q1, t)
var r1 = q1.lerp(q2, t)
var s = r0.lerp(r1, t)
return s
三、Astar寻路
Astar算法是最广泛应用的寻路算法,它的特点是基于网格,而且可以快速的求解某个点到另一个点的最短有效路径。Godot种提供了算法的封装类可以直接使用。2D版本是AStar2D、AStarGrid2D。
1.思路
添加可以到达的位置
将可以行走的点两两连接,形成路径
通过其方法直接求取某个位置到目标位置的最短路径
让玩家或其他角色按照路径上点的顺序依次前进,直到到达目标位置
java
extends Node2D
var astar = AStar2D.new() # 实例化
func _ready():
# 添加可以到达的位置
astar.add_point(0,Vector2(0,0))
astar.add_point(1, Vector2(0, 0))
astar.add_point(2, Vector2(0, 1), 1) # 默认权重为 1
astar.add_point(3, Vector2(1, 1))
astar.add_point(4, Vector2(2, 0))
# 在点之间创建连接,形成路径
astar.connect_points(1, 2, false)
astar.connect_points(2, 3, false)
astar.connect_points(4, 3, false)
astar.connect_points(1, 4, false)
# 查询某两个位置之间的路径
var res = astar.get_id_path(1, 4) # [1,4]
add_point()
的时候传入了一个ID,可以将其想象为是一个唯一索引值,对点的标记。
get_id_path()
方法获取的是两个对应ID的点之间的最短路径,返回的是包含路径经过的所有点的ID所组成的数组。
你也可以用get_point_path()
方法直接获取两个点之间的最短路径,返回的额是包含所有经过的点数组。
2.应用
在Tilemap挂载方法(版本4.2.1):
java
extends TileMap
@onready var astar_node: AStar2D = AStar2D.new()
var map_size: Vector2i = get_used_rect().size
func _ready():
# 遍历所有层,将可以寻路的tile加入Astar图的节点
for layer in range(get_layers_count()):
var cells = get_used_cells(layer)
for cell in cells:
var tileData = get_cell_tile_data(layer, cell)
var nav = tileData.get_navigation_polygon(0)
if nav == null:
continue
var point_index = calculate_point_index(cell)
astar_node.add_point(point_index, Vector2(cell.x, cell.y))
# 移除有碰撞体的点
for layer in range(get_layers_count()):
var cells = get_used_cells(layer)
for cell in cells:
var tileData = get_cell_tile_data(layer, cell)
var collision = tileData.get_collision_polygons_count(0)
if collision <= 0:
continue
var point_index = calculate_point_index(cell)
if astar_node.has_point(point_index):
astar_node.remove_point(point_index)
# 连接图的节点
for id in astar_node.get_point_ids():
var cellPosition = Vector2i(astar_node.get_point_position(id))
var relativeCells: Array[Vector2i] = [
cellPosition + Vector2i.RIGHT,
cellPosition + Vector2i.LEFT,
cellPosition + Vector2i.DOWN,
cellPosition + Vector2i.UP,
]
for relativeCell in relativeCells:
var relativeCellIndex = calculate_point_index(relativeCell)
if is_outside_map_bounds(relativeCell):
continue
if astar_node.has_point(relativeCellIndex):
astar_node.connect_points(id, relativeCellIndex, false)
func calculate_point_index(point: Vector2i) -> int:
return point.x + point.y * map_size.x
func is_outside_map_bounds(point: Vector2i) -> bool:
return point.x <0 || point.y < 0 || point.x >= map_size.x || point.y >= map_size.y
func get_nav_path(startCellPosition: Vector2i, endCellPosition: Vector2i) -> PackedVector2Array:
var navCellPath = astar_node.get_point_path(calculate_point_index(startCellPosition), calculate_point_index(endCellPosition))
var localPath = PackedVector2Array()
for cellPosition in navCellPath:
localPath.push_back(map_to_local(Vector2i(cellPosition)))
return localPath