第六章 画出江南小镇——TileMap地图与摄像机
引言有无相生难易相成高下相倾 ——《道德经》上一章我们完成可操控主角但角色仅存在空白虚空场景。本章先重构清理冗余main.tscn搭建标准化全局常驻容器再独立拆分江南小镇关卡采用多层TileMapLayer制作国风瓦片地图、规范地形物理碰撞搭配Camera2D实现带边界限制的平滑跟随镜头最后完善全局管理器GameManager实现关卡动态加载、切换自动释放美术资源从根源解决多地图叠加造成的内存堆积问题。本章核心目标完整清理、重构main.tscn主场景搭建无冗余标准化全局层级结构新建独立关卡资源目录实现地图、角色与主场景完全解耦制作、裁切通用国风TileSet瓦片资源统一规范 2D 物理碰撞分层分层绘制江南小镇地图提前预留战斗、NPC、传送交互点位为主角配置带地图边界锁定的平滑跟随摄像机迭代GameManager全局管理脚本实现关卡动态实例化、切换自动释放贴图资源。6.1 重构主场景 main.tscn全局永久根场景统一规范主场景节点层级区分全局常驻模块与动态可销毁地图容器所有探索关卡仅临时挂载切换时完整释放。6.1.1 标准主场景树形结构Main (Node2D游戏唯一启动根节点全程不销毁) ├─ World (Node2D 大世界探索总容器) │ └─ CurrentLevel (空Node2D仅运行时挂载动态地图编辑器空白无资源、无脚本) ├─ BattleArena (Node2D 独立战斗空间全局常驻不销毁) └─ UI (CanvasLayer 全局HUD、对话、酿造界面永久顶层渲染)6.1.2 分步搭建规范操作选中父节点World右键添加子节点Node2D重命名CurrentLevel清空CurrentLevel所有子节点、贴图与附加脚本保持空白容器状态保留原有BattleArena、UI节点二者属于全局常驻功能不受地图切换影响6.1.3 层级功能约束说明World仅承载城镇、室内等探索类地图所有关卡运行时挂载至子节点CurrentLevelBattleArena独立隔离战斗逻辑投壶、对战系统与探索地图完全互不干扰UI依托CanvasLayer天然顶层渲染特性玩家状态栏、对话弹窗、酿造面板永久可见。6.2 创建独立关卡目录与小镇关卡场景6.2.1 新建关卡专用资源文件夹编辑器左侧文件面板右键scenes文件夹新建文件夹命名levels作用所有城镇、室内关卡场景统一归档与主场景、角色预制件分类管理项目结构清晰。6.2.2 创建江南小镇独立关卡场景快捷键CtrlN新建空白场景根节点选择Node2D重命名TownRootCtrlS保存路径res://scenes/levels/level_town.tscn在TownRoot下创建全套地图专用节点瓦片、交互标记、主角全部隔离在本关卡内TownRoot (Node2D 小镇关卡独立根节点切换时整体销毁) ├─ GroundLayer (TileMapLayer 地面底层) ├─ DecoLayer (TileMapLayer 花草装饰中层) ├─ WallLayer (TileMapLayer 墙体障碍顶层) ├─ NPCs (Node2D NPC出生坐标标记容器) └─ Player (实例第五章完成的Player.tscn主角预制件)6.3 瓦片地图核心原理与分层设计规范6.3.1 瓦片地图基础概念常规 RPG 地图存在大量重复视觉单元地砖、墙体、草木若每一块地面单独使用独立图片会造成海量内存占用。瓦片地图TileMap核心思路将所有地图视觉单元整合至一张图集Atlas通过网格坐标记录瓦片摆放位置。如同批量地砖复用同一套素材拼接出完整地图。三大核心优势内存极低单张图集承载数百种瓦片无论地图尺寸多大贴图资源仅加载一次编辑高效编辑器画笔、填充工具可视化绘制地图碰撞易管理可单独为每一类瓦片配置专属碰撞形状整张地图碰撞批量生效。6.3.2 TileMapLayer 分层机制Godot4.0 起废弃旧版单节点TileMap改用独立TileMapLayer图层节点每层可单独控制渲染层级、可见性、碰撞逻辑。《酒魂》固定三层瓦片分层规范渲染顺序自下而上底层→中层→顶层图层节点名层级作用是否启用物理碰撞GroundLayer底层青石板、泥土、河道水面否DecoLayer中层草丛、落叶、地面花纹、低矮灌木否WallLayer顶层院墙、房屋墙体、大树、巨石、栅栏是分层核心价值碰撞逻辑分离不会出现装饰草丛阻挡玩家移动、地板误触发碰撞的问题后续修改单一图层不影响整张地图。6.3.3 TileSet 瓦片集资源作用TileMapLayer仅记录瓦片摆放坐标不存储图片素材视觉资源、碰撞属性全部存放在独立TileSet资源中核心存储内容纹理源瓦片图集 PNG 原图裁切规格单块瓦片像素尺寸本项目统一 32×32 基础单元瓦片元数据碰撞外形、自定义标签、水波循环动画资源复用一套TileSet可供给多个关卡、多层TileMapLayer共用减少贴图重复加载。6.4 国风瓦片素材导入与 TileSet 标准化制作6.4.1 全局像素渲染统一配置顶部菜单项目 → 项目设置 → 渲染 → 纹理将「默认纹理过滤」修改为最近邻 (Nearest)全局统一生效优化原理线性过滤会平滑像素边缘造成国风像素瓦片模糊最近邻缩放保留锐利像素边界适配像素美术风格。6.4.2 导入江南瓦片图集本地文件管理器找到配套资源tileset_jiangnan.png直接拖拽至编辑器文件面板assets/tilesets/目录引擎自动完成图片导入。6.4.3 创建全局共用 TileSet 资源右键resources文件夹 → 新建资源 → 选择TileSet命名jiangnan_tileset.tres并保存双击打开 TileSet 编辑器右侧检查器设置基础瓦片尺寸Tile Size (32, 32)编辑器左下角「图块源」点击导入tileset_jiangnan.png图集弹窗提示「自动生成瓦片」时选择否图集包含 32/64/128px 多尺寸建筑、树木素材自动裁切会拆分完整模型手动框选拆分瓦片规范32px 小花、碎石单格框选右键生成瓦片64px 石板、矮墙按住 Shift 框选 2×2 网格128px 大树、房屋主体Shift 拖拽 4×4 网格区域全部瓦片裁切完成CtrlS保存 TileSet 资源。6.4.4 统一全局 2D 物理碰撞分层规范进入菜单项目 → 项目设置 → 常规 → 层名称 → 2D 物理固定分层命名全项目统一标准层号层名称适用实体对象1world地图墙体、树木、静态障碍物瓦片2player玩家主角本体3enemy敌方怪物实体4hitbox攻击伤害判定区域5item可拾取酒坛、道具交互体6.4.5 TileSet 瓦片绑定物理层双击打开jiangnan_tileset.tres进入 TileSet 编辑器右侧检查器找到Physics Layers点击Add Element新增物理配置物理层参数统一配置Layer自身所属层勾选world(层1)Mask可碰撞对象留空逻辑说明地图静态障碍物仅被动阻挡玩家、敌人无需主动检测其他实体遮罩保持空白节省性能。6.4.6 单瓦片碰撞形状绘制流程TileSet 编辑器左侧瓦片列表选中需要添加碰撞的目标瓦片底部切换至Paint绘制标签页下拉框选中刚创建的world物理层预览面板工具栏点击矩形碰撞快捷键F自动生成贴合瓦片尺寸的矩形碰撞体微调绿色碰撞框范围避开镂空空隙如窗户、门洞避免玩家被门框误阻挡其余障碍物瓦片重复以上流程统一配置。6.5 town.tscn 江南小镇分层绘制流程6.5.1 三层 TileMapLayer 绑定共用 TileSet打开level_town.tscn依次选中GroundLayer、DecoLayer、WallLayer检查器加载资源resources/jiangnan_tileset.tres三层共用同一套瓦片图集仅加载一次贴图杜绝重复加载造成显存浪费。6.5.2 TileMap 编辑器绘制工具在场景树中选中GroundLayer节点。此时编辑器底部的TileMap 编辑器会自动激活你会看到所有可用的瓦片排列在面板里。同时场景视口中会出现淡黄色的网格线——这就是我们的绘制画布。TileMap 编辑器工具栏中最常用的几个工具从左到右工具图标工具名称快捷键使用场景铅笔单格绘制B点缀零散装饰、修正局部瓦片矩形填充批量铺砖R绘制大片地面、墙体轮廓油漆桶泛洪填充G一键替换连通区域地面河道、草坪橡皮擦擦除瓦片E删除多余瓦片、清理门洞通路随机绘制随机铺放无快捷键地砖、草丛自然化消除重复单调感6.5.3 《酒魂》起始小镇的地图设计参考6.5.3 小镇地图整体规划首张探索地图回忆巷江南小镇尺寸20×15 瓦片格1280×960 像素区域布局示意┌─────────────────────────────────────────────┐ │ ███████████████ 北侧院墙 ███████████████ │ │ █ [草坪] █ 酒坊主建筑 █ [古井地标] █ │ │ █ █ 【主角初始出生点】 █ │ │ ███ [石桥] ███ 河道水域 ████ [柳树] ███ │ │ █ [主石板街道] 广场空地【战斗预留区】 [摊贩NPC点位] █ │ │ █ [茶肆] ██████████ [竹林景观] █ │ │ ███████████████ 南侧院墙 ███████████████ │ └─────────────────────────────────────────────┘设计提示对于第一张地图够玩比够美更重要。只要有清晰的可行走区域、可碰撞的障碍物以及一两个有辨识度的地标酒坊门口、古井、小桥就已经足够进入第七章的战斗开发了。地图的丰富和美化可以在所有系统开发完成后再回来打磨。6.5.4 分层绘制标准流程自下而上不可颠倒第一步GroundLayer 底层地面绘制选中GroundLayer矩形填充工具铺满整张地图青石板基础地面泛洪填充工具将河道区域替换水面瓦片建筑周边使用随机绘制混合 2-3 种相似地砖弱化大面积纯色单调感。第二步DecoLayer 中层装饰绘制草坪区域零散绘制草丛、落叶河道边缘放置柳树根部装饰瓦片石板道路随机散落落叶贴合秋季江南美术设定低矮灌木仅做视觉点缀不添加碰撞。第三步WallLayer 顶层障碍墙体绘制地图四周边缘绘制院墙锁定地图行走边界绘制酒坊、茶肆建筑墙体预留门口通行空位河道两侧放置太湖石、木桩障碍物地图角落绘制大树景观全部自带碰撞阻挡玩家穿行。绘制完成后按F5基础测试玩家可自由行走地面墙体、树木完全阻挡角色碰撞功能生效。遗留问题角色走到屏幕边缘无镜头限制画面露出黑色空白6.6 节摄像机模块解决。6.6 Camera2D 平滑跟随摄像机带地图边界锁定6.6.1 Camera2D 底层逻辑Godot2D 坐标系为无限平面Camera2D相当于屏幕可视窗口仅渲染摄像机范围内的游戏画面。将摄像机作为主角子节点可实现自动跟随但直接父子绑定会出现镜头抖动因此配置两大核心优化属性Position Smoothing位置平滑摄像机延迟缓慢追上角色消除移动抖动提升画面丝滑度Limit边界限制锁定摄像机移动最大范围镜头不会移出地图露出黑边。平滑速度参数适配表主角移动速度 200px/sSpeed 数值镜头表现适用场景3.0 ~ 5.0跟随延迟极高镜头大幅滞后角色剧情特写、镜头慢拉特效非日常探索6.0 ~ 10.0轻微滞后流畅无眩晕城镇日常探索项目标准推荐值 8.015.0 以上几乎同步跟随延迟极小高速战斗、追逐场景调试规范默认设置Speed8.0运行游戏沿地图四边行走测试拖拽感过重上调数值跟随生硬下调数值。6.6.2 Camera2D 节点创建与基础配置摄像机挂载至主角预制件保证所有关卡复用统一镜头逻辑打开Player.tscn主角场景选中根节点CharacterBody2D添加子节点Camera2D最终主角场景树形结构Player (CharacterBody2D) ├─ CollisionShape2D ├─ AnimatedSprite2D └─ Camera2D摄像机基础参数配置属性路径参数值Position Smoothing → Enabled勾选开启平滑Position Smoothing → Speed8.06.6.3 地图边界 Limit 两种实现方案方案一固定数值手动配置本章小镇专用当前小镇尺寸20×15 瓦片单格 64px总像素尺寸宽 20×6401280高 15×64960摄像机 Limit 固定参数Limit Left 0Limit Top 0Limit Right 640Limit Bottom 480设置完Limit之后无论玩家跑向哪里摄像机都会乖乖停在地图范围内永远不会让玩家看到地图边缘的黑边。方案二代码动态自适应边界预留扩展接口第 21 章数据驱动使用适配尺寸不统一的多关卡、程序化地图通过读取 TileMap 实际宽高自动更新摄像机边界本章仅预留函数暂不启用。6.6.4 主角物理碰撞层匹配校验保证玩家可与地图障碍物正常碰撞核对Player根节点物理参数Collision Layer自身归属层仅勾选player(层2)Collision Mask可碰撞检测层仅勾选world(层1)碰撞层的工作逻辑Collision Layer是我是什么Collision Mask是我能感知到什么。两个物体之间发生碰撞需要至少一方的Mask包含另一方的Layer。这种设计让你可以精细控制哪些东西之间会发生碰撞比如子弹只和敌人碰撞、不和友军碰撞——这在第7章开始编写战斗系统时会非常重要。6.7 GameManager 全局管理器关卡动态加载与内存释放打开第四章自动加载脚本scripts/game_manager.gd新增完整关卡切换逻辑游戏启动自动加载江南小镇切换关卡时调用queue_free()销毁旧场景瓦片、精灵贴图完整释放显存全程仅单张地图资源驻留内存。extends Node # ---- 游戏状态枚举 ---- enum GameState { EXPLORE, BATTLE, BREWING, DIALOGUE, PAUSED } # ---- 全局状态变量 ---- var current_state: GameState GameState.EXPLORE var player_drunk_level: float 0.0 # 全局统一醉意数值 # ---- 关卡加载相关资源与容器 ---- export var town_scene: PackedScene preload(res://scenes/levels/level_town.tscn) var active_level: Node2D null onready var level_container: Node get_node(/root/Main/World/CurrentLevel) # ---- 游戏初始化 ---- func _ready() - void: print([GameManager] 游戏启动当前状态: , GameState.keys()[current_state]) # 启动默认加载江南小镇主角出生坐标 switch_level(town_scene, Vector2(400, 450)) # ---- 关卡切换核心函数内存优化核心 ---- func switch_level(target_scene: PackedScene, spawn_pos: Vector2) - void: # 销毁正在运行的旧关卡释放全部贴图资源 if active_level ! null: active_level.queue_free() active_level null # 实例化新关卡挂载至空白容器 active_level target_scene.instantiate() level_container.add_child(active_level) # 重置主角出生位置 var player active_level.get_node_or_null(Player) if player: player.position spawn_pos var camera player.get_node_or_null(Camera2D) var ground_tilemap active_level.get_node_or_null(GroundLayer) # 动态边界函数预留多关卡适配 if camera and ground_tilemap and camera.has_method(setup_limits_from_tilemap): camera.setup_limits_from_tilemap(ground_tilemap) else: push_warning([GameManager] 当前关卡未找到Player节点出生坐标未赋值) # ---- 全局状态切换统一入口 ---- func change_state(new_state: GameState) - void: if current_state new_state: return print([GameManager] 状态切换, GameState.keys()[current_state], → , GameState.keys()[new_state]) current_state new_state match new_state: GameState.EXPLORE: get_tree().paused false GameState.PAUSED: get_tree().paused true GameState.BREWING, GameState.DIALOGUE: get_tree().paused true # UI弹窗时暂停世界UI节点需设置Ignore Pause GameState.BATTLE: pass # ARPG战斗模式不暂停探索世界加载内存逻辑说明_ready()游戏启动自动调用切换函数小镇场景实例挂载至CurrentLevel空白容器切换其他关卡时旧关卡整体执行queue_free()瓦片图集、角色精灵全部从内存、显存卸载游戏运行全程仅保留当前活动关卡资源杜绝多地图叠加占用内存。6.8 运行验收清单与常见故障排查按下F5运行游戏逐项核对验收标准全部通过代表本章功能完成主角可在地面、石板路、河道边缘自由行走无隐形阻挡院墙、房屋、大树、石桥可正常阻挡角色无穿墙 bug角色移动时摄像机平滑跟随无画面抖动、瞬移角色走到地图四角镜头锁定边界不会出现外侧黑边角色移动播放行走动画静止切换待机动画第五章预制件逻辑复用正常常见故障原因与修复方案故障现象根因解决方式角色直接穿墙WallLayer 瓦片未配置 world 碰撞或碰撞框缺失返回 TileSet 编辑器给墙体瓦片添加矩形碰撞绑定物理层 1摄像机完全不跟随角色Camera2D 未作为 Player 子节点打开 Player.tscn确认摄像机层级归属地图边缘出现大片黑边Limit 左右上下像素数值计算错误重新计算瓦片总像素格数 ×32更新摄像机边界参数瓦片缝隙出现白色细线图片导入启用线性过滤、未开启像素捕捉项目设置纹理过滤改为最近邻图集重新导入关闭抗锯齿草丛、落叶挡住玩家移动DecoLayer 装饰瓦片误添加碰撞TileSet 编辑器删除低矮装饰瓦片的碰撞外形6.9 地图预留交互点位适配后续全章节开发遵循「先占位、后开发」的工程规范绘制地图时提前预留交互节点后续章节无需重构地图第 7 章投壶战斗预留小镇中央广场预留 10×8 格无障碍物空地作为投壶对战、野外遭遇战场地第 18 章 NPC 系统预留在TownRoot/NPCs容器下创建空 Node2D 标记点位SpawnPoint_LiBai固定 NPC 生成坐标第 13 章酿造系统预留酒坊门口添加Area2D触发区域TriggerZone_Brewery矩形碰撞框覆盖门口后续绑定进出触发酿造 UI 逻辑。优势所有交互点位坐标固化后续新增功能仅补充脚本信号无需重新调整地图瓦片布局大幅减少重构工作量。6.10 本章产出与知识小结6.10.1 本章完整交付产物标准化通用jiangnan_tileset.tres瓦片资源配套分层碰撞配置三层隔离TileMapLayer江南小镇完整可运行地图带平滑跟随、边界锁定的复用型Camera2D主角摄像机解耦式全局main.tscn场景结构区分常驻容器与动态关卡迭代完成GameManager关卡动态加载销毁脚本解决内存溢出问题预占 NPC、战斗、酿造交互点位的完整关卡场景。6.10.2 核心知识点总结学习内容项目开发价值TileMapLayer 三层分层架构地面、装饰、障碍视觉与碰撞逻辑分离易维护、可扩展TileSet 统一瓦片与批量碰撞资源全局复用一次性配置全地图碰撞提升开发效率2D 物理分层规范精细化控制实体碰撞关系为战斗、拾取交互打下底层基础平滑边界摄像机提升探索沉浸感避免镜头溢出破坏世界完整性动态关卡加载释放机制控制内存占用支持多地图无缝切换防止游戏卡顿闪退提前预留交互点位开发习惯降低后期多系统整合的重构成本规范大型项目开发流程知之愈明则行之愈笃行之愈笃则知之益明。—— 朱熹本章核心跃迁从单一角色虚空移动搭建出分层、可扩展、内存可控的完整游戏世界。分层设计思维不仅适用于瓦片地图后续 UI 分层、渲染批次优化、战斗模块隔离均沿用同一开发思路。江南小镇探索舞台搭建完毕下一章开启核心战斗玩法开发。下一章预告第 7 章《投壶战斗蓄力、抛物线、命中判定》依托本章统一配置的 world/player/enemy 物理分层实现蓄力投壶弹道物理、碰撞命中判定正式接入游戏核心战斗模块。延伸阅读参考Godot 官方瓦片地图教程https://docs.godotengine.org/zh_CN/stable/tutorials/2d/using_tilemaps.html拓展学习地形自动拼接、瓦片动画等进阶 TileSet 功能Godot Camera2D 官方文档https://docs.godotengine.org/zh_CN/stable/classes/class_camera2d.html镜头震动、偏移、淡入淡出特效底层参数查阅《Pixel Art for Game Developers》像素美术开发书籍从零制作适配游戏项目的自定义瓦片图集。