Unity学习(1)

工程基于Unity learn的教程John Lemon’s Haunted Jaunt: 3D Beginner

https://learn.unity.com/project/john-lemon-s-haunted-jaunt-3d-beginner?language=en

文章用于自己对学习内容进行梳理

Prefabs(预设)

类似于创建模板,双击可以打开预设编辑页面,页面里更改的会应用于所有的由该预设创建出的对象,但需要注意的是,如果在Hierarchy目录里为单个对象添加组件的话,并不会改变预设内的值,但可以用overrides来将修改运用于预设

目录里的对象如果对应有预设的话会蓝色显示

角色

本工程中角色包含三个要素:动画,模型,脚本

动画:

Unity中利用一个Animators的对象来控制角色在不同的动画中切换

Entry为起始点,箭头指向的动画Idle为静止动画,人物不动的时候播放该动画,当人物移动时,需要切换为移动动画,白色的双向箭头中可以添加切换的条件

其中的Has Exit Time一般取消选择,意思为会在确定的时间进入下一状态,当前情况下是需要判断IsWalking是否为true来进入下一状态

image-20200112221844706

需要在角色对象里添加Animator组件将以上部分添加,这样在该角色的脚本里才能通过获取组件的方式获取到Animator里的IsWalking这个变量并修改

关于animator的update mode

image-20191125111911991

脚本

角色的移动交互需要靠Script实现

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
public float turnSpeed = 20f;
Animator m_Animator;
Vector3 m_Movement;
Quaternion m_Rotation = Quaternion.identity;
Rigidbody m_Rigidbody;

AudioSource m_AudioSource;
// Start is called before the first frame update
void Start()
{
m_Animator = GetComponent<Animator>();
m_Rigidbody = GetComponent<Rigidbody>();
//GetComponent is a method of MonoBehaviour
m_AudioSource = GetComponent<AudioSource>();
}

// Update is called once per frame
void Update()
{
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);
if (isWalking)
{
if (!m_AudioSource.isPlaying)
{
m_AudioSource.Play();
}
}
else
{
m_AudioSource.Stop();
}
Vector3 desiredForward = Vector3.RotateTowards(transform.forward, m_Movement, turnSpeed * Time.deltaTime, 0f);

/*RotateTowards 有四个参数,前两个为转动角度的起始和终点,第三个为旋转的角度变化,第四个为旋转的幅度变化
*/
m_Rotation = Quaternion.LookRotation(desiredForward);
}
void OnAnimatorMove()
{
m_Rigidbody.MovePosition(m_Rigidbody.position + m_Movement * m_Animator.deltaPosition.magnitude);
m_Rigidbody.MoveRotation(m_Rotation);
}
}

这是控制角色移动的脚本,需要注意的点有

image-20191125120049740

物体运动的时候斜向的运动速度会快于水平的移动速度,normalizing可以将向量的最大值统一为1

在控制角色移动时,有如下两行代码

image-20191127144008063

官网上给出的API解释为

Returns the value of the virtual axis identified by axisName.

The value will be in the range -1…1 for keyboard and joystick input. If the axis is setup to be delta mouse movement, the mouse delta is multiplied by the axis sensitivity and the range is not -1…1.

This is frame-rate independent; you do not need to be concerned about varying frame-rates when using this value.s

edit-project setting里可以设置

image-20191127144239613

控制台输出的log如下

image-20191127150818786

我理解为,horizontal和vertical会在指定的输入按键(这里是wasd)下,在-1到1之间变化其值,表示其在某方向下的增量,然后再将这个值传给rigidbody.position就可以控制人物在场景里的坐标变化

Camera

unity里可以使用一个相机管理工具Cinemachine来控制镜头,甚至可以用来做电影和动画,如同AI相机操作员帮你控制镜头

本工程里的应用为在角色斜后方创建一个镜头跟着角色,这样可以实现俯视角游戏里以角色为屏幕中心的效果

image-20200112223220578

参数里的Follow可以设定为需要跟的人物

image-20200112223257172

其工作原理为根据对象的位置和角色中心点的位置差来将镜头移动,下面是相机的视觉图

image-20200112223411088

game视窗里红色区域表示绑定的object绝对不会到达的区域,屏幕中心的点会一直追着obj将其位于中心

Post Process

可以理解为在渲染完成后于屏幕上增加效果,类似于滤镜一样的,其有一个组件为作用体积

image-20200112223646035

勾选了Is Global后整个Scene里的镜头都会加上这个Post里的效果,有几个常用的effect

image-20200111192725851

这个特效可以调整镜头阴影

image-20200111192758638

image-20200112223907964

这个可以调整场景里的一些光照效果

敌人

本工程里设置了两种敌人,分别为静态和动态

静态敌人

敌人加上一个有Collider组件的子对象可以用来表示判定范围

image-20200112224259396

image-20200112224332120

当敌人位于判断范围内时进行某些行动,但如果玩家和敌人中间有障碍物,例如一堵墙的时候,敌人实际上是没有看见玩家的,判断方式为

1
2
3
4
5
6
7
8
9
10
11
12
13
if (m_IsPlayerInRange)
{
Vector3 direction = player.position - transform.position + Vector3.up;
Ray ray = new Ray(transform.position, direction);
RaycastHit raycastHit;
if (Physics.Raycast(ray, out raycastHit))
{
if (raycastHit.collider.transform == player)
{
gameEnding.CaughtPlayer();
}
}
}

Unity提供一个Ray类,作用可以理解发出一道光线,然后判断光线击中的对象是否为设定的对象

动态敌人

动态的敌人需要解决的问题为如何移动,本次用到的是另动态敌人在固定点之间巡逻,Unity提供了一个名为NavMesh的寻路网格,它用一个区域表示对象可以到达范围,设定参数后烘培效果如图

生成网格前需要先把对象变为静态

image-20200112225110592

Unity有自己的寻路算法,给对象添加寻路代理组件后可以通过调用该组件的方法来设定目的地

image-20200112225310678

waypoint需要定义为对象并调整它的位置,故寻路可以寻路至某个对象,例如敌人朝玩家靠近的实现。下面的脚本内容为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class WaypointPatrol : MonoBehaviour
{
public NavMeshAgent navMeshAgent;
public Transform[] waypoints;
int m_CurrentWaypointIndex;
// Start is called before the first frame update
void Start()
{
navMeshAgent.SetDestination(waypoints[0].position);
//调用组件的方法设立目的地
}

// Update is called once per frame
void Update()
{
//判断是否到达目的地,如果到达则将目的地改为数组的下一个
if (navMeshAgent.remainingDistance < navMeshAgent.stoppingDistance)
{
m_CurrentWaypointIndex=(m_CurrentWaypointIndex+1)%waypoints.Length;
}
navMeshAgent.SetDestination(waypoints[m_CurrentWaypointIndex].position);
}
}

all.