实现原理
将加载的区域按特定范围(本教程以3X3为例)作为一个区块,一个区块创建一个TileMapLayer,并以一条线程负责生成地图块
将多个(本教程以9x9为例)区块(即多个TileMapLayer)叠放后做为一张完整区域地图,最终实现27X27范围的地图块加载
在玩家移动的过程中,计算玩家坐标所处的格子(玩家坐标 / 格子大小),在通过格子的坐标,计算玩家所处的区块(格子坐标 / 区块大小)是否发生变化
实现过程
创建一个资源,选择TileSet,并设置好地图块
创建一个脚本,名为Chunk.dg,所有区块都将使用此脚本,脚本继承自TileMapLayer
完善负责更新图块的方法update_chunk,按噪声生成随机的地形,线程结束后将缓存的数据设置到当前的TileMapLayer
在_exit_tree中等待线程完成,到这一步,一个3*3区块的线程加载就完成了,现在需要在父节点生成9*9共81个此类节点,并动态添加
将携带此脚本的节点拖拽到文件系统,生成一个单独的场景
在父节点添加脚本
在每一帧,检测玩家的坐标,计算出图标的坐标,在计算出区块的坐标,循环9*9的区块,将81个区块坐标保存,在判断多出来的区块,和缺少的区块,多出来的释放掉,缺少的创建出来
最终效果:
附上完整代码:
class_name Chunk extends TileMapLayer
# 区块加载的图块大小
var chunk_size : Vector2 = Vector2(3,3)
# 区块的坐标
var chunk_coord : Vector2
# 线程ID
var task_id : int
# TileMapLayer缓存
var buffer_layer : TileMapLayer = TileMapLayer.new()
# TileSet
var tileset = preload("res://new_tile_set.tres")
# 生成随机地形的噪声
var noise : FastNoiseLite
func _ready() -> void:
# 设置图块集
buffer_layer.tile_set = tileset
# 启动线程加载数据
task_id = WorkerThreadPool.add_task(update_chunk)
# 更新区块
func update_chunk():
# 计算结束图块的位置
var start_coord = chunk_coord * chunk_size
var end_coord = start_coord + chunk_size
# 遍历3*3的范围创建图块
for x in range(start_coord.x,end_coord.x):
for y in range(start_coord.y,end_coord.y):
# 通过噪声获取海拔
var h = noise.get_noise_2d(x,y)
if h < 0:
# 创建水面
buffer_layer.set_cell(Vector2i(x,y),0,Vector2i(2,0))
elif h < 0.1:
# 创建沙滩
buffer_layer.set_cell(Vector2i(x,y),0,Vector2i(0,1))
else:
# 创建草地
buffer_layer.set_cell(Vector2i(x,y),0,Vector2i(1,0))
# 从buffer获取数据
var data = buffer_layer.tile_map_data
# 空闲时更新数据
call_deferred("update_finished",data)
# 线程处理完毕后
func update_finished(data):
tile_map_data = data
# 在退出事件中,等待线程结束
func _exit_tree() -> void:
WorkerThreadPool.wait_for_task_completion(task_id)
extends Node2D
# 图块大小
var cell_size = Vector2(16,16)
# 加载的区块大小
var size : Vector2 = Vector2(9,9)
# 一个区块的大小
var once_chunk_size : Vector2 = Vector2(3,3)
# 玩家
@onready var player : CharacterBody2D = $"../CharacterBody2D"
# 玩家当前所处区块
var current_chunk : Vector2
# 所有区块的字典
var chunks = {}
# 预加载场景
var chunk_scene:PackedScene = preload("res://Chunk.tscn")
# 海拔噪声
var noise = FastNoiseLite.new()
# 强制更新
var update = true
func _ready():
noise.seed = randi()
func _process(delta: float) -> void:
# 取地图快坐标
var cell_coord = (player.global_position / cell_size).floor()
# 取区块坐标
var chunk_coord = (cell_coord / once_chunk_size).floor()
# 区块坐标发生变化,或者强制更新
if chunk_coord != current_chunk or update:
update = false
# 更新当前区块
current_chunk = chunk_coord
# 开始的区块,中心点坐标需要偏移一半
var start_chunk = current_chunk - (size / 2).floor()
var end_chunk = start_chunk + size
# 加载的区块
var load = []
# 把所有的图块坐标加入到数组
for x in range(start_chunk.x,end_chunk.x):
for y in range(start_chunk.y,end_chunk.y):
load.append(Vector2(x,y))
# 卸载的区域,遍历已经加载的区块
for coord in chunks.keys():
if !load.has(coord):
# 卸载
(chunks[coord] as TileMapLayer).queue_free()
chunks.erase(coord)
# 加载的区域
for coord in load:
if !chunks.has(coord):
#加载,创建节点,设置坐标,大小,噪声,保存到字典,添加到节点
var layer:Chunk = chunk_scene.instantiate()
layer.chunk_coord = coord
layer.chunk_size = once_chunk_size
layer.noise = noise
chunks[coord] = layer
add_child(layer)