TOS 开发周记 07:好友、聊天与更丰富的地图分层

本文根据 2025-11-17 至 2025-11-23 的 Git 提交整理,是一篇待审核的历史开发周记。

从“看见别人”到“和别人互动”

上一周解决了其他玩家的显示,这一周开始补人与人之间的操作。客户端增加发送好友请求和好友列表:API 客户端负责请求,独立的 Friends Panel 负责列表界面,玩家对象也接入了发起请求的入口。

聊天则同时推进前后端。后端在 MapRoom 一侧加入聊天服务与消息处理,客户端增加聊天面板,并通过 Colyseus 收发消息。随后,客户端把网络消息处理从 main.gd 拆到 colyseus_message_handler.gd。这个拆分很重要,因为位置、好友和聊天消息都在增长,主场景不应该长期承担所有协议分发。

地图生成开始分层

后端这一周重新推进地块生成脚本。Tile 增加新的字段与元素类型常量,生成逻辑支持更多 layer,城市也开始分配 Region ID。这里的“分层”是数据结构上的扩展:一个地块可以承载比单一地形更丰富的信息,为后续客户端叠加显示做准备。

为了观察生成结果,后端加入第二版几何调试地图和六边形地块调试页。道路、城市、区域与地块坐标之间牵涉多套数据,调试页面仍然是排查生成结果的重要工具。客户端也在同一周修正坐标处理,并加入城市、道路、山地、海面等素材,前后端都在围绕同一套地图语义收敛。

地图上的操作入口

客户端还增加了悬停弹窗与目标容器,并整理玩家碰撞逻辑。它们把地图元素从“可以看见”继续推向“可以选中、可以成为操作目标”。这些提交尚不足以证明完整战斗流程已经形成,但界面已经为目标型操作留下位置。

本周的主线因此很清楚:世界数据变得更丰富,地图上的人也不再只是移动的图标。好友、聊天、悬停与目标选择,让多人地图开始有了互动层。

提交记录

日期 仓库 提交 原始说明
2025-11-17 客户端 1b61944 feat: add hover modal
2025-11-17 客户端 29bea97 feat: add target container
2025-11-17 客户端 a370013 tidy: collision
2025-11-18 后端 1c6d786 feat: add debug map v2
2025-11-18 客户端 4ec4107 feat: send friend request
2025-11-18 客户端 fd375cd feat: friends list
2025-11-19 后端 38e3f7d feat: add genTiles script
2025-11-19 后端 7a28749 tmp
2025-11-19 后端 9173849 feat: add chat
2025-11-19 客户端 3eaf554 tidy
2025-11-19 客户端 5e1fb67 feat: add chat
2025-11-19 客户端 e509db4 tidy: seperate message handler from main.gd
2025-11-20 后端 2ea0271 tmp: center
2025-11-20 后端 dc52be4 feat: add loog
2025-11-20 后端 e41895a feat: debug tiles map
2025-11-20 后端 f38286e feat: add more layers for tiles
2025-11-20 后端 fbabdd7 feat: assign regionId for city
2025-11-20 客户端 12c9710 fix coordinates
2025-11-21 后端 4b6b7d2 feat: abstract enum for element type

TOS 开发周记 06:看见同一张地图上的其他玩家

本文根据 2025-11-10 至 2025-11-16 的 Git 提交整理,是一篇待审核的历史开发周记。

多人同步的第一轮闭环

这一周只有六个提交,主题却很集中:把房间连接真正落实到玩家位置同步。

后端先调整 MapRoom、MatchMakerRoom 和认证流程,开始同步玩家坐标;客户端随即根据房间状态创建并更新其他玩家。到这里,上一周搭好的登录、房间和角色场景第一次在同一条流程中相遇:本地玩家发送位置,房间维护状态,其他客户端再把这个状态还原成地图上的角色。

新加入的人也要看到旧玩家

实时同步不只处理“之后发生的移动”。当一名玩家晚些进入房间时,还需要得到已经在线玩家的位置。后端在周末增加向新连接推送现有用户位置的处理,客户端则修复重复创建其他玩家的问题。

这两个提交放在一起看很有代表性:补齐初始状态后,客户端如果没有区分“已有对象”和“新对象”,同一名玩家就可能被生成两次。历史里没有运行指标或完整测试结论,因此这里只能确认协议与对象管理都针对这一场景做了修改。

把 Player 逻辑从主场景拿出来

随着本地玩家和远端玩家共享越来越多行为,角色逻辑被正式移到 player.gd。后续整理又把一部分处理从 main.gd 转移过去。主场景负责组织世界和玩家集合,Player 脚本负责单个角色,这个边界比把移动、同步和显示全部堆在主脚本里更清楚。

本周没有扩展地图内容,也没有新增大块界面;六个提交都服务于同一个目标:让不同连接中的玩家可以在同一张地图上彼此出现。

提交记录

日期 仓库 提交 原始说明
2025-11-11 后端 9a0766a feat: sync playes location
2025-11-11 客户端 9a52972 feat: multi player
2025-11-16 后端 a151364 feat: push existing user location
2025-11-16 客户端 2af329d feat: split off player.gd from main.gd
2025-11-16 客户端 a203ab8 tidy
2025-11-16 客户端 af2ebd2 fix: dup other players

TOS 开发周记 05:从登录走向同一个房间

本文根据 2025-11-03 至 2025-11-09 的 Git 提交整理,是一篇待审核的历史开发周记。

先让玩家有身份

此前地图里的玩家只是客户端对象。这一周后端加入认证模块,并扩展 User 与 Fortress 数据;客户端同步增加登录流程,让玩家身份开始贯穿开始菜单、API 请求和游戏场景。后端还提供了按坐标查询 Region 的接口,登录与进入世界所需的用户、堡垒和所在区域数据逐渐连了起来。

同一阶段,后端把原有 Zone 命名统一为 Region。这个重命名波及实体、数据库初始化、地图、地块、怪物和堡垒路由,说明 Region 已经成为多条业务链路共享的概念。提交说明里的 regon 拼写也被原样保留在附录中,但正文统一使用 Region。

接上 Colyseus

客户端引入 Godot Colyseus 插件并建立 colyseus_client.gd,后端则扩展 MapRoom,并加入用于房间与匹配流程的测试页面和代码。到周末,客户端已经能显示房间信息,后端也修正了加入房间和在线人数统计相关逻辑。

这一周还没有完整的多人玩法,但网络层的职责开始清楚:常规账号、地图与堡垒数据通过 HTTP API 获取,房间里的实时状态交给 Colyseus。客户端最后把连接地址收敛为统一入口,减少不同模块各自拼接地址的情况。

社交与角色也开始成形

后端同时加入好友、队伍相关实体和路由,并补充 Swagger 描述;堡垒也有了独立路由。这里多数工作集中在服务端结构,客户端尚未在本周提供对应的完整好友与队伍界面,因此更适合把它看作后续社交功能的数据与接口准备。

客户端另一边加入玩家行走动画,并把 Player 场景从主场景中拆出。配合此前已经拆开的细节模式,main.gd 的职责继续缩小。TOS 到这一周第一次同时具备了“谁在玩”“进入哪个房间”“地图上显示哪个角色”这几个基础问题的实现线索。

提交记录

日期 仓库 提交 原始说明
2025-11-03 后端 068d35d feat: add /map/region/by-point
2025-11-03 后端 7539c1f feat: add auth module
2025-11-03 后端 9b7689f feat: update user, add fortress
2025-11-04 后端 2917c01 feat: add region id
2025-11-04 后端 54e8c0d feat: add team
2025-11-04 后端 88816cf feat: add swagger
2025-11-04 客户端 20dcc05 feat: add login
2025-11-04 客户端 6c0592d tidy: move detai logic to detail_mode.gd
2025-11-04 客户端 7b65392 feat: add addon
2025-11-04 客户端 bf15c93 fix
2025-11-05 客户端 465a7e8 tidy: clean code
2025-11-05 客户端 c1069d0 feat: add player animation
2025-11-06 后端 1a007b3 doc: update readme
2025-11-06 后端 397c50f refactor: unify names to regon
2025-11-06 后端 5da0cc3 fix: fix vibe coding with friends
2025-11-06 后端 7cbaea8 feat: add fortress router
2025-11-06 后端 d1b7cc6 fix:swagger
2025-11-06 客户端 13d380c feat: add addon
2025-11-06 客户端 a1371dc feat: use dev api host
2025-11-06 客户端 d2ec1f4 feat: add colyseus
2025-11-07 后端 5fc5ff7 test: add test
2025-11-08 后端 32daa27 fix: fix join and user counts
2025-11-08 客户端 a83a51f fix: show room info
2025-11-08 客户端 b388277 tidy: clean code
2025-11-09 客户端 46826c1 tmp
2025-11-09 客户端 690723d feat: spit off player
2025-11-09 客户端 a80b950 feat: use unified url

TOS 开发周记 04:城市、怪物与三层地图视角

本文根据 2025-10-27 至 2025-11-02 的 Git 提交整理,是一篇待审核的历史开发周记。

世界里终于有了内容

这一周的地图不再只有地形。后端增加 Monster 实体、怪物路由与 /nearby 查询,客户端随即加入敌人显示;城市也沿着相似的路线进入项目:后端补充城市导入和查询,客户端负责把城市画到地图上。前后端在这里形成了很清楚的配合——服务端提供世界对象和位置数据,Godot 客户端将它们变成地图中的可见元素。

为了支撑这些内容,后端还增加了导出完整几何数据的接口。客户端则把 API 调用从主脚本中拆到 api_client.gd,把坐标换算提取到独立工具脚本。随着对象增多,继续把所有逻辑留在 main.gd 已经越来越难维护,这轮拆分来得很及时。

从全国到省份,再到地块

客户端本周加入全国视图,随后又增加省份模式,并把细节模式从主脚本中拆出。三种观察尺度开始各自承担职责:全国层面看整体,省份层面看区域,细节层面承载地块、城市和敌人。

视图切换也暴露了真实问题,比如城市从全国视图切回后消失、游戏开始时玩家发生移动,以及缩放状态不一致。这些都在当周留下了针对性修正。地图模式不是三个孤立页面,它们共享世界数据、坐标和相机状态,所以切换过程本身也成了功能的一部分。

生成地形,也生成道路

资源层面加入了水面、沙地等地块素材,并尝试伪随机资源生成;城市之间的道路也开始自动绘制。与此同时,游戏配置和开始菜单被单独建立,地图参数不再只依赖开发中的控制面板。

这一周提交很多,也有多次重构。回头看,真正的进展不是单个素材或按钮,而是 TOS 从“六边形地图原型”向“有视角层级、有城市、有怪物的世界”迈出了一步。

提交记录

日期 仓库 提交 原始说明
2025-10-27 客户端 6aeddfd fix: fix brick show
2025-10-27 客户端 ecabec7 tidy: move files
2025-10-28 后端 122fbc5 feat: add /nearby
2025-10-28 后端 afa6673 feat: add monster
2025-10-28 后端 bf9b0d0 fix: fix outline
2025-10-28 后端 d958f2a feat: add monster
2025-10-28 客户端 237e692 feat: add enemy
2025-10-28 客户端 9135c51 tidy: split off files
2025-10-29 后端 e508783 feat: add city
2025-10-29 客户端 0272aeb feat: debug dynamic draw
2025-10-29 客户端 77c908d tidy: clean code
2025-10-29 客户端 b82898a feat: show cities
2025-10-30 后端 39bfa36 feat: export all geometrys
2025-10-30 客户端 0a6a929 feat: refactor dynamic load
2025-10-30 客户端 ad7e4c0 feat: add scale
2025-10-31 客户端 4ff3222 feat: add national view
2025-10-31 客户端 73a55e9 feat: debug positon infos
2025-10-31 客户端 85be803 tidy
2025-11-01 客户端 00d59ca feat: Pseudo-random resource generation
2025-11-01 客户端 7df0066 fix: elemet showing
2025-11-01 客户端 da0023e fix: fix citiy disappear after switch from national view
2025-11-01 客户端 e1fd066 feat: draw road automatically
2025-11-02 客户端 05cf290 feat: add game config
2025-11-02 客户端 0c46c4e feat: add start menu
2025-11-02 客户端 10114fa feat: fix player moving at the begin of game
2025-11-02 客户端 70509f5 feat: add province mode
2025-11-02 客户端 c91da7e feat: split off detila model from main
2025-11-02 客户端 e2a919c feat: place utils together, fix scale

TOS 开发周记 03:让地块有颜色、有边界,也有反馈

本文根据 2025-10-20 至 2025-10-26 的 Git 提交整理,是一篇待审核的历史开发周记。

地图从结构走向表现

前两周解决的是地图能不能生成、能不能在 Godot 中查看;这一周开始回答“不同地块看起来是什么样”。客户端先为地块加入颜色和悬停反馈,随后调整元素类型与更新逻辑。后端只有一笔针对调试地图默认值的修改,主要迭代仍发生在客户端。

悬停功能的意义很朴素:鼠标经过哪一格,界面就需要知道当前是哪一格。它让六边形地图从一幅静态图变成了可以被指向和识别的交互表面,也为以后显示地块信息留下入口。

修好六边形的接缝

六边形地块最容易暴露边缘问题。本周专门修正了 tile edge,并在随后删除一批旧逻辑、重整元素更新。提交差异显示,这不是简单换一个颜色值,而是在持续调整地块重绘与元素类型之间的关系。

接着加入的 shader 和材质资源负责地块的阴影表现;最后又把草地、砖块、树木等素材组织进自定义 TileSet。至此,颜色、纹理和阴影不再全挤在脚本里,地图表现开始落到 Godot 自己的材质与 TileSet 资源中。

给后续内容一套视觉语言

这一周没有新增复杂玩法,重点是让地图的信息更容易读懂:元素类型决定画什么,颜色与贴图表达差异,悬停提供交互反馈,边缘和阴影负责把每一格分清楚。地图仍然处于快速修改期,但已经不再只是调试用的六边形网格。

提交记录

日期 仓库 提交 原始说明
2025-10-20 后端 433307d feat: add default value
2025-10-20 客户端 972e3fb feat: add color
2025-10-21 客户端 22d0c56 feat: support hover
2025-10-21 客户端 26d7174 feat: fix update
2025-10-21 客户端 3e69f6b tmp: update elemet type
2025-10-21 客户端 b3ff0b1 tmp
2025-10-22 客户端 0e05f27 fix: fix tile edge
2025-10-22 客户端 64883b1 tidy
2025-10-22 客户端 e09f34b feat: update elememt with color
2025-10-23 客户端 6620f85 feat: add shadower
2025-10-23 客户端 7c5977f feat: customize tile.tres

TOS 开发周记 02:把六边形地图搬进 Godot

本文根据 2025-10-13 至 2025-10-19 的 Git 提交整理,是一篇待审核的历史开发周记。

客户端的第一块地图

上一周主要在后端和浏览器调试页里处理地图,这一周则全部转到 Godot 客户端。项目引入了六边形 TileMap 插件,并围绕 main.gd、主场景和控制面板快速搭出第一版地图查看器。

地图尺寸在第一天就经历了几轮扩大,随后又改成固定尺寸;这不是一条笔直的实现路线,更像是在实际画面中不断确认六边形铺设方式。到周中,每个地块的宽度可以从面板配置,原先散落的选项也被收进控制面板,地图参数终于有了比较集中的入口。

让大地图可操作

只把六边形铺出来还不够。客户端随后补上缩放、区域列表和 Sprite 展示,并清理了一轮集中在 main.gd 里的试验代码。区域列表让地图中的 Zone 不再只是内部数据,面板开始承担选择和观察世界的职责。

周末的动态加载是这一周最关键的方向变化:地图不再只按一次性、固定范围的思路处理,而是开始根据视野组织加载。最后的裁剪调整很小,却与动态地图的显示边界直接相关。

一边试,一边收拢

这一周有两个很大的临时提交,也有明显的删除和重构。回看历史,我更愿意保留这种真实感:客户端还在寻找合适的地图表现方式,但插件、主场景、控制面板与动态加载已经形成了基本分工。后面继续做元素、城市和玩家时,至少有了一张真正能承载它们的 Godot 地图。

提交记录

日期 仓库 提交 原始说明
2025-10-13 客户端 143b9f0 feat: add panel
2025-10-13 客户端 778dd3b tmp
2025-10-13 客户端 80641ba feat: zoom
2025-10-13 客户端 8507ae5 init with addons
2025-10-13 客户端 9c27182 feat: expand map again
2025-10-13 客户端 c83b2a5 feat: enlarge map
2025-10-13 客户端 cc7cfe3 tidy: clean code!
2025-10-13 客户端 eb3e6c9 feat: list zones
2025-10-15 客户端 bc823da feat: add sprite
2025-10-16 客户端 4141a0b tidy: fixed map size
2025-10-16 客户端 aedba32 feat: allow to configue width per tile
2025-10-18 客户端 5705a85 refactor: move options into panels
2025-10-18 客户端 e57b18e tmp: white
2025-10-19 客户端 315bc09 feat: dynamicly load
2025-10-19 客户端 b77183f feat: clip

TOS 开发周记 01:先把世界画出来

本文根据 2025-10-06 至 2025-10-12 的 Git 提交整理,是一篇待审核的历史开发周记。

从一张调试地图开始

这是现有 Git 历史里 TOS 的第一周。此时还没有客户端提交,主要工作都在后端:搭起服务、数据库连接和地图接口,再做一个可以直接观察结果的调试页面。

最初的两个 init 提交放进了 Zone 数据模型、地图路由与 MapRoom 等基础结构。随后增加的调试页,把原本只能从接口和数据库里查看的地图数据画到了浏览器中。对这个阶段来说,它不是附属工具,而是继续调整地图结构时最直接的反馈入口。

从区域到六边形地块

地图模型很快从区域继续细化到 Tile。add polygon 同时修改了 Tile、Zone、地图和地块路由,也扩展了调试页;换句话说,数据层增加六边形几何信息后,接口和可视化也一起跟了上来。

调试页在几次临时提交中反复调整:尝试缩放,随后撤掉一版缩放逻辑,再补上画布拖拽。最后一笔提交同时改动地图接口和页面,加入了直接绘制的能力。提交记录里能看出明显的试验轨迹,我没有把这些中间状态包装成已经定型的编辑器——这一周更准确的描述,是建立了一套可以看、可以拖、也可以尝试修改地图的原型工具。

留下可继续工作的起点

README 在同一周得到补充,项目的启动与使用信息不再只存在于代码中。至此,TOS 已经有了后端骨架、地图与地块数据,以及用于验证这些数据的可视化页面。功能仍然早期,但“世界数据—接口—调试视图”这条最初的链路已经出现。

提交记录

日期 仓库 提交 原始说明
2025-10-10 后端 461998f feat: init
2025-10-10 后端 5b18325 feat: init
2025-10-10 后端 a484c65 feat: add debug page
2025-10-11 后端 4b32e71 feat: add polygon
2025-10-11 后端 5f7bec8 doc: add readme
2025-10-11 后端 7f2393e tmp: support scale
2025-10-12 后端 1a23aac tidy: remove scale logic
2025-10-12 后端 aedf167 tmp
2025-10-12 后端 bc598e3 feat: allow dragging canvas
2025-10-12 后端 f974dba fetat: allow to draw

重装机兵重构4-功能篇

上一篇重装机兵重构2-地图篇的后续

Day24

  1. 感谢 Jax’s Studio 的无私奉献, 复用背包系统
    inventory

Day25

  1. 背包系统是使用 UGUI实现的, 利用RenderTexture 渲染在 UI Document里面

Day26

  1. RenderTexture 之后的背包不能点击, 使用UGUI重新画了一个tab, 然后把背包重新放到 UGUI里面

Day27

  1. 开源的背包物品交换有bug,直接购买了 Ultimate Inventory System, 竟然还赠送商店系统, 准备直接站在巨人肩膀上

Day28/29/30

  1. 研究Ultimate Inventory System

重装机兵重构3-剧情篇

上一篇重装机兵重构2-地图篇的后续

Day16

  1. 认识一些像素人物的生成工具;
  2. 增加持久化的能力,基于json 的文件的 SaveSystem的实现

Day17

  1. 认识 Unity 的 Timeline
  2. 认识 Timeline 的 Playable Track, Signal Track

Day18

  1. 利用 ProcessFrame 实现 Timeline 中两个 playableAsset 的暂停,等待用户输入才继续播放

  2. 认识 Timeline 的 Animation Track,实现人物移动 + 动画播放
    效果图

  3. 修复 PlayableDirector.Pause 会导致人物会回到初始地点的bug:

    1
    2
    //director.Pause();
    director.playableGraph.GetRootPlayable(0).Pause();

Day19

  1. 处理TextMeshPro的中文乱码:
    从windows-> TextMeshPro -> Font Asset Creator 创建没有效果
    从*.ttf -> creat ->TextMeshPro ->Font Asset -> SPF 这样可以
  2. 简单过场黑屏动画 天亮了 使用 UI -> Panel + UI -> TextMeshPro 实现
  3. 封装打字机,过场动画 天亮了 也可以使用

Day20

  1. 解决Visual Studio 的中文在Unity 乱码的问题:
    在VS里面安装 扩展 -> 管理扩展, 搜索Force UTF-8(No BOM) 2022
  2. 增加逻辑, 开始游戏 -> 三选一角色 + 用户名称 -> 存档
  3. 增加逻辑, 记载游戏 -> 选择存档 -> 进入游戏

Day21

  1. 实现gameObject 在 UI Document 的展示(通过将GameObject 渲染到 RenderTexture)

  2. 实现Player的prefab, 根据职业选择渲染不同的人物

Day22

  1. 根据玩家选择的职业, 动态更新Timeline中animation track的人物的gameobject, 以及更新animation track 中的 animation clip

    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
    private void UpdateAnimationTrack(PlayableDirector director)
    {
    var playerObject = GameObject.FindWithTag("Player");
    Animator playerAnimator = playerObject.GetComponentInChildren<Animator>();
    Debug.Log("playerAnimator is " + playerAnimator);
    foreach (var track in director.playableAsset.outputs)
    {
    if (track.streamName == "PlayerPosition" || track.streamName == "PlayerAnimation")
    {
    if (track.streamName == "PlayerAnimation")
    {
    UpdateAnimationClip(track, playerAnimator);
    }
    director.SetGenericBinding(track.sourceObject, playerAnimator);
    }
    }
    }
    private void UpdateAnimationClip(PlayableBinding track, Animator animator)
    {
    if (track.sourceObject is AnimationTrack animationTrack)
    {
    // 获取该 Track 上的所有绑定(这里是 Animation Clip)
    foreach (var clip in animationTrack.GetClips())
    {
    // 替换现有的 Animation Clip
    foreach (var newClip in animator.runtimeAnimatorController.animationClips)
    {
    if (clip.displayName == newClip.name)
    {
    clip.asset = newClip; // 将新的 Animation Clip 赋值给 Track
    break;
    }
    }
    }
    }

    }

  2. 增加postTimeline 脚本, 实现剧情中人物的销毁和常规人物的位置重置

  3. 在camera上面增加audio source, *.m4a 需要转成 *.wav

    1
    ffmpeg -i input.m4a output.wav

Day23

  1. 打字机支持加速(左键跳过)
  2. 利用UI Docuemnt 实现tab
    tabs

Day24

  1. 实现menu 的隐藏出现,menu里面包含退出,实现游戏的开始->退出->开始的闭环:
    注意UIDocument 的隐藏不要使用SetActive(false), 这样会导致按钮的事件丢失

至此, 素材篇基本结束了,后面进入功能篇

重装机兵重构2-地图篇

上一篇重装机兵重构的后续

Day12

  1. 认识Aseprite, 一个Animated Sprite Editor & Pixel Art Tool。这个工具可以快速把真实的画转成像素风
  2. 认识Tiled, 比Unity自带的Palette 多了一个地形集,画地图更方便
  3. 认识SuperTiled2Unity 可以把Tiled 的地图放入unity里面
  4. 把多个Aseprite 合并成一个
    1
    2
    3
    4
    C:\Users\amanoooo\Desktop\Aseprite\aseprite --batch building-2.aseprite building3.aseprite building4.aseprite building5.aseprite building6.aseprite building7.aseprite building8.aseprite --sheet buildings.png


    C:\Users\amanoooo\Desktop\Aseprite\aseprite --batch cliff1.aseprite cliff2.aseprite cliff3.aseprite garbge.aseprite grass.aseprite grass2.aseprite hedge.aseprite hedge2.aseprite hedge-left.aseprite shizijia.aseprite stone.aseprite tree1.aseprite water1.aseprite sand.aseprite 三个点.aseprite 宝箱1.aseprite 地板1.aseprite 地板2.aseprite 地板3.aseprite 工作台.aseprite 平台.aseprite 油桶1.aseprite 油桶2.aseprite --sheet envs.png```

Day13

  1. 在Tiled 上面增加对象层,增加对象, 然后unity 使用脚本实现把对象改成IsTrigger ,实现门碰撞逻辑, 通过SuperCustomProperties 来获取门的名称, 实现不同门的跳转. 脚本如下

DoorManager 放在空对象上

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
using UnityEngine;
using SuperTiled2Unity;

public class DoorManager : MonoBehaviour
{
void Start()
{
// 获取所有的 SuperObject 组件

// 使用 FindObjectsByType 替代 FindObjectsOfType
SuperObject[] superObjects = FindObjectsByType<SuperObject>(FindObjectsSortMode.None);


foreach (SuperObject superObject in superObjects)
{
// 检查对象是否有碰撞器
Collider2D collider = superObject.GetComponent<Collider2D>();
if (collider != null)
{
// 动态添加 TriggerDoorNamePrinter 脚本
superObject.gameObject.AddComponent<DoorHandler>();
}
}
}
}

DoorHandler

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
using UnityEngine;
using SuperTiled2Unity;

[RequireComponent(typeof(Collider2D))] // 确保对象有 Collider2D 组件
public class DoorHandler : MonoBehaviour
{
private SuperCustomProperties customProperties;

void Start()
{
// 获取 SuperCustomProperties 组件
customProperties = GetComponent<SuperCustomProperties>();

// 确保碰撞器是触发器
Collider2D collider = GetComponent<Collider2D>();
if (collider != null)
{
collider.isTrigger = true;
}

// 打印 doorName 的值
PrintDoorName();
}

void OnTriggerEnter2D(Collider2D other)
{
// 当触发器被触发时打印 doorName
PrintDoorName();
}

private void PrintDoorName()
{
if (customProperties != null)
{
// 检查是否有 doorName 属性
if (customProperties.TryGetCustomProperty("doorName", out CustomProperty property))
{
Debug.Log($"Door Name: {property.m_Value}", this);
}
else
{
Debug.LogWarning("No doorName property found on this object.", this);
}
}
else
{
Debug.LogWarning("No SuperCustomProperties component found on this object.", this);
}
}
}

Day14

  1. 实现不同场景人物的初始化位置:
    在Awake 里面查找自定属性 IsEntry
  2. fix迭代更新tmx对应的tsx对应的png(补充素材),出现的问题是unity的地图素材乱了,和tiled的不一致,通过更新tsx的高度修正bug

Day15

  1. 认识Tiled editor 增加类
  2. 实现多场景多入口的切换:
    进入IsDoor 的时候, 检查DoorName(场景名) ,检查 DoorIndex, 然后切换到对应的 Scene,查找EntryIndex==DoorIndex的 IsEntry 的gameObject
  3. 临时处理人物位移 x+0.5, y-0.5

Day16

  1. 处理bug:
    老版本的MetalMax有个逻辑是,场景A->B通过楼梯, B->A也是通过楼梯, 要求是到B的初始化状态是在楼梯上, 这时候按照我们的写法, B会因为OnTriggerEnter2D 立即切换到A, 解决方案是在 IsEntry 上面也增加一个Trigger, 只有 OnTriggerExit2D 才能是IsDoor 的trigger启用
    代码如下:
    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
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    using UnityEngine;
    using SuperTiled2Unity;
    using static UnityEngine.EventSystems.EventTrigger;

    [RequireComponent(typeof(Collider2D))] // 确保对象有 Collider2D 组件
    public class DoorHandler : MonoBehaviour
    {
    private SuperCustomProperties customProperties;
    private bool disableByEntry = false;


    void Awake()
    {
    // 获取 SuperCustomProperties 组件
    customProperties = GetComponent<SuperCustomProperties>();

    var isDoor = IsDoor();
    if (IsDoor())
    {
    // 确保碰撞器是触发器
    Collider2D collider = GetComponent<Collider2D>();
    if (collider != null)
    {
    collider.isTrigger = true;
    }
    }

    // 检查是否是入口门
    if (IsEntry())
    {

    Collider2D collider = GetComponent<Collider2D>();
    if (!isDoor)
    {
    collider.enabled = false;
    }

    if (GetEntryIndex() == SceneLoader.Instance.DoorIndex)
    {
    if (isDoor)
    {
    disableByEntry = true;
    }
    MovePlayerToThisDoor();
    }
    }
    }

    private void Update()
    {
    }


    void OnTriggerEnter2D(Collider2D other)
    {
    if (!disableByEntry)
    {
    SceneLoader.Instance.SwitchScene($"mm/scenes/{GetDoorName()}");
    } else
    {
    Debug.Log("ignore scene loader due to disableByEntry: " + disableByEntry);
    }
    }
    void OnTriggerExit2D(Collider2D other)
    {
    disableByEntry = false;
    }

    private int GetDoorIndex()
    {
    if (customProperties.TryGetCustomProperty("DoorIndex", out CustomProperty DoorIndex))
    {
    if (DoorIndex != null)
    {
    return DoorIndex.m_Value.ToInt();
    }
    }
    return 0;
    }

    private int GetEntryIndex()
    {
    if (customProperties.TryGetCustomProperty("EntryIndex", out CustomProperty Index))
    {
    if (Index != null)
    {
    return Index.m_Value.ToInt();
    }
    }
    return 0;
    }

    private string GetDoorName()
    {
    if (customProperties != null)
    {
    // 检查是否有 doorName 属性
    if (customProperties.TryGetCustomProperty("DoorName", out CustomProperty DoorName))
    {
    var DoorIndex = GetDoorIndex();
    Debug.Log($"Door Name: {DoorName.m_Value} Index:{DoorIndex}", this);
    SceneLoader.Instance.DoorIndex = DoorIndex;
    return DoorName.m_Value;
    }
    else
    {
    Debug.LogWarning("No doorName property found on this object.", this);
    return null;
    }
    }
    else
    {
    Debug.LogWarning("No SuperCustomProperties component found on this object.", this);
    return null;
    }
    }

    private bool IsEntry()
    {
    if (customProperties != null)
    {
    // 检查是否有 IsEntry 属性
    if (customProperties.TryGetCustomProperty("IsEntry", out CustomProperty property))
    {
    return property.m_Value == "true";
    }
    }
    return false;
    }

    private bool IsDoor()
    {
    if (customProperties != null)
    {
    // 检查是否有 IsEntry 属性
    if (customProperties.TryGetCustomProperty("IsDoor", out CustomProperty property))
    {
    return property.m_Value == "true";
    }
    }
    return false;
    }

    private void MovePlayerToThisDoor()
    {
    // 查找玩家对象
    GameObject player = GameObject.FindGameObjectWithTag("Player");
    Debug.Log($"player is {player}");
    if (player != null)
    {
    Vector3 alignedPosition = new Vector3(
    transform.position.x + 0.5f,
    transform.position.y - 0.5f,
    transform.position.z);
    // 将玩家移动到门的位置
    player.transform.position = alignedPosition;
    }
    else
    {
    Debug.LogWarning("Player not found in the scene.", this);
    }
    }
    }

效果

至此, 素材篇基本结束了,后面进入剧情篇