一、下载并导入资源
3D Beginner: Tutorial Resources
二、设置场景
1、Main Scene
2、打开Assets-UnityTechnologies-Prefabs,将Level 拖放到Hierarchy面板
3、打开Assets-UnityTechnologies-Models-Characters,将JohnLemon拖放到Hierarchy面板
设置位置(16,0,2)。制成预制体
三、设置玩家角色
1、观察动画
(1) 打开Assets-UnityTechnologies-Animation-Animation,选中John@Idle,选中Animation播放
(2) 打开Assets-UnityTechnologies-Animation-Animation,选中John@Walk,选中Animation播放
2、设置角色运动的动画
(1) 打开Assets-UnityTechnologies-Animation-Animators,Create-Animator-Controller,命名为JohnLemon
(2) 打开JohnLemon,出现下图
(3) 依次将Assets-UnityTechnologies-Animation-Animation中的John@Idle和John@Walk拖放到Base Layer窗口中
(4) 右击Idle-Make Transition,与Walk建立联系;同样的方法建立Walk与Idle的联系
(5) 设定联系的条件:选中Parameters,点击“+”,Bool。命名为IsWalking。不勾选(默认状态)
(6) 设定转换:
选中Idle到Walk的箭头,右侧点加号,出现IsWalking true,取消勾选Hash Exit Time
(7) 调整走路时间:按住竖蓝色区域拖动
(8) 同样的方法设定Walking转换为Idle。将IsWalking设置为false。取消勾选Hash Exit Time
(9) 关闭Animator面板,回到Scene面板,选中JohnLemon,设置它的Controller
Apply Root Motion:
将动画的根运动应用到角色的实际运动中。
根运动是动画中根骨骼的运动,例如角色行走或跑步时的移动。
当将Apply Root Motion设置为true时,角色将根据动画中的根运动实际移动,而不是完全依靠程序代码来控制角色的运动。这样可以使动画的运动更加自然和流畅。
勾选时:能根据动画中物体的位移信息对物体的速度进行赋值。看起来的效果一般是角色持续移动
应用:只需要动画本身的运动时,不勾选
需要动画运动反应在场景中时,勾选
(10) 设置Update Mode
normal:角色本身的运动在FixedUpdate()方法中刷新
Animate Physics:使角色的动画能够正确地响应物理效果,如重力、碰撞等
3、设置角色运动
(1) 给JohnLemon 添加Rigidbody组件
(2) 勾选Constraints中Freeze Position 约束角色(冻结)在Y轴方向的移动,只能向X、Z方向移动
也可以使用代码实现这一功能
rigidbody.constraints = rigidbody.constraints | RigidbodyConstraints.FreezePositionY;
(3) 勾选Constraints中,Freeze Rotation 约束角色在X、Z轴方向的旋转
(4) 给JohnLemon 添加Capsule Collider组件,编辑碰撞范围
4、使角色运动
说明:
角色【前进、倒退】移动:transform forward
角色【向左、向右】改变方向:
(1) 给角色 JohnLemon添加JohnLemon.cs组件
private Animator _animator;//声明私有变量,用于控制JohnLemon角色的动画系统
void Start()
{
_animator = this.GetComponent<Animator>();//JohnLemon角色的Animator组件
}
void Update()
{
float moveH = Input.GetAxis("Horizontal");//获取水平方向上的输入
float moveV = Input.GetAxis("Vetical");
bool hasMoveH = !Mathf.Approximately(moveH,0);//判断是否正在行走(忽略位置的微小变动)
bool hasMoveV = !Mathf.Approximately(moveV,0);//hasMoveV不是发生微小变动(发生了位移)
bool IsWalking = hasMoveH || hasMoveV;//角色运动,IsWalking是声明一个变量,判断是否应该运动
_animator.SetBool("IsWalking", IsWalking);//双引号内的是Animator中添加的运动的方法,后面的IsWalking是上一行声明的那个变量
}
(2) 使角色改变方向
private Rigidbody rb;
private Vector3 direction;
void Start()
{
……
rb = this.GetComponent<Rigidbody>();
}
void Update()
{
……
if (IsWalking)
{
direction = new Vector3(moveH,0,moveV);
direction.Normalize();//Normalized:长度为1的单位
}
}
private void FixedUpdate()
{
Quaternion rotation =Quaternion.LookRotation(direction);//据给定的方向向量计算出一个四元数,表示将物体旋转以面向该方向的旋转
rb.MoveRotation(rotation);
//使用MovePosition方法将这个rb移动到新的位置,而忽略任何物理模拟
rb.MovePosition(transform.position + direction*_animator.deltaPosition.magnitude);
}
MovePosition
:
将物体移动到指定的位置,而不会考虑任何物理影响(比如速度、碰撞等)
常在FixedUpdate方法中使用,因为它不基于时间步长,而是直接设置物体的位置
transform.position
:
当前GameObject的世界坐标位置
transform
表示物体在世界中的位置、旋转和缩放
animator.deltaPosition.magnitude
:
根据Animator的动画动态地调整移动距离
_animator
:
私有变量,用于控制JohnLemon角色的动画系统
deltaPosition
:
一个表示从上一帧到当前帧位置变化的Vector3
magnitude
: Vector3的一个属性,返回该向量的长度(即其到原点的距离)
direction*_animator.deltaPosition.magnitude
计算一个移动量,这个移动量是direction
方向上的一个距离,这个距离等于_animator.deltaPosition
的长度(或大小)
(3) 平滑过渡
public float moveSpeed = 10f;
private void OnAnimatorMove()
{
//平滑旋转
Vector3 deltaDirection = Vector3.RotateTowards(transform.forward, direction, Time.deltaTime*moveSpeed,0);
//transform.forward:初始方向
//direction:旋转后的方向
……
}
本节完整代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Ball : MonoBehaviour
{
public float turnSpeed = 20f;
Animator m_Animator;
Rigidbody m_Rigidbody;
Vector3 m_Movement;
Quaternion m_Rotation = Quaternion.identity;
void Start()
{
m_Animator = GetComponent<Animator>();
m_Rigidbody = GetComponent<Rigidbody>();
}
void FixedUpdate()
{
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
m_Movement.Set(horizontal, 0f, vertical);
m_Movement.Normalize();
bool hasHorizontalInput = !Mathf.Approximately(horizontal, 0f);
bool hasVerticalInput = !Mathf.Approximately(vertical, 0f);
bool isWalking = hasHorizontalInput || hasVerticalInput;
m_Animator.SetBool("IsWalking", isWalking);
Vector3 desiredForward = Vector3.RotateTowards(transform.forward, m_Movement, turnSpeed * Time.deltaTime, 0f);
m_Rotation = Quaternion.LookRotation(desiredForward);
}
void OnAnimatorMove()
{
m_Rigidbody.MovePosition(m_Rigidbody.position + m_Movement * m_Animator.deltaPosition.magnitude);
m_Rigidbody.MoveRotation(m_Rotation);
}
}
四、使用插件镜头跟踪
1、安装插件
Window-Package Manager
2、调整摄像机的位置
3、创建虚拟摄像机,命名VCM
(1) 此时,VCM自动与主摄像机关联(主摄像机无法通过拖动改变位置),只能通过VCM调整摄像角度
(2) 设置VCM
注:
Follow:设定参数在body
Look At:勾选时可设定为一直朝向某物体(本工程不设定。设定后的参数在Aim)
五、通关触发
1、目标
穿越物体,进入通关房间,游戏结束
2、创建和设置物体
(1) 3D Object-Cube。命名:EixtDoor。Position(21,0.5,1.6)
(2) 移除Cube的Mesh Filter(网格过滤器) 和 Mesh Renderer(物体不可见)
(3) 设置碰撞体:勾选Is Trigger
3、设置触发事件
(1) 给角色添加标签Player
(2) 给EixtDoor添加EixtDoorCtrller.cs
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag("Player"))
{
Debug.Log("成功退出");
}
}
六、添加敌人
1、静态敌人
(1) 打开Assets-UnityTechnologies-Models-Characters,将Gargoyle制成预制件
(2) 调整Transform:Position(6.7,0,1.8),Rotation(0,90,0)
(3) 添加动画效果
① 打开Assets-UnityTechnologies-Animation-Animators,Create-Animation-Controller,命名为StaticEnemy
② 将打开Assets-UnityTechnologies-Animation-Animation中的Gargoyle@Idle,拖放到Base Layer窗口
③ 关闭Animator面板,回到Scene面板,选中StaticEnemy,设置它的Controller
(4) 添加Capsule Collider
(5) 设置视野范围
原理:在眼睛前面放置Capsule Collider(触发器)
注意:没有形状、Is Trigger、原点在触发器内部、本Collider不能与其它Collider接触
制作:以Gargoyle为父物体,Create Empty,命名为SightPoint。设置Transform
添加Collider,并设置
(6) 将场景中改变的项赋予给预制件:点击Overrides,点击Apply All
(7) 触发检测(发现角色)
① 给Gargoyle的预制件添加SightPointCtrller.cs 组件
private bool needDetect = false;//正常不检测
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag("Player"))
{
Debug.Log("进入检测范围");
needDetect = true;
}
}
private void OnTriggerExit(Collider other)
{
if (other.gameObject.CompareTag("Player"))
{
Debug.Log("离开检测范围");
needDetect = false;
}
}
② 射线算法:
射线的方向 = 待测物体.position - 检测物体.position + 待测物体.position的向上偏移的量
③ 射线检测
public Transform playerTransform;
void Update()
{
if (needDetect)//进入检测范围
{
//射线方向
Vector3 rayDirection = playerTransform.position - transform.position + Vector3.up;
//创建射线
Ray ray = new Ray(transform.position, rayDirection);//transform.position:射线起点; rayDirection:射线方向
RaycastHit raycastHit;//存储射线投射过程的结果
if (Physics.Raycast(ray, out raycastHit))//out raycastHit:得到的内容返回给raycastHit
{
if (raycastHit.collider.transform == playerTransform)
{
Debug.Log("被发现了!");
}
else
{
Debug.Log("不是角色!" +raycastHit.collider.gameObject);
}
}
}
}
④ 赋值
(8) UI-Image,命名Winbg。Shift+Alt+拉伸全屏(锚点、轴心点都在中心),调整颜色为黑色
勾选Preserve(图像不变形)
(9) 以Winbg为父物体,UI-Image,命名Win。Shift+Alt+拉伸全屏,为Win,选择图片;保持宽高比
(10) 添加Canvas Group组件,设置Alpha=0
(11) Ctrl + D,更改名字为Failbg和Fail,更换图片
(12) 完善SightPointCtrller.cs和EixtDoorCtrller.cs
① Create Empty。命名为GameManager
② 给GameManager添加GameManager.cs组件
public float duration = 1f;
private float pastTime;
public CanvasGroup winBg;
public CanvasGroup failBg;
private bool isWin = false;
private bool isFail = false;
public void Win()
{
isWin = true;
}
public void Fail()
{
isFail = true;
}
public void Update()
{
if (isWin)
{
showBackground(winBg);
}
else if (isFail)
{
showBackground(failBg);
}
}
private void showBackground(CanvasGroup backgroung)
{
//随时间变化,透明度逐渐减小
pastTime = pastTime + Time.deltaTime;//累加时间
backgroung.alpha = pastTime / duration;//
}
③ 赋值
④ 打开EixtDoorCtrller.cs
public GameManager gameManager;
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag("Player"))
{
Debug.Log("成功退出");
gameManager.Win();//新增
}
}
⑤ 赋值
⑥ 打开SightPointCtrller.cs
public GameManager gameManager;
if (raycastHit.collider.transform == playerTransform)
{
Debug.Log("被发现了!");
gameManager.Fail();//新增
}
⑦ 赋值
2、动态敌人
(1) 打开Assets-UnityTechnologies-Models-Characters,将Ghost 制成预制件
(2) 调整Transform:Position(23,0,2),Rotation(0,90,0)
(3) 添加动画效果
① 打开Assets-UnityTechnologies-Animation-Animators,Create-Animation-Controller,命名为DynamicEnemy。
② 打开DynamicEnemy,将Animation中的Ghost@Walk拖入Base Layer中
③ 将DynamicEnemy拖拽到Hierarchy面板的Ghost上
(4) 添加发现Player的功能(与静态敌人相同)
① 选中Gargoyle,点击右侧小箭头,进入编辑预制件界面
② 将SightPoint转变为预制件
③ 回到主场景,编辑Ghost的预制件:为之添加SightPoint预制件,调整SightPoint的位置(下移)
④ 回到主场景,为SightPoint预制件上的脚本赋值
巡逻思路:
地图、巡逻的主体(在这里是 Ghost)
(5) 巡逻地图
① 生成地图:
Window-AI-Navigation(Obsolete)
② 选中Hierarchy面板上的Level(背景房间),在它的Inspector面板右侧,勾选Static后,选择yes
物体被标记为Navigation Static后,Unity会在Bake过程中将这些物体的几何信息纳入导航网格中,从而使得游戏中的角色或其他可移动对象能够知道哪些区域是可行走的,哪些是不可行走的
③ 点击右下角的Bake按钮,生成地图
④ 选中FogPlane,取消勾选Static
⑤ 搜索CeilingPlane,取消勾选Static
⑥ 重新生成地图
(6) 巡逻对象
① 给Ghost添加Nav Mesh Agent组件
Nav Mesh Agent组件中的Agent Type与Navigation中的Agent对应
Navigation中可以根据需要增加Agent类型
Nav Mesh Agent组件:
作用:实现游戏角色的导航功能。使角色能够在场景中找到路径并朝目标移动
speed:巡逻速度改为1.5
Stopping Distance:改为0.2
当靠近目标位置的距离达到此值时,代理将停止
即:指定代理(Agent)在距离目标位置多远的地方停止移动
② 巡逻对象巡逻路径:提供巡逻起点、途经点、终点、地图后,由Navigation系统自动计算。
③ 给Ghost添加Capsule Collider组件,设置碰撞器范围
④ Create Empty,命名为WayPoints。Reset
⑤ 以WayPoints为父物体,Create Empty,2个,分别命名为WayPoint0,WayPoint1
⑥ 设置Postion(25.5,0,3)和( 16.8,0,2)
⑦ 给 Ghost 添加WayPointsCtrller.cs组件
using UnityEngine.AI;
private int pointIndex = 0;
public Transform[] wayPoint;//路径点数组
public NavMeshAgent enemyAgent;//动态敌人的导航代理组件
void Start()
{
// 确保至少有两个路径点
if (wayPoint.Length > 1)
{
// 将enemyAgent的目标位置设置为wayPoint数组中的第一个元素的位置
//一旦执行了这句代码,enemyAgent将会开始尝试移动到这个新的目标位置
enemyAgent.SetDestination(wayPoint[pointIndex].position);//SetDestination:设置代理(动态敌人)的目标位置
}
else
{
Debug.LogError("至少需要两个路径点!");
}
}
// Update is called once per frame
void Update()
{
if (enemyAgent.remainingDistance < enemyAgent.stoppingDistance) //enemyagent到达wayPoint[0].position
{
//在0和1两个点之间巡逻
pointIndex = (pointIndex+1)%wayPoint.Length;
enemyAgent.SetDestination(wayPoint[pointIndex].position);//向wayPoint[1].position移动
}
}
⑧ 赋值、Apply All (接受对Ghost预制件的所有更改)
七、视觉渲染
1、调整Directional Light
(1) Transform中的Rotation
(2) Light中的Color(225,240,255),Intensity=2 略惊悚
(3) Light中的Shadow Type
2、环境光
(1) Window-Rendering-Lighting-Environment,Source选择Gradient,
Sky Color(170,180,200),Equator Color(90,110,130),Groung Color(0,0,0)
(2) Generate Lighting
3、后期特效
(1) 特效插件:Window-Package Manager-Post Processing是否安装
(2) Create Empty,命名。GlobalPostProcessing,Reset
(3) 为GlobalPostProcessing添加Post-Process Volume 组件(容纳各种特效的容器)
(4) 为GlobalPostProcessing添加Layer
(5) 选中Main Camera,添加PostProcessLayer,并指定Layer。从而关联GlobalPostProcessing与Main Camera
(6) 开启反锯齿功能
(7) 选中GlobalPostProcessing,点击New
(8) 勾选Is Global
(9) 点击Add effect-Unity-Color Grading
(10) 设置Tonemapping下的Mode为ACES(创设胶片感画面)
(11) 改变Post-exposure(EV)的值
(12) 调节Trackballs,执行三向颜色分级
Lift:控制暗色调。对阴影产生更大的影响
Gamma:通过幂函数控制中间调
Gain:增强信号并使高光更亮
(13) 泛光效果:点击Add effect-Unity-Bloom,调节Intensity
控制辉光效果的亮度。
屏幕上的高光点会显得更为突出和明显。
这种效果常用于模仿如霓虹灯、发光的武器、激光剑、车灯以及夜晚窗户透出的灯光等特效
(14) 点击Add effect-Unity-Vignette,勾选Center,调节Intensity。使四周变暗,中间变亮
(15) 点击Add effect-Unity-Lens Distortion,调节Intensity可能会有鱼眼效果
八、音效
1、设置背景音乐
(1) 将SFXHouseAmbience拖放到Hierarchy面板,命名为BgAudio。勾选Play On Awake和Loop
(2) 调节Volume(小)
2、设置Ghost移动的声音
(1) 将SFXGhostMove拖拽到Hierarchy面板的Ghost上。勾选Play On Awake和Loop
(2) 设置根据与角色的距离调节声音大小
Spatial Blend调节为1;VolumeRollof选择Custom Rolloff,Max Distance调节为10
3、移除Main Camera上的Audio Listener
4、在角色(JohnLemon)上添加Audio Listener组件,接受预制件的改变
5、分别将SFXGameOver和SFXWin拖放到Hierarchy面板,命名
6、打开GameManager.cs
public AudioSource winAudio;
public AudioSource failAudio;
public void Update()
{
if (isWin)
{
showBackground(winBg,winAudio);//增加,winAudio
}
else if (isFail)
{
showBackground(failBg, failAudio);//增加,failAudio
}
}
private void showBackground(CanvasGroup backgroung,AudioSource audio)//增加,AudioSource audio
{
//随时间变化,透明度逐渐减小
pastTime = pastTime + Time.deltaTime;//累加时间
backgroung.alpha = pastTime / duration;
//新增判断
if (!audio.isPlaying)
{
audio.Play();
}
}
8、赋值
9、调整:取消SFXGameOver和SFXWin上Play On Awake的勾选
10、角色脚步声
(1) 将 SFXFootstepsLooping 拖放到JohnLemon上
(2) 取消勾选Play On Awake
(3) 打开JohnLemon.cs
public AudioSource stepAudio;
if (IsWalking)
{
direction = new Vector3(moveH, 0, moveV);
direction.Normalize();//normalized:长度为1的单位
if (!stepAudio.isPlaying) //新增
{
stepAudio.Play();
}
}
else//新增
{
if (stepAudio.isPlaying)
{
stepAudio.Stop();
}
}
(4) 赋值
九、退出界面
1、设置按钮
(1) Create Empty。命名为BtnCtrller
(2) 给BtnCtrlle添加BtnCtrller.cs组件
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class BtnCtrller : MonoBehaviour
{
public void OnRestart()
{
SceneManager.LoadScene("MainScene");
}
public void OnApplicationQuit()
{
Application.Quit();
}
}
2、创建2个按钮:
UI-Button,分别命名Restart、Quit。调整按钮高度、位置,字体大小颜色等
3、按钮控制:
添加按钮事件
4、控制按钮显示/隐藏
(1) 打开GameManager.cs
public Button restartBtn;
public Button quitBtn;
private void Start()
{
restartBtn.gameObject.SetActive(false);
quitBtn.gameObject.SetActive(false);
}
if (!audio.isPlaying)
{
audio.Play();
}
restartBtn.gameObject.SetActive(true);
quitBtn.gameObject.SetActive(true);
(2) 赋值