4270 字
21 分钟

My ARPG:Unity 2D ARPG 原型

项目仓库: Gitee | GitHub 在线试玩:ARPG WebGL Demo

My ARPG 基于 Unity 2022.3.62f3c1,覆盖场景切换、状态恢复、条件对话、任务推进、商店交互、UI 管理和存档系统。资源加载用 Addressables,输入用 Input System,模块通信用 ScriptableObject 事件;已完成移动端虚拟输入适配,支持安卓构建并部署实体机。

设计思路#

工程按职责分层:场景层负责持久场景和地图切换;状态层负责可持久化对象的注册与数据分发;导航层提供寻路网格、寻路算法与路径消费;交互层承载对话、任务、商店、背包与 NPC 行为;存档层负责序列化和文件 I/O。

系统间通过 ScriptableObject 事件通道解耦——广播方 Raise 事件,接收方订阅回调,统一约定在启用时订阅、停用时注销。存档、任务奖励、场景加载、UI 切换等跨系统操作都走事件传递,避免直接引用。跨模块的级联通知(敌人死亡、升级、技能满级)用 C# 静态事件,模块内部用序列化字段直接引用,全局数据经泛型单例基类取用。数据分两类:ScriptableObject 只放静态资产数据(对话、任务、场景、事件定义),运行时状态放在专门的运行时类里,不写回共享资产,避免多份引用共享同一份被篡改的数据。

对话基于树状对话图:每个节点包含对话行和子选项,形成对话树;条件分支通过拒绝对话数据实现,对话开始前检查前置条件(角色是否对话过、物品是否拾取够数量),条件不满足时展示拒绝对话;一次性对话和历史记录由历史管理器统一维护。

任务基于状态机:未接受 → 已接受 → 可完成 → 已完成,带拒绝分支可回退到已接受。目标类型支持物品拾取数量检查和角色对话检查,目标达成后自动推进状态,完成任务通过事件系统发放奖励到背包。

寻路采用三层解耦架构:网格数据层从 Tilemap 和静态碰撞体自动构建节点地图,支持可步行/障碍标记和世界坐标↔网格坐标转换;寻路算法层做标准 A* 搜索,支持 8 方向移动和对角线通行检查;路径消费层挂在移动对象上推进节点,内置重寻路机制——目标移动超过阈值时自动重建路径,新旧路径比较后决定是否替换。NPC 和敌人只需挂载移动控制器即可获得寻路能力。

存档采用注册表模式:所有可持久化对象实现统一存/读接口,激活时自动注册到存档管理器,场景切换时统一调用所有已注册对象的存/读方法;序列化和文件 I/O 单独成层,支持手动存档与自动系统档分离,删除前校验路径不超出数据目录,加载时自动跳过损坏存档并回退到最近的完整档。

模块详解#

全项目 108 个脚本分布在 24 个目录,其中相当一部分是数据资产定义和薄按钮封装,这一节只展开有设计内容的模块。

场景框架(Scene,8 个)#

场景切换器订阅加载请求事件,时序为:禁用玩家输入、暂停游戏、回血、淡入、Addressables 卸载旧场景、叠加式加载新场景、摆放玩家位置、淡出、广播加载完成事件、恢复输入与时间。入口场景只放一个引导脚本拉起持久场景,所有常驻系统在持久场景里,游戏场景叠加加载。重试管理用全局配置表按当前场景匹配出生点。相机配套三个脚本:切换后按标签找新场景的相机边界碰撞体;宽高比变化时按宽度优先重算正交尺寸,处理异形屏;渲染前修正相机矩阵做像素对齐,不修改 Transform,避免与 Cinemachine 冲突。传送门在触发时发一条场景加载请求。

玩家(Player,13 个)#

玩家状态机有待机、奔跑、攻击、射击、击退五个状态,转换判定与执行分开写。剑士和弓箭手是同一角色的两种武器形态,用互斥的激活标记加动画分层权重切换,带冷却。两种武器共用一个 Animator,动画事件由一个路由脚本按当前激活武器转发给对应组件。攻击期间锁输入,动画结束时经事件解锁,近战结束、射击结束、切换装备三个来源共用一个解锁处理。数值中心单例持有玩家全部数值字段,修改时同步刷新界面;经验系统订阅敌人死亡事件获得经验,升级时广播事件,技能树收到后发技能点。时间管理器用计数式暂停,处理嵌套的暂停与恢复。

技能树接在成长线上:技能槽绑定的资产只有名称、等级上限和图标;前置技能必须点满才解锁后继,解锁由满级事件触发、管理器遍历全树完成;技能可以直接解锁武器,剑术技能就是激活近战组件;技能点来自升级事件。

寻路(A*,5 个)#

三层结构。网格层最先初始化,从瓦片地图按层名分类生成节点字典,再扫描静态碰撞体补充障碍,支持把一个瓦片细分成多个寻路格。算法层做标准 A* 搜索:八方向移动,斜走时禁止穿角(两个正交邻格必须至少一个可走),距离用近似值代替开根号。消费层挂在每个 AI 单位上管理路径:目标移动超过阈值且冷却结束才重寻路,新路径更优才替换。路径落点检查八邻格,靠墙时朝反方向偏移,避免擦墙卡碰撞体;起点另做直线可达预检。敌人追击、NPC 巡逻、NPC 游荡三种行为调用同一套接口。

单位(Units,9 个)#

敌人按组件组合,没有中心状态机类:移动组件持有待机、追击、攻击、击退状态机,周期检测玩家,进入范围后经寻路追击;战斗组件由动画帧事件触发,圆形范围检测玩家层;血量组件实现受击接口,受击时扣血并击退,死亡时广播带经验奖励的静态事件后销毁;击退单独一个组件。NPC 用启停式状态机,总控通过启用/停用同物体上的三个行为组件切换状态:游荡以出生点为圆心随机取点,取到不可达点重新随机;巡逻按矩形四角循环走;玩家进入触发器范围时切换到对话态。店主没有移动逻辑,玩家进范围时向商店管理器注册三个货架数据和头像摄像机目标。

对话与历史(Dialog,4 个)#

对话树是数据资产的自引用节点图:节点持有多句台词(可换说话人)和若干选项,选项指向下一个节点,无后续节点即结束。拒绝对话是对话节点的子类,带进入条件:默认对话标记实现”主对话只进行一次”;前置角色和前置物品列表实现条件门槛。开始对话前先遍历拒绝列表,条件不满足时播放拒绝台词,不进入主对话。判定数据来自三个历史管理器:对话历史记录对话过的角色和完成的对话;物品拾取历史累计拾取总量,出售和使用不减记;位置历史未实现,是预留的空类。历史同时是对话条件和任务进度的数据来源。

任务(Quest,6 个)#

任务资产由目标列表和奖励列表组成,目标按填写的字段区分类型:收集物品、与角色对话、到达地点。运行时进度存在管理器字典里,不写回资产。状态机为待机、已接受(可拒绝回退)、可完成、已完成,状态切换是唯一入口,同时切换操作按钮组的显隐。目标进度在每次刷新面板时从历史管理器重新计算,全部达标后自动升为可完成;点击完成时校验目标,通过后逐条奖励发背包更新请求事件。任务板是场景触发器,玩家进范围时把任务列表推给管理器并打开面板。任务日志界面显示目标进度,完成的任务条目置灰。

商店与背包(10 个脚本)#

店主持有物品、武器、护甲三个货架数据,子页签切换重填货架;悬浮信息面板跟随鼠标显示物品属性;商店有一个头像摄像机,逐帧跟踪当前店主的动画位置。购买和出售共用同一条背包更新通道:购买发正价格加正数量请求,出售发负价格加负数量请求,商店与背包之间没有直接引用。

背包是热键栏和背包两区槽位,堆叠在槽位内处理。入包统一走一个入口:金币直接加钱,经验直接加经验,负数请求从被点击的槽位扣除(出售),正数请求先堆叠同类再找空槽,装不下的部分在玩家脚下生成掉落物。金币和经验在数据上也定义为物品,用标记区分。物品效果按属性字段加成,带持续时间的临时效果由协程计时,到时反向还原。交互是点击:左键在商店打开时出售、否则使用,右键丢弃。掉落物实现存档接口,按 GUID 记录位置和拾取状态,是目前唯一持久化的场景物体。

存档(Save,6 个)#

三层分工。注册层:可存档物体挂标记组件,检视器校验时自动生成 GUID,接口默认方法把自己登记进数据管理器的注册表。聚合层:数据管理器在收到场景加载请求时遍历注册表收集数据(自动存档),场景加载完成后分发数据(自动读档);手动存档由玩家操作触发,走同一个数据准备入口。序列化层:JSON 序列化写入持久化目录,文件名带毫秒级时间戳;删除前校验路径不越出数据目录;读档时坏档自动回退到最新的可用档。数据容器目前存掉落物状态、场景与玩家位置、玩家数值;任务和背包的持久化尚未实现。

UI 框架(画布调度)#

所有面板走同一套调度,公共的开关与回报逻辑放在画布契约接口的默认实现里,各画布管理器不写公共基类。调度分三层:UI 管理器合并按键输入和外部代码请求两类输入源,每帧统一裁决,ESC 优先——没有面板打开时打开 ESC 菜单,有面板时关闭栈顶;中间是纯 C# 的焦点栈,不依赖 Unity 生命周期,链表记录打开顺序,关闭后焦点回落,按层深计算画布排序值,单独拆分是为了能脱离引擎做单元测试;各画布管理器订阅开关事件执行开关,再回报状态给焦点栈。开关与焦点是两个独立事件。场景切换前统一重置所有面板。左下角的集成菜单把配置的画布组生成分页按钮,作为面板统一入口。移动端输入(虚拟摇杆、攻击按钮)也在这一层,摇杆按平台控制显隐。

流程#

场景流程:切换请求事件 → 场景切换器异步加载 → 加载完成回调。传送门、菜单、碰撞脚本只发切换请求,坐标写入新位置,缺失时回退到场景数据资产的初始位置。

状态流程:存档/读档事件 → 存档管理器遍历注册表 → 把数据分发给各对象。对象侧实现统一接口,用稳定标识定位,掉落物拾取状态由数据字典恢复。

寻路流程:请求路径 → 网格坐标转换 → A* 搜索 → 对角线通行检查 → 消费节点推进。路径起点做直线可达性优化,直达最优起点;目标移动超阈值时重寻路并比较新旧路径。

对话流程:NPC 交互 → 对话管理器 → 条件检查(读对话历史、物品历史)→ 对话树推进/分支选择。角色条件读对话历史,物品条件读拾取历史,一次性对话读节点自身的触发标记。

任务流程:任务管理器直接读历史数据更新目标进度,状态机推进任务状态;奖励通过事件写入库存数据,走事件进入背包更新流程。

商店流程:商店加载事件 → 商店管理器陈列商品。买卖都经过库存更新请求,出售时价格和数量取负值,共用同一条库存更新链路。

UI 流程:输入检测 → 切换事件 → 画布组开关,切场景时重置画布清除残留。集成菜单通过输入状态管理面板入口,部分菜单需满足触发条件(接近 NPC 或任务板)才可打开。面板支持”X”关闭、拖动顶部移动、最近操作的面板显示在最上层。

存档流程:游戏场景内提供保存/读取按钮,菜单场景内保存切换为删除。存档逻辑通过存档管理器的统一接口与 UI 层连接。

边界#

  • 场景切换:切换逻辑集中处理,外层对象只保留请求入口
  • 状态保存:注册表统一管理,对象侧只处理序列化字段
  • 寻路:导航数据共享,移动对象不持有网格生成逻辑
  • 对话:对话管理器依赖历史数据,不依赖移动逻辑;条件判断与对话内容在同一数据对象内,不依赖外部枚举匹配
  • 任务:任务管理器依赖历史系统,不维护对话状态
  • UI:UI 管理器处理面板开关和存档画布组,不处理任务、商店、对话数据;输入状态管理负责输入状态,不持有业务数据
  • NPC:本体不持有对话 UI、商店 UI、任务 UI

限制#

当前版本覆盖场景切换、状态恢复、条件对话、任务统计、商店、面板互斥、集成菜单和存档系统。背包系统已实装,支持物品使用、出售和丢弃。移动端虚拟摇杆与攻击按键已实装,安卓 APK 可构建并部署到实体机。Settings 菜单、敌人状态持久化和地图交互对象仍在补全。

操作指南#

移动#

WASD 控制角色移动;移动端改用虚拟摇杆移动,配合屏幕攻击按键操作。

战斗#

按键功能
Q切换远程(弓箭手)/ 近战(剑士)
J弓箭手射箭
K剑士挥砍

剑士模式下每次挥砍使面板伤害 +1(血怒机制)。

面板交互#

左下角集成菜单统一管理所有面板入口。能力面板和技能面板可直接打开,其余需要触发条件——比如走到 NPC 或任务板附近。

面板支持”X”关闭、拖动顶部移动,最近操作的面板显示在最上层。

按键 / 入口功能
能力面板集成菜单打开
技能面板集成菜单打开,左键槽位消耗技能点解锁
F与商店交互
T打开 / 关闭 NPC 对话,左键推进、选择
C任务菜单
ESC退出菜单——返回菜单、存档、退出游戏

死亡自动弹出 GameOver 菜单。

物品栏#

操作效果
左键(普通)使用物品
左键(商店打开时)出售物品
右键丢弃物品

丢弃的物品在场景重载时不保留(包括场景切换和重试)。

存档#

游戏场景内点击 Save / Load 存档和读档。菜单场景内 Save 变为 Delete,可删除存档。

场景切换#

木桥走到尽头切换场景。


配套开发日志:My ARPG 开发日志

My ARPG:Unity 2D ARPG 原型
https://www.yonagi.world/posts/unity-arpg-project-intro/
作者
YONAGI
发布于
2026-06-02
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

封面
遠い日に想いを馳せて
Laplacian
封面
遠い日に想いを馳せて
Laplacian
0:00 / 0:00