2026 年 7 月 28 日 Unity RPG 入门项目
核心知识点:角色上下坡持续移动 Bug 修复、跟随相机两种实现(固定偏移 / 平滑 Lerp)、鼠标滚轮缩放、右键环绕旋转、相机俯仰角度限制、NPC 任务弹窗、DoTween 界面滑入动画、射线 UI 穿透问题、全屏遮罩解决方案、任务数据配置化、脚本单一职责开发规范
一、角色上下坡持续移动 Bug 修复
1 概念
平地移动正常,上坡 / 下坡时人物会无限来回抖动、无法停在目标点,根源是向量距离计算包含 Y 轴高度差,高度差持续大于停止阈值,循环触发移动逻辑。
2 特性 / 规则
-
原始
Vector3.Distance包含 X/Y/Z 三轴,高低地形产生永久差值; -
新增布尔标记
isMoving记录移动状态,仅鼠标新点击时更新目标坐标; -
计算距离时只取水平面 XZ 坐标,剔除 Y 轴高度干扰;
-
距离小于 0.3 时关闭移动状态,停止 Lerp 插值。
3 语法 & 代码片段
private Vector3 targetPos;
private bool isMoving;
void Update(){
if(Input.GetMouseButtonDown(0)){
// 射线获取地面点,仅更新XZ,Y保留自身高度
targetPos = new Vector(hit.point.x, transform.position.y, hit.point.z);
isMoving = true;
}
if(isMoving){
// 仅计算水平距离,消除高低坡Y轴误差
float dis = Vector2.Distance(
new Vector2(transform.position.x, transform.position.z),
new Vector2(targetPos.x, targetPos.z)
);
if(dis > 0.3f){
transform.position = Vector3.Lerp(transform.position, targetPos, moveSpeed * Time.deltaTime);
}
else{
isMoving = false;
}
}
}
4 相关知识点对比
三轴 Distance 计算 VS 仅 XZ 平面距离计算
三轴:高低地形自带高度差值,人物永远达不到停止距离,无限抖动; XZ 平面:只判断地面水平间距,不受坡度高度影响,平地 / 斜坡均可正常停下。
5 模块拓展
可增加移动缓冲阈值,优化人物急停手感。
二、主角跟随相机两种实现方案
1 概念
相机固定绑定玩家相对偏移,分为直接赋值跟随、Lerp 平滑缓动跟随两种写法,适配不同游戏手感需求。
2 特性 / 规则
-
固定偏移方案:每一帧直接对齐偏移,相机紧贴人物无滞后;
-
Lerp 平滑方案:插值缓慢追赶人物,画面有轻微拖拽缓冲,影视感更强;
-
偏移向量在 Start 一次性缓存,避免每帧重复计算坐标差。
3 语法 & 代码片段
public Transform player;
private Vector3 offset;
public float smoothSpeed;
void Start(){
offset = transform.position - player.position;
}
// 方案1 无平滑直接跟随
transform.position = player.position + offset;
// 方案2 Lerp平滑跟随
Vector3 targetCamPos = player.position + offset;
transform.position = Vector3.Lerp(transform.position, targetCamPos, smoothSpeed * Time.deltaTime);
transform.LookAt(player);
4 相关知识点对比
直接坐标赋值 VS Lerp 插值跟随
直接赋值:零延迟,操作反馈即时,适合竞技类游戏; Lerp 缓动:轻微拖拽缓冲,画面更柔和,RPG、单机剧情项目常用。
5 模块拓展
可增加相机震动、受伤抖动效果。
三、鼠标滚轮缩放相机远近
1 概念
读取鼠标滚轮增量,修改相机与玩家偏移向量长度,搭配Mathf.Clamp限制最大 / 最小距离,防止相机贴脸或过远。
2 特性 / 规则
-
原始偏移向量归一化,仅修改长度,保持相对角度不变;
-
滚轮数值正负区分拉近、拉远;
-
距离区间锁定,超出边界不再缩放。
3 语法 & 代码片段
float scrollDelta = Input.GetAxis("Mouse ScrollWheel");
float curDis = offset.magnitude;
curDis -= scrollDelta * zoomSpeed;
curDis = Mathf.Clamp(curDis, 3f, 18f);
offset = offset.normalized * curDis;
4 相关知识点对比
无边界缩放 VS Clamp 区间限制
无边界:滚轮持续拉近会穿模贴人物,拉远后角色过小看不清; Clamp 锁定:固定视野范围,游戏画面表现稳定。
5 模块拓展
可根据距离动态调整平滑系数,近距离缩放更细腻。
四、鼠标右键环绕旋转相机
1 概念
按住鼠标右键拖动,相机以玩家为圆心水平 360° 环绕,通过Transform.RotateAround实现绕轴旋转。
2 特性 / 规则
-
仅按住右键时读取鼠标水平轴增量,避免左键移动冲突;
-
RotateAround 第一个参数为旋转中心点(主角坐标);
-
水平旋转只绕 Y 轴,不改变俯仰角度。
3 语法 & 代码片段
if(Input.GetMouseButton(1)){
float rotateX = Input.GetAxis("Mouse X") * rotateSpeed;
transform.RotateAround(player.position, Vector3.up, rotateX);
offset = transform.position - player.position;
}
4 相关知识点对比
手动修改欧拉角 VS RotateAround 接口
手动赋值:需要自行计算偏移坐标,代码繁琐; RotateAround:引擎内置环绕 API,一行实现圆心旋转。
5 模块拓展
分离水平、垂直旋转逻辑,拆分上下俯仰控制。
五、相机上下俯仰 + 角度限制
1 概念
右键拖动鼠标垂直方向控制相机抬头 / 低头,读取欧拉 X 角度,设置上下极限值防止相机完全翻转、俯视脚底。
2 特性 / 规则
-
向上俯仰上限约 80°,向下下限约 7°;
-
每次旋转后读取当前 X 欧拉角,超出阈值直接锁死角度;
-
仅修改垂直旋转,水平环绕逻辑不受影响。
3 语法 & 代码片段
if(Input.GetMouseButton(1)){
float rotateY = Input.GetAxis("Mouse Y") * rotateSpeed;
transform.RotateAround(player.position, transform.right, -rotateY);
// 读取当前俯仰角度限制
Vector3 euler = transform.eulerAngles;
if(euler.x < 7 || euler.x > 80){
euler.x = Mathf.Clamp(euler.x, 7, 80);
transform.eulerAngles = euler;
}
offset = transform.position - player.position;
}
4 相关知识点对比
无角度限制俯仰 VS 阈值锁定俯仰
无限制:鼠标拖动会相机倒转,画面完全颠倒; 角度 Clamp:视野固定合理区间,符合 RPG 常规观察视角。
5 模块拓展
可做视角渐变缓冲,俯仰转动更顺滑。
六、NPC 点击任务弹窗与 DoTween 滑入动画
1 概念
NPC 挂载碰撞体射线检测,点击弹出任务面板;面板初始放在屏幕外侧,使用 DoTween 缓动从侧边滑入画布。
2 特性 / 规则
-
任务面板默认隐藏、坐标移出屏幕;
-
点击 NPC 触发 Tween 动画,X 坐标插值滑至屏幕中心;
-
面板包含任务描述文本、进度文本、接受 / 关闭按钮。
3 语法 & 代码片段
public RectTransform taskPanel;
public Vector2 showPos;
// NPC点击打开面板
taskPanel.anchoredPosition = new Vector2(2000, 0);
taskPanel.DOAnchorPos(showPos, 0.6f);
4 相关知识点对比
直接 SetActive 显示 VS DoTween 滑入动画
直接激活:面板瞬间弹出,无过渡观感生硬; DoTween 缓动:有移动过渡动画,游戏 UI 交互更精致。
5 模块拓展
关闭面板增加滑出退场动画。
七、任务业务逻辑(描述 / 进度切换)
1 概念
区分未接任务、进行中任务两种状态,点击 NPC 自动切换面板文本:未接显示任务需求与奖励,已接显示当前击杀进度。
2 特性 / 规则
-
布尔变量
isAcceptTask标记是否已领取任务; -
接收任务按钮点击后修改状态,同步更新文本;
-
任务完成后弹出提示弹窗,发放虚拟货币奖励。
3 语法 & 代码片段
public Text descTxt;
public Text progressTxt;
private bool isAcceptTask;
// 点击NPC刷新面板文字
if(!isAcceptTask){
descTxt.text = "击杀10只野怪,奖励1000金币";
progressTxt.gameObject.SetActive(false);
}
else{
progressTxt.gameObject.SetActive(true);
progressTxt.text = "已击杀3/10";
}
4 相关知识点对比
硬编码文本 VS 配置表读取
硬编码:写死文字,修改任务内容需改代码; 配置表:JSON/TXT 存储任务 ID、描述、奖励,新增任务无需改脚本。
5 模块拓展
多任务 ID 管理,区分主线 / 支线任务。
八、UI 射线穿透地面移动 Bug 与遮罩解决方案
1 概念
弹出任务面板后,点击 UI 空白处仍会触发地面寻路,根源是 GraphicRaycaster 射线穿透到场景碰撞体;使用全屏底层白色 Image 遮罩拦截射线。
2 特性 / 规则
-
遮罩铺满 1920*1080 画布,层级低于按钮、文本;
-
遮罩开启 Raycaster,拦截所有 UI 区域鼠标输入;
-
地面移动逻辑增加
EventSystem.current.IsPointerOverGameObject()判断,鼠标在 UI 内禁止发射地面射线。
3 语法 & 代码片段
// 地面射线前置判断,UI上不寻路
if(Input.GetMouseButtonDown(0)){
if(EventSystem.current.IsPointerOverGameObject())
return;
// 执行地面射线寻路逻辑
}
4 相关知识点对比
仅代码判断鼠标 UI VS 底层遮罩拦截
代码判断:简单弹窗可用,多层弹窗容易遗漏判断; 全屏遮罩:底层统一拦截,所有 UI 界面自动屏蔽地面点击。
5 模块拓展
弹窗关闭时同步隐藏遮罩。
九、项目开发规范:单一职责脚本拆分
1 概念
禁止一个脚本承载 NPC 点击、任务 UI、移动、相机全部逻辑,遵循单一职责:NPC 脚本仅负责弹窗触发,任务面板脚本只管理 UI 动画与任务文本,移动 / 相机独立挂载。
2 特性 / 规则
-
每个脚本只处理一类业务,代码解耦;
-
新增功能只新增脚本,不修改旧业务代码;
-
避免几百行巨型单脚本,排错、迭代成本极高。
3 相关知识点对比
巨型万能脚本 VS 按功能拆分脚本
巨型脚本:逻辑堆砌,修改一处容易牵连其他功能,Bug 频发; 拆分脚本:分层隔离,修改任务 UI 不影响角色移动、相机逻辑。
5 模块拓展
大型项目采用 MVC 分层,视图、数据、控制完全分离。
综合拓展
1 完整 RPG 摄像机 + NPC 任务业务流程
玩家地面 XZ 平面移动→平滑跟随环绕相机(缩放 + 俯仰限制)→NPC 射线点击→DoTween 滑入任务面板→状态切换文字→全屏遮罩解决 UI 穿透;
2 高频 Bug 排查清单
人物斜坡不停抖动:距离计算包含 Y 轴,改用 XZ 平面;
相机完全翻转:俯仰角度未用 Clamp 锁定阈值;点击 UI 仍能走路:缺少 IsPointerOverGameObject 判断 / 无底层遮罩;
任务面板无滑入效果:DoTween 命名空间未引用、初始坐标未移出屏幕;
3 考点
平面距离计算修复移动抖动、RotateAround 环绕相机、鼠标三轴相机控制、UI 射线穿透解决方案、单一职责代码规范、DoTween 界面动画;
4 进阶拓展学习方向
任务 JSON 配置表、怪物 AI 自动计数、任务完成弹窗、背包道具奖励、多支线任务系统;