Unity 虚拟美术馆漫游系统
虚拟美术馆项目在Virtual art space Volume下,Demo是场景展示

Player脚本
在脚本中通过chracter controller 控制移动

代码依靠 CharacterController + 相机朝向,实现跟着镜头方向走(WASD 前后左右)。
1. 获取输入方向
float horizontal = Input.GetAxis("Horizontal"); // A/D、左右箭头:-1 ~ 1
float vertical = Input.GetAxis("Vertical"); // W/S、上下箭头:-1 ~ 1
Horizontal:左右轴,A=-1、D=1、无按键=0Vertical:前后轴,W=1、S=-1、无按键=0
这一步拿到玩家按键的偏移值。
2. 对齐相机方向(关键:跟着镜头走)
Vector3 cameraForward = cameraTransform.forward;
cameraForward.y = 0f; // 去掉上下俯仰,只保留水平方向
cameraForward.Normalize(); // 单位化,保证移动速度均匀
Vector3 cameraRight = cameraTransform.right;
cameraRight.y = 0f;
cameraRight.Normalize();
cameraTransform.forward:相机正前方- 把
y置0 = 忽略高低,只算水平面,不会往天上/地下走 Normalize():把方向向量长度固定为1,避免斜着走速度变快。
3. 合成最终移动方向
Vector3 move = cameraForward * vertical + cameraRight * horizontal;
公式理解:
- 相机前方向 × 前后按键(W/S) = 相对镜头前后
- 相机右方向 × 左右按键(A/D) = 相对镜头左右
两者相加,得到基于相机视角的整体移动方向。
4. 执行移动(核心运动函数)
controller.SimpleMove(move * moveSpeed);
move * moveSpeed:方向 × 移动速度,算出最终移动矢量CharacterController.SimpleMove():
自带重力、碰撞检测,直接让角色沿目标方向移动,不用自己算位移。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// 相机控制脚本:实现第三人称跟随、鼠标旋转视角、滚轮缩放、点击交互
public class PlayerCamera : MonoBehaviour
{
[Header("=== 相机跟随目标 ===")]
public Transform playerTarget; // 要跟随的玩家对象
[Header("=== 鼠标视角控制 ===")]
public float mouseSensitivity = 2f; // 鼠标灵敏度
public float minYAngle = -20f; // 视角向下最大俯角
public float maxYAngle = 80f; // 视角向上最大仰角
public float targetHeight = 1.5f; // 相机看向玩家的高度(头顶/胸口)
private float mouseX; // 存储鼠标左右旋转值(水平)
private float mouseY; // 存储鼠标上下旋转值(垂直)
[Header("=== 相机距离(缩放)===")]
public float currentDistance = 3f; // 当前相机与玩家的距离
private float zoomSpeed = 2f; // 滚轮缩放速度
private float minDistance = 1f; // 最近缩放距离
private float maxDistance = 5f; // 最远缩放距离
[Header("=== UI 交互对象 ===")]
public GameObject QuestionsCanvas; // 问题UI面板
public GameObject DeepSeekCanvas; // 对话UI面板
public GameObject DrawCanvas; // 绘画UI面板
private CanvasShowArtMessage artUI; // 显示艺术信息的UI脚本引用
void Start()
{
// 1. 如果没有手动指定玩家,自动通过标签寻找Player
if (playerTarget == null)
{
GameObject player = GameObject.FindGameObjectWithTag("Player");
if (player != null)
{
playerTarget = player.transform;
}
}
// 2. 游戏开始时锁定鼠标,隐藏光标(FPS/第三人称常用)
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
// 3. 找到场景中的艺术信息UI脚本
artUI = FindObjectOfType<CanvasShowArtMessage>();
}
// LateUpdate:所有Update执行完后再执行,保证相机跟随更平滑,不卡顿
void LateUpdate()
{
// 没有跟随目标直接退出,防止报错
if (playerTarget == null) return;
// 处理鼠标显示/隐藏(LeftAlt切换)
HandleCursorLock();
// 只有在光标隐藏时,才允许旋转视角
if (!Cursor.visible)
{
HandleMouseInput();
}
// 鼠标左键点击交互
HandleClick();
// 鼠标滚轮缩放
HandleZoom();
// 更新相机位置和角度
UpdateCameraPosition();
}
// 功能:鼠标左键点击物体,触发UI显示
void HandleClick()
{
// 检测鼠标左键按下
if (Input.GetMouseButtonDown(0))
{
// 从相机发射一条射线到鼠标点击的位置
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
// 如果射线碰到了物体
if (Physics.Raycast(ray, out hit))
{
if (artUI != null)
{
// 根据点击物体的名称,打开对应的UI面板
if(hit.collider.gameObject.name == "WorldQuestionsCanvas")
{
QuestionsCanvas.SetActive(true);
}
else if(hit.collider.gameObject.name == "WorldDeepSeekCanvas")
{
DeepSeekCanvas.SetActive(true);
}
else if(hit.collider.gameObject.name == "WorldDrawCanvas")
{
DrawCanvas.SetActive(true);
}
// 点击其他物体 → 显示对应的艺术介绍
else
{
artUI.ShowArtByName(hit.collider.gameObject.name + "Art");
}
}
}
}
}
// 功能:按 Left Alt 键 切换鼠标显示/隐藏
void HandleCursorLock()
{
if (Input.GetKeyDown(KeyCode.LeftAlt))
{
if (Cursor.visible)
{
// 隐藏光标 + 锁定到屏幕中心
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
else
{
// 显示光标 + 解锁
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = true;
}
}
}
// 功能:获取鼠标输入,计算旋转角度
void HandleMouseInput()
{
// 水平旋转:鼠标左右移动 累加
mouseX += Input.GetAxis("Mouse X") * mouseSensitivity;
// 垂直旋转:鼠标上下移动 累加(减号=反转Y轴,符合正常操作习惯)
mouseY -= Input.GetAxis("Mouse Y") * mouseSensitivity;
// 限制垂直视角角度,防止相机翻到角色底下或天上
mouseY = Mathf.Clamp(mouseY, minYAngle, maxYAngle);
}
// 功能:鼠标滚轮控制相机远近(缩放)
void HandleZoom()
{
// 获取滚轮输入
float scroll = Input.GetAxis("Mouse ScrollWheel");
// 调整相机距离
currentDistance -= scroll * zoomSpeed;
// 限制距离在最大/最小范围内
currentDistance = Mathf.Clamp(currentDistance, minDistance, maxDistance);
}
// 核心功能:计算相机最终位置 + 旋转,让相机环绕玩家
void UpdateCameraPosition()
{
// 相机跟随的目标点(玩家位置 + 向上偏移高度)
Vector3 targetPosition = playerTarget.position + Vector3.up * targetHeight;
// 将鼠标的X、Y值转为旋转角度
Quaternion rotation = Quaternion.Euler(mouseY, mouseX, 0);
// 相机向后移动的距离(沿着相机自身的Z轴后退)
Vector3 negDistance = new Vector3(0.0f, 0.0f, -currentDistance);
// 计算相机最终位置:目标点 + 旋转后的后退位置
Vector3 desiredPosition = targetPosition + rotation * negDistance;
// 应用旋转和位置 → 相机完成跟随+视角旋转
transform.rotation = rotation;
transform.position = desiredPosition;
}
}

game这里固定为1920*1080 。
角色动画
Player中有了角色动画资源,如何使用?

播放行走动作
在Player脚本中使用角色动画资源
定义动画组件
private Animation animation1;
- 这是 Unity 旧版动画系统 Animation 组件
- 角色身上已经挂载了这个组件,并放入:
- 走路动画(名字叫 walk)
- 待机动画(名字叫 wait)
代码在哪里初始化动画?
让脚本找到角色身上的 Animation 组件,以后可以用代码控制它。
void Start()
{
animation1 = GetComponent<Animation>();
}
核心:如何判断播放什么动画?
代码用 move移动量(move前面已经解释过,是一个Vector3方向向量) 来判断角色是在走还是站着, magnitude是模长:
if (move.magnitude > 0.1f) // 如果正在移动
{
animation1.Play("walk"); // 播放走路动画
}
else // 如果没移动
{
animation1.Play("wait"); // 播放待机动画
}
最关键的一句:动画是怎么播放的?
播放 Animation 组件里,名字叫 walk 的动画片段。
animation1.Play("walk");
同理,播放名字叫 wait 的动画。
animation1.Play("wait");
当游戏运行时,按住左Alt键,可以显示鼠标。
主控脚本PlayerCamera
本脚本为Unity 第三人称环绕相机控制器,挂载在相机对象上,整体功能分为六大模块:
1. 初始化配置
- 支持在编辑器面板手动指定相机跟随目标;若未手动赋值,程序会自动检索标签为
Player的游戏对象作为跟随主体。 - 程序启动后自动锁定并隐藏鼠标光标,适配第三人称漫游操作习惯。
- 自动场景中查找艺术信息UI脚本,提前完成引用绑定,规避空引用报错问题。
2. 视角旋转控制
- 鼠标光标处于锁定隐藏状态时,移动鼠标可控制视角:左右滑动实现相机绕玩家水平360°环绕,上下滑动控制镜头俯仰角度。
- 对垂直视角做区间限制,防止镜头翻转、穿模;鼠标灵敏度可在面板自定义调节。
- 光标解锁显示时,自动禁用视角旋转功能,避免操作UI时镜头异常转动。
3. 相机缩放功能
通过鼠标滚轮调整相机与玩家之间的距离,实现镜头拉近、拉远效果;同时设置距离上下限,约束缩放范围,保证视觉效果正常。
4. 光标状态切换
按下左Alt键可快速切换鼠标状态:光标锁定隐藏,专注于视角操控;光标解锁显示,可正常操作UI、点击场景物体。
5. 射线点击交互
点击鼠标左键时,相机向点击方向发射物理射线检测碰撞物体,实现交互逻辑:
- 点击指定名称的交互物体,分别激活问题、对话、绘画三类功能UI面板;
- 点击场景内其他物体,调用UI接口,弹出对应物体的艺术介绍面板。
6. 相机跟随逻辑
- 所有相机逻辑在
LateUpdate中执行,保证在角色运动、动画更新完成后再刷新相机位置,画面跟随更流畅无抖动。 - 以玩家胸口/头顶高度作为环绕中心点,结合视角旋转角度计算相机坐标,实现稳定的环绕跟随效果。
制作画像UI展示
创建一个UI Canvas
然后在Canvas下创建两个UI image, 分别将画作和背景画框的图片改为 2D和UI模式,分别拖动到两个image上,改变两幅图的顺序来改变层级关系,使画框作为背景显示。

等比例的显示。
制作画像UI的放大显示
创建一个空物体,将刚刚的两个物体拖动到空物体下。通过判断名字来显示和隐藏游戏物体。
在脚本中创建一个数组,储存gameobject,写一个函数,可以根据传入的字符串,控制数组中的其中一个显示,其余隐藏。
在Inspector面板显示数组中的gameobject,将刚刚创建的空物体画作拖动过来,此时数组中的gameobject显示为1。
在下面的脚本中通过空物体的名字控制画作介绍的显示与隐藏。
场景中可点击的物体名字必须和画布物体的名字保持一致,这里加了Art是因为我在代码里加了“Art”字符串。
设置文字
创建UI text文本
拖动 text框 至合适大小,在右侧Text组件中编辑文字。
调整字体大小,可以给字体添加Shadow组件,增加阴影效果。其次,也可以调整文字颜色。
设置退出按钮
在空物体下创建一个Button, 编辑下面自带的子物体Text为X。
进一步设置点击X按钮,关闭画作介绍。
在脚本中写一个 CloseArt方法,调用HideAll() 方法。
将画布拖动到Button的 鼠标点击 On Click中,并选择方法为CloseArt()。

此时,点击X,可以关闭画布。
如何在游戏场景中播放视频
创建一个canvas,在canvas下面再创建一个RawImage
在画布中搜索添加VideoPlayer组件,并将视频拖动过来。
在Res中创建一个Render Texture, 并改名为Video Texture。
将Video Texture 拖动到 Raw Image下的 Target Image。调整一下size。

修改名字,调整一下大小,位置。
控制背景音乐和视频声音的播放
Audio Source 中 Play On Awake 和 Loop 为游戏开始时就 播放视频和循环播放。
在CanvasShowArtMessage脚本中编写代码控制视频的暂停和播放。
同样的,播放视频的也和前面的图片一样复制一份预制体并改名为一个同名+“Art”的画布。
知识问答功能制作
重新创建一个Canvas,将预制体公园们的孩子们Art拖动过来,修改界面,做成下图的四个选项的问答样式,按钮比较多,按钮做成预制体。

问答系统题库的制作
问题是text组件,下面的选项是Button组件,创建CavansQusetion脚本,问题是text组件,下面的选项是Button组件,创建CavansQusetion脚本,将四个按钮拖动到对应的。
下一题切换的逻辑
选择脚本的NextQuestion函数。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)