练习 1
实现路径
下载并导入角色资源。
编写角色控制脚本,使角色进行移动。
编写摄像机控制脚本进行摄像机跟随。本例使用“操作演示:搭建像素游戏场景”导入的Voxel Castle Pack Lite完成案例的制作。
执行“窗口>资源商店”菜单命令,在资源商店中下载并导入Easy Primitive People。然后将“项目”面板中的Easy Primitive People/Prefab/Patient预制件拖拽到场景视图中,并命名为Player,接着设置Player的“标签”为Player,
在“项目”面板中执行“创建>C#脚本”命令创建一个脚本,并命名为PlayerControl,然后将其添加到Player物体上,编写的脚本代码如下。
using UnityEngine;
public class PlayerControl : MonoBehaviour
{
void Update()
{
// 获取水平虚拟轴
float horizontal = Input.GetAxis("Horizontal");
// 获取垂直虚拟轴
float vertical = Input.GetAxis("Vertical");
// 创建一个向量,该向量为用户按下按键对应的向量
// 这里让horizontal控制x轴移动,vertical控制z轴移动
Vector3 dir = new Vector3(horizontal, 0, vertical);
// 如果向量不为0,证明玩家一定按了方向键
if (dir != Vector3.zero)
{
// 玩家转向dir向量的方向
transform.rotation = Quaternion.LookRotation(dir);
// Update是按帧刷新的,如果参数不乘以Time.deltaTime,
// 代表的含义为每帧移动2m
// 所以这里做一个转换,让2乘以帧时间间隔Time.deltaTime,
// 即可转换单位,从每帧移动2m变为每秒移动2m
// 转换单位还可以同步不同设备的速度。
transform.Translate(Vector3.forward * 2 * Time.deltaTime);
}
}
}
在“项目”面板中执行“创建>C#脚本”命令创建一个脚本,并命名为CameraControl,然后将其挂载到Camera上。编写的脚本代码如下,游戏的运行情况如图所示。
using UnityEngine;
public class CameraControl : MonoBehaviour
{
// 保存向量
private Vector3 vector;
// 玩家角色的Transform组件
private Transform player;
void Start()
{
// 通过标签获取玩家角色的Transform组件
player = GameObject.FindWithTag("Player").transform;
// 获取摄像机到玩家角色的向量
vector = player.transform.position - transform.position;
}
void Update()
{
// 执行向量计算,更新摄像机的位置来进行跟随
transform.position = player.transform.position - vector;
}
}
本例初次提到了摄像机,摄像机的使用方法会在第4章中细讲,这里我们仅实现摄像机的跟随功能。目前角色移动到墙体上会穿墙而过,当学习了第5章后,可以尝试为角色添加Rigidbody(刚体)组件。Rigidbody组件可以让角色拥有碰撞效果。
练习2
1.玩法介绍
在本例的爆破人游戏中,玩家控制角色在场景中四处走动,但是注意不要接触到敌人,在一定距离内接触到敌人则游戏失败。玩家可放置炸弹,通过销毁敌人来保护自己。
2.实现路径
- 01下载资源并导入场景和角色。
- 02实现角色的移动。
- 03添加判断功能,当炸弹和敌人的距离小于3m时,对敌人进行销毁。
- 04添加判断功能,当角色和敌人的距离小于3m时,追踪角色;当角色和敌人的距离小于2m时,角色死亡。
- 05按U键释放炸弹,并且每2s内只能释放一个炸弹。
- 06更新摄像机的位置并跟随角色。
执行“窗口>资源商店”菜单命令,在资源商店中下载并导RPG/FPSGameAssetsforPC/Mobile。资源导入完成后,双击“项目”面板中RPG_FPS_game_assets_industrial/Map_v1,场景效果如图所示。
在“层级”面板中执行“创建>摄像机”命令创建一个摄像机,然后在Transform组件中设置“位置”的x属性为51,Y属性为6,Z属性为-37;设置“旋转”的X属性为18,Y属性为360,Z属性为0;设置“缩放”的X属性为1,Y属性为1,Z属性为1,如图所示。
执行“窗口>资源商店”菜单命令,在资源商店中下载并导入EasyPrimitivePeople。资源导入完成后,将“项目”面板中的EasyPrimitivePeople/Prefab/Santa拖曳到场景视图中,同时将该游戏物体命名为Player,设置其“标签”为Player,如图所示。
在“项目”面板中执行“创建>C#脚本”命令创建一个脚本,并命名为PlayerControl,然后将其挂载到Player物体上。编写的脚本代码如下,游戏的运行情况如图所示。
using UnityEngine;
public class PlayerControl : MonoBehaviour
{
void Update()
{
//移动部分代码
//获取水平虚拟轴
float horizontal = Input.GetAxis("Horizontal");
//获取垂直虚拟轴
float vertical = Input.GetAxis("Vertical");
//创建一个向量,该向量为玩家按下按键对应的向量
//这里让horizontal控制x轴移动,vertical控制z轴移动
Vector3 dir = new Vector3(horizontal, 0, vertical);
//如果向量不为0,证明玩家一定按了方向键
if (dir != Vector3.zero)
//玩家角色转向dir向量的方向
transform.rotation = Quaternion.LookRotation(dir);
//Update是按帧刷新的,如果参数不乘以Time.deltaTime,
//代表的含义为每帧移动2m
//所以这里做一个转换,
//让2乘以帧时间间隔Time.deltaTime即可转换单位
//从每帧移动2m变为每秒移动2m
//转换单位还可以同步不同设备的速度,
//因为设备不同可能帧率也不同,所以如果不转换单位,
//可能看到不同设备的移动速度不同
transform.Translate(Vector3.forward * 2 * Time.deltaTime);
}
}
单击“添加组件”按钮添加组件,在弹出的菜单中查找并添加Rigidbody组件,如图所示,游戏的运行情况如图所示。
执行“窗口>资源商店”菜单命令,在资源商店中下载并导入3DMonsterBomb!和Proceduralfire。资源导入完成后,将“项目”面板中的JKT_ART/mo
using Unity.VisualScripting;
using UnityEngine;
public class BombControl : MonoBehaviour
{
//爆炸效果预制件,关联“项目”面板中的ErbGameArt/Proceduralfire
//Prefabs/Explosion预制件
public GameObject EffectPre;
void Start()
{
//2s后执行Boom0方法
Invoke("Boom", 2f);
}
//爆炸
void Boom()
{
//爆炸效果
GameObject effect = Instantiate(EffectPre, transform.position, transform.rotation);
//2s后删除爆炸效果
Destroy(effect, 2f);
//获取场景中的敌人
GameObject[] enemys = GameObject.FindGameObjectsWithTag("Enemy");
//遍历敌人
foreach (GameObject enemy in enemys)
{
//判断该敌人和炸弹间的距离是否小于3m
if (Vector3.Distance(transform.position, enemy.transform.position) < 3f)
{
//小于3m,炸到敌人,对敌人进行销毁
Destroy(enemy);
}
}
Destroy(gameObject);
}
}
n_00/Mon__00预制件拖曳到场景视图中,并命名为Bomb,效果如图所示。
在“项目”面板中执行“创建>C#脚本”命令创建一个脚本,并命名为BombControl,然后将其挂载到Bomb物体。编写的脚本代码如下,游戏的运行情况如图所示。
单击“添加组件”按钮添加组件,在弹出的菜单中查找并添加Rigidbody组件,如图所示,游戏的运行情况如图所示。
执行“窗口>资源商店”菜单命令,在资源商店中下载并导入3DMonsterBomb!!和Proceduralfire。资源导入完成后,将“项目”面板中的JKT_ART/mon_00/Mon__00预制件拖曳到场景视图中,并命名为Bomb,效果如图3-73所示。
在“项目”面板中执行“创建>C#脚本”命令创建一个脚本,并命名为BombContro1,然后将其挂载到Bomb物体。编写的脚本代码如下,游戏的运行情况如图所示。
将“项目”面板中的EasyPrimitivePeople/Prefab/Zombie预制件拖曳到场景视图中,并命名为Enemy,然后设置其“标签”为Enemy,如图所示。
在“项目”面板中执行“创建>C#脚本”命令创建一个脚本,并命名为EnemyControl,然后挂载到Enemy物体上。编写的脚本代码如下。
using UnityEngine;
public class EnemyControl : MonoBehaviour
{
//玩家角色
private GameObject player;
void Start()
{
//通过标签值获取玩家角色
player = GameObject.FindWithTag("Player");
}
void Update()
{
//获取与玩家角色的距离
float distance = Vector3.Distance(player.transform.position, transform.position);
//如果距离小于3m,追踪玩家角色
if (distance < 3)
{
//看向玩家角色
transform.LookAt(player.transform);
//向玩家角色移动
transform.Translate(Vector3.forward * 1f * Time.deltaTime);
}
//如果距离玩家角色小于2m,玩家角色死亡,游戏结束
if (distance < 2)
{
Debug.Log("玩家角色死亡,游戏结束");
Time.timeScale = 0;
}
}
}
释放炸弹完善主角控制,通过按键使主角释放炸弹。双击PlayerControl脚本,将其修改为如下代码,游戏的运行情况如图所示。
using UnityEngine;
public class PlayerControl : MonoBehaviour
{
// 炸弹预制件,关联“层级”面板中创建好的炸弹预制件
public GameObject BombPre;
// 炸弹CD计时器
private float CD = 2;
void Update()
{
// 移动部分代码
// 获取水平虚拟轴
float horizontal = Input.GetAxis("Horizontal");
// 获取垂直虚拟轴
float vertical = Input.GetAxis("Vertical");
// 创建一个向量,该向量为玩家按下按键对应的向量
// 这里让horizontal控制x轴移动,vertical控制z轴移动
Vector3 dir = new Vector3(horizontal, 0, vertical);
// 如果向量不为0,证明玩家一定按了方向键
if (dir != Vector3.zero)
{
// 玩家角色转向dir向量的方向
transform.rotation = Quaternion.LookRotation(dir);
// Update是按帧刷新的,如果参数不乘以Time.deltaTime,
// 代表的含义为每帧移动2m
// 所以这里做一个转换,让2乘以帧时间间隔Time.deltaTime
// 即可转换单位,从每帧移动2m变为每秒移动2m
// 转换单位还可以同步不同设备的速度,
// 因为设备不同可能帧率也不同,所以如果不转换单位,
// 可能看到不同设备的移动速度不同
transform.Translate(Vector3.forward * 2 * Time.deltaTime);
}
// 计时器时间增加
CD += Time.deltaTime;
// 如果按下U键
if (Input.GetKeyDown(KeyCode.U))
{
// 2s的CD
if (CD > 2)
{
// 重置CD
CD = 0;
// 释放炸弹
Instantiate(BombPre, transform.position, transform.rotation);
}
}
}
}
在“项目”面板中执行“创建>C#脚本”命令创建一个脚本,并命名为CameraControl,然后挂载到摄像机上。编写的脚本代码如下,游戏的运行情况如图
using UnityEngine;
public class MainControl : MonoBehaviour
{
//保存向量
private Vector3 vector;
//玩家角色的Transform组件
private Transform player;
void Start()
{
//通过标签获取玩家角色的Transform组件
player = GameObject.FindWithTag("Player").transform;
//获取从摄像机到玩家角色的向量
vector = player.transform.position - transform.position;
}
void Update()
{
//执行向量计算,更新摄像机的位置来进行跟随
transform.position = player.transform.position - vector;
}
}
本书出现的所有实例,其中编写的代码并不一定就是解决游戏需求的最佳方式,主要目的是希望读者能够对章节内容的学习进行总结。运行游戏时可能会卡顿,这种情况是正常的,并非代码的问题。

灯光
定向光类似平行光源,因此会发射出平行的光线。但是这种灯光比较特殊,因为它并没有固定的光线起始坐标,也就是说光线并不是从定向光的坐标处开始发出的。那么只要有一个定向光,场景中的所有物体都会被认为有一个定向光角度的光线从无穷远处照射而来,并产生阴影效果,如图所示。编辑下面创建一个定向光,为了使表现的效果统一,先将层级”面板中自动创建的DirectionalLight删除。然后在层级”面板中执行“创建>灯光>定向光”命令,创建完成后即可在场景视图中看到灯光效果,如图所示。定向光的“检查器”面板如图所示。
重要参数介绍图
类型:控制灯光的类型,有“定向”“点”“区域”和聚光”4种模式。
颜色:控制灯光的颜色
模式:控制灯光的光照模式,有“实时”“混合”和“烘焙”3种模式。
强度:控制灯光的明亮程度。
间接乘数:改变间接光的强度。
阴影类型:改变灯光照射后产生的阴影,有“硬阴影”“软阴影”“无阴影”3种模式。“软阴影”的效果最好,但是也更耗费资源。
剪影:灯光照明的纹理遮罩。
绘制光晕:是否显示光晕效果。
眩光:指定灯光的光晕对象,
剔除遮罩:设定受灯光照射的图层。
在性能上,点光源比定向光更加耗费资源,尤其是点光源的阴影。如果开启点光源的阴影,那么将会消耗大量的资源,所以在游戏的开发过程中一定要根据情况决定是否开启阴影,点光源的照明效果如图1所示。
壁灯、照射灯和手电筒等也均为聚光灯,可以把聚光灯理解为带了灯罩的点光源。
在性能上,聚光灯比点光源更耗费性能,聚光灯的照明效果如图所示。与点光源一样,阴影也是较为耗费资源的一项,需要按照情况来决定是否开启阴影。
区域光与上述灯光不同,它不是从点发射出光线,而是从面发射出光线。区域光提供了一个矩形平面,这个平面已经设置好了大小和方向,之后该平面将会向z轴面一起均匀地发射光线。区域光类似照相馆中的柔光灯,所以发射出的光线十分柔和。
使用好区域光,可以更好地为游戏场景营造对应的氛围,光线示意如所示。
区域光不支持实时照明,只支持烘焙照明,所以不存在性能的消耗问题。下个小节中将专门介绍光照烘焙,区域光的照明效果如2图所示。
光照烘培
随着设备性能的提高,游戏场景的制作也会趋于华丽和复杂,使用的灯光便会越来越多样。因此也考验着程序员对灯光的运用能力,但是大量的实时灯光对性能的消耗还是非常大的。针对这类问题,在移动端游戏或是游戏中有着大量拥有固定阴影的静态物体的情况下,Unity会将灯光制作成光照贴图,之后该物体就不用参与阴影的计算了。这种方法可以节约大量性能,这个过程就是烘焙。
选择你要做烘培灯光的物体,在检查器中选择contribute ui,在窗口-渲染-光照里,选择生成照明,即可。
2.设置环境光
在“照明”面板中,除了可以设置烘焙,还可以在“环境”一栏中对环境光进行设置。环境光是一种较为特殊的光源,它并不属于灯光,但是会对整个场景进行照明,如图所示。
重要参数介绍
天空盒材质:设置场景中的天空盒材质。该材质既可以在资源商店中加载外部资源,又可以在“项目”面板中创建材质球。将材质球的Shader设置为Skybox类型,即可创建自己的天空盒材质。
太阳源:设置一个定向灯光作为太阳源使用。
环境照明:设置环境光。环境光有“天空盒”“渐变”和“颜色”3种光源,可对其进行强度和模式的设置。
环境反射:设置环境光的反射效果,可以进行反射源和质量等的设置。
技巧提示
除了环境光,还可以进一步地对光照条件进行调节,如烘焙、光照贴图等;对雾效果、光晕和炫光效果等也可进行设置。
练习
摄像机
透视摄像机有着近大远小的效果,与我们在现实中看到的相同。正是因为透视摄像机存在近大远小的效果,所以当两个同样大小的物体到摄像机的距离不同时,我们可以看到物体的大小随距离的增加而减小,因此它们之间有明显的距离感,如图所示。透视摄像机广泛运用在各种3D游戏中,Unity的3D项目默认使用的就是透视摄像机,屏幕显示的画面如图所示。
正交摄像机没有透视摄像机具有的近大远小的效果,如图所示,所以当两个同样大小的物体到摄像机的距离不同时,其显示出的大小仍然是相同的,自然也不会产生距离感。这种摄像机一般用于照射平面,常常用在2D游戏和UI的开发上,屏幕显示的画面如图所示。
清除标志:设置空百区域内的显示信息,默认为天空盒。也就是说摄像机照射到的空白区域均会用天空盒来进行填充,除此之外还有“纯色”“仅深度”和“不清除”3种选项。
每个摄像机都不能确保整个屏幕内都显示游戏物体,有时候摄像机会照射到很多空白区域(物体外的其余部分就是空白区域)
纯色:代表空白区域用纯色来进行填充,纯色可以在“背景”选项中设置为自己需要的颜色
仅深度:该选项常被用来混合多个摄像机的内容。
技术专题:摄像机深度可以呈现的效果在离地面较远的位置创建一个球体,然后在“层级”面板中执行“创建>摄像机”命令创建一个摄像机,接着将摄像机调整到只能照射到球体的位置上
优先级低的在更深的位置,没有更低优先级就像看到深渊,可以理解需要两个画面叠加的场景,一个相机是主视角,另一个是叠加的3D效果。具体方案可以打开自己的脑洞。功能可多了,ui显示或是小地图功能
选择新建的摄像机,在“检查器”面板中设置“清除标志”为“仅深度”,同时确保新摄像机的深度属性值大于主摄像机的深度属性值,即可看到两个摄像机的内容产生了融合。从这个例子中可以看出,该模式清除了摄像机的深度信息,然后将深度交由深度属性来进行控制。
FOV轴 和视野调整,viewport矩形;;目标纹理则适合做监控镜子之类的东西
VOICE
如果想要在游戏中听到声音,那么就要为我们的角色添加耳朵。Unity中的AudioListener(音频监听器)组件即为我们的“耳朵”,只要确保场景中存在一个音频监听器,就可以听到场景中播放的声音。在游戏场景中,每个摄像机都会自动添加AudioListener组件。选择场景中的摄像机,在“检查器”面板中即可看到AudioListener组件,如图所示。
添加多个摄像机后运行游戏,“控制台”面板中会不断输出提示信息,说明游戏中有多个音频监听器,这时候只需要删除多余的AudioListener组件,只保留一个摄像机上的AudioListener组件即可。
- AudioClip(音频剪辑):该属性用于添加一个音频剪辑。将普通的音乐文件添加到Unity后,即可变成音频剪辑,Unity中常使用的音频格式有WAV、MP3、AIF和OGG。
- 空间混合:设置音频源为3D音频源还是2D音频源,3D音频源会随着距离的增加而减小音量。
- 3DSoundSettings(3D声音设置):当音频源为3D时,可对音频的扩张、衰减距离等进行设置。
在大多数时候,音频的播放都会通过脚本来进行控制。AudioSource组件对应的类为AudioSource,AudioSource的常用属性方法如表所示。
clip | 要播放的音频剪辑,音频剪辑对应的类为AudioClip | isPlaying | 当前是否正在播放音频 |
loop | 是否循环该音频剪辑 | mute | 设置音频静音 |
playOnAwake | 是否开启唤醒时播放 | time | 播放位置时间 |
volume | 音量大小 | Pause | 暂停播放 |
Play | 播放设定好的音频剪辑 | PlayOneShot | 播放一次音频剪辑 |
Stop | 停止播放 | UnPause | 恢复暂停播放 |
PlayClipAtPoint | 静态方法,在世界空间的某一点播放音频 |
1. 这里如果没有用public的话就不能在unity里拖上去 2. 而是需要在脚本里通过代码调用音频组件
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AudioTest : MonoBehaviour
{
//AudioClip
public AudioClip music;
public AudioClip se;
//播放器组件
private AudioSource player;
//Start is called before the first frame update
void Start()
{
player = GetComponent<AudioSource>();
player.clip = music;
//循环
player.loop = true;
//音量
player.volume = 0.5f;
//播放
player.Play();
}
//Update is called once per frame
void Update()
{
//按空格切换声音的播放和暂停
if (Input.GetKeyDown(KeyCode.Space))
{
//如果当前正在播放声音
if (player.isPlaying)
{
//暂停
//player.PauseO;
//停止
player.Stop();
}
else
{
//继续
//player.UnPauseO;
//开始播放
player.Play();
}
}
//按鼠标左键播放声音
if (Input.GetMouseButtonDown(0))
{
player.PlayOneShot(se);
}
}
}
实例

视频
创建渲染器纹理,选择渲染器纹理,将视频拖进去,
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Video;
public class VideoTest : MonoBehaviour
{
private VideoPlayer player;
//Start is called before the first frame update
void Start()
{
player = GetComponent < VideoPlayer > ();
}
// Update is called onceper frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
}
}
}
实例

玩家角色控制
1.直接到应用商店里面找,已经非常完善了,潜行,攀爬,跳水等等等等
2.在游戏的开发过程中,完善角色控制需要花费不少心思,Unity提供了CharacterController(角色控制器)组件来帮助用户进行简单的角色控制。除此之外,Character Controller组件还提供了一个胶囊体的碰撞外形,接 下来我们就来进行学习。
在场景中添加一个平面作为地面,再添加一个胶囊并命名为Player,作为场景中的角色,然后为角色添加CharacterController组件,如图所示。
斜度限制:设置爬坡的斜率上限。
每步偏移量:设置爬坡台阶的偏移量。
蒙皮宽度:设置角色皮肤的宽度。
最小移动距离:设置移动最小值,如果移动距离小于该值,则不会移动。
中心:设置中心位置的坐标点。
半径:设置碰撞胶囊的半径。
高度:设置碰撞胶囊的高度。
在制作游戏的过程中,常常需要判断当前角色是否已碰撞到地面,然后根据结果进行不同的逻辑操作。例如,我们常常在游戏中为游戏角色制作跳跃功能,制作逻辑一般是先判断角色是否碰撞到地面,如果没有碰撞到地面,那么证明其位于空中,这时候不允许跳跃:但是如果角色碰撞到地面,证明其目前不在空中,这时才允许角色进行跳跃,这样就能很好地防止角色因连续跳跃而飞到空中。下面分别展示角色碰撞到地面允许跳跃的状态和角色没碰撞到地面不允许跳跃的状态,如图所示。
CharacterController为我们提供了isGrounded属性,我们可以在脚本中通过直接获取该属性来判断当前角色是否碰撞到地面。灵活使用该属性可以使后续的编程更加简单,但是有些时候该属性出现的结果并不符合我们的预期,这时就要想办法判断角色是否接触地面。针对判断的方法,我们可以使用第5章中的碰撞回调进行编写,或是通过射线来进行地面碰撞检测,具体使用哪种方式可以根据游戏的需求来决定。
其实这种脚本的输入检测循环是最不消耗资源的,真正耗资源的是画面渲染和物理效果
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerControl : MonoBehaviour
{
private CharacterController player;
// Start is called before the first frame update
void Start()
{
player = GetComponent<CharacterController>();
}
// Update is called once per frame
void Update()
{
// 水平轴
float horizontal = Input.GetAxis("Horizontal");
// 垂直轴
float vertical = Input.GetAxis("Vertical");
// 创建成一个方向向量
Vector3 dir = new Vector3(horizontal, 0, vertical);
//Debug.DrawRay(transform.position, dir, Color.red);
// 朝向该方向移动
player.SimpleMove(dir * 2);
}
}
实例
编辑
物理系统
给物体添加组件Rinidbody
若要对物体进行控制(如移动与旋转),既可以使用Transform组件,也可以使用Rigidbody组件,但是这两种方式的呈现效果是有区别的。使用Transform组件控制物体会将重点放到物体的位置和旋转上,而使用Rigidbody组件控制物体则会将重点放在物体的力、速度和扭矩上。例如,当一个添加了Transform组件的物体持续撞向墙体时,可能会发生物体穿过墙体或抖动等现象,但是使Rigidbody组件控制物体时产生的现象会比较平滑。
- 质量:物体的质量,以kg作为单位。
- 阻力:用于表示当物体移动时会受到的空气阻力,0为不受阻力影响。
- 角阻力:用于表示当物体根据扭矩旋转时受到的空气阻力大小。
- UseGravity(使用重力):勾选该选项后,物体才会受到重力影响。
- IsKinematic(运动学):勾选该选项后,刚体不会受到物理特性的影响,但是仍然可以触发物理检测等功能。
- 插值:有“无”“插值”和“外推”3种插值方式。
- 无:不使用插值。
- 插值:根据上一帧的变化进行插值。
- 外推:根据下一帧的变化进行插值。
- 碰撞检测:有“离散的”“持续”“连续动态”和“ContinuousSpeculative”4种碰撞检测方式。
- 离散的:该选项为默认方式,表示不连续检测,比较节省性能,但高速物体可能会发生穿透现象而检测不到,如子弹穿墙。
- 持续:如果希望连续检测,那么被检测物体可以设置为此项(如墙体),但性能消耗较大。
- 连续动态:如果希望连续检测,那么快速移动的物体可以设置为此选项(如子弹),但性能消耗较大。
- ContinuousSpeculative(连续检测):性能消耗低于前两项连续检测,但仍然可能发生穿透现象。
- Constraints(冻结):冻结刚体在某个轴向的移动或旋转。
方法详解
position刚体的位置
rotation刚体的旋转
velocity刚体的速度
AddForce向刚体添加一个力
AddExplosionForce向模拟爆炸的刚体添加一个力
AddTorque向刚体添加一个扭矩
MovePosition移动刚体
MoveRotation旋转刚体
IsSleeping是否在休眠状态
Sleep让刚体进入至少一帧的休眠
WakeUp唤醒刚体
物理引擎会实时地对所有具有物理特性的物体进行运算,所以为物体添加Rigidbody组件后,也就意味着对物理引擎添加了
一个运算。但是具有物理特性的物体并非时时刻刻都ody组件的箱子在大多数时间内可能只是放置
在地面上,所以为了节省性能,物理系统设置了了休眠阈值。当判断物体的能量國值低于该休眠阈值时,会设定该物体为休眠模
式,省去了重复为该物体进行运动和碰撞检测的计算,节省了大量性能。
当处于休眠模式的刚体受到外界的影响时,刚体会被唤醒,继续参与物理运算。如果希望通过脚本来控制刚体的休眠状态,
那么可以调用刚体的Sleep和WakeUp方法让刚体进入休眠或从休眠中被唤醒。
在一般情况下不需要修改物理引擎的休眠阀值。如果想要修改,那么可以执行“编辑>项目设置>物理”菜单命令,在“选
项”面板中修改SleepThreshold(休眠阀值)。
实例
mesh collider网格碰撞器
procedural fire资源,火焰和爆炸效果,为了实现火焰调到平面消失并爆炸需要脚本
ExplosionTest.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ExplosionTest:MonoBehaviour
{
float timer=0;
//Start is called before the first frame update
void Start(){}
// Update is called once perframe
void Update()
timer += Time.deltaTime;
if (timer > 1){
Destroy(gameObject);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FireTest : MonoBehaviour
{
// 创建一个爆炸预设体
public GameObject Prefab;
void Start()
{
}
void Update()
{
}
// 监听发生碰撞
private void OnCollisionEnter(Collision collision)
{
// 创建一个爆炸物体
Instantiate(Prefab, transform.position, Quaternion.identity);
// 销毁自身
Destroy(gameObject);
// 获取碰撞到的物体
Debug.Log(collision.gameObject.name);
}
// 持续碰撞中
private void OnCollisionStay(Collision collision)
{
}
// 结束碰撞
private void OnCollisionExit(Collision collision)
{
}
}
实例
触发检测与碰撞检测
写一个角色控制器PlayerControl.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerControl : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
// 获取水平轴输入值
float horizontal = Input.GetAxis("Horizontal");
// 获取垂直轴输入值
float vertical = Input.GetAxis("Vertical");
// 创建一个向量,表示移动方向
Vector3 dir = new Vector3(horizontal, 0, vertical);
// 将角色朝向输入的方向移动,移动速度为2
transform.Translate(dir * 2);
}
}
在游戏对象-对齐视图这个方法,可以把也可以在选中物体让物体移动到你的位置
乱飞的把CharacterController组件移除。刚写的控制脚本不需要获取CharacterController
在Unity中,除了可以对游戏物体进行碰撞检测,还可以对其进行触发检测。我们可以看到大多数碰撞器都有一个“是触发器”选项,勾选该选项后,该碰撞器就会变成一个触发器。触发器并不会产生碰撞,当一个游戏物体碰到碰撞器时会发生碰撞,但是碰到触发器则会穿过触发器而不会产生碰撞。触发检测脚本中的方法与碰撞检测的类似,代码如下。
技巧提示
触发器非常有用,如在一个空房间中创建一个触发器,一旦判断有玩家角色进来就动态地创建一个敌人:也可以在NPC附近创建一个触发器,当玩家角色进来时NPC会看向玩家角色并发生对话:也可以将游戏中的金币设置为触发器,当玩家角色碰到金币时销毁金币,并触发吃金币的事件。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Video;
public class CubeControl : MonoBehaviour
{
//Start is called before the first frame update
void Start()
{
}
void update()
{
}
private void OnTriggerEnter(Collider other)
{
GameObject door =GameObject.Find("Door");
if (door != null)
{
door.SetActive(false);
}
}
private void OnTriggerStay(Collider other)
{}
private void OnTriggerExit(Collider other)
{
}
}
物理关节
添加组件hinge joint
40.铰链、弹簧,特殊的物理关节_哔哩哔哩_bilibili
材质
若希望在这个斜坡上的物体都向下滑动下去,而非滚动下去,该怎么做呢?
这就需要创建物理材质。在“项目”面板中执行“新建>物理材质”命令创建一个物理材质,然后将其命名为“斜坡”。在“项目”面板中选择“斜坡”,即可在“检查器”面板中看到该物体的物理材质的属性。
重要参数介绍
DynamicFriction(动摩擦力):数值范围为0~1。
StaticFriction(静摩擦力):静摩擦力,数值范围为0~1。
Bounciness(弹力):数值范围为0~1。0代表无弹力,1代表弹力最大。
FrictionCombine(摩擦力组合):摩擦力的组合模式。
Average(平均值):使用两个摩擦力的平均值。
Minimum(最小值):使用两个摩擦力中的最小值。
Multiply(乘积):使用两个摩擦力的乘积。
Maximum(最大值):使用两个摩擦力中的最大值。
BounceCombine(弹力组合):与“摩擦力组合”类似。
将动摩擦力、静摩擦力均设置为0,并将该材质拖曳到斜坡上的MeshCollider组件的“材质”选项框中。立方体从向下滚动变成向下滑动
实例
射线检测
射线就是从一个固定点向一个方向发射出的一条直线,在发射过程中需要判断该条射线有没有与游戏物体发生碰撞。射线既可以用来检测射击游戏中武器指向的目标,又可以判断鼠标指针是否指向了游戏世界中的游戏物体;还可以通过从敌人位置向前方发射射线来判断敌人的前方是否有其他角色。射线的创建非常简单,点击下方的屏幕实际上是相机的矩形区域射线行摄到的物体上。
oiiaioiiai huh happy banana
创建RayTest脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RayTest : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
// 方式1:从原点向上发射射线(被注释掉了)
// Ray ray = new Ray(Vector3.zero, Vector3.up);
}
// Update is called once per frame
void Update()
{
// 方式2:检测鼠标左键是否被按下
if (Input.GetMouseButtonDown(0))
{
// 按下鼠标左键时,从鼠标位置发射射线
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// 声明一个碰撞信息类
RaycastHit hit;
// 进行碰撞检测
bool res = Physics.Raycast(ray, out hit);
// 如果碰撞到的情况下,hit就有内容了
if (res == true)
{
// 输出碰撞点的位置
Debug.Log(hit.point);
transform.position=hit.point
}
}
}
}
这里out hit是传参,就是第二个返回值,可以看作是给hit赋值
多点同一个地方你会发现小球越来越大,多点同一个地方不是球大小变了,是球离屏幕越来越近了,球提前碰撞射线了
//多检测
//RaycastHit[hits =Physics.RaycastAll(ray, 100, 1<<10);
void Update()
{
//从屏幕(摄像机)一点向游戏世界发射一条射线,该射线常用于检测鼠标指针有没有选择游戏物体
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
//声明一个碰撞信息数组
RaycastHit[] hits;
//射线检测,并返回所有的碰撞信息
hits = Physics.RaycastAll(ray);
hits = Physics.RaycastAll(Vector3.zero, Vector3.up);
//添加射线检测的距离设置
hits = Physics.RaycastAll(ray, 100);
hits = Physics.RaycastAll(Vector3.zero, Vector3.up, 100);
//只与第10个图层上的物体碰撞
hits = Physics.RaycastAll(ray, 1000, 1<<10);
hits = Physics.RaycastAll(Vector3.zero, Vector3.up, 1000, 1<< 10);
}
技术专题:射线与图层的碰撞
有时在射线碰撞检测的过程中,我们并不希望射线对所有的物体都进行碰撞检测。例如,有一个场景,场景中放置了地面和房屋,我们希望通过射线检测判断我们当前单击的地面的坐标是多少,这时我们就需要让射线忽略与房屋的碰撞,而图层也就派上用场了。
在前面的3.2节中我们认识了图层,也学习了给不同的物体设置不同的图层,图层的设置面板如图5-47所示。
图5-47中显示了Unity编辑器中的图层设置面板,列出了不同的图层名称,如Default(默认)、TransparentFX(透明效果)、Ignore Raycast(忽略射线检测)等,以及User Layer 0到User Layer 31等用户自定义图层。
现在如果我们要实现射线只与地面发生碰撞,我们就需要先将地面与房屋设置为不同的图层,然后在脚本中进行射线碰撞检测。在前两个小节中我们知道射线碰撞检测的方法有很多种,这里使用其中一种射线碰撞检测的API来进行讲解,即Physics.RaycastAll(Ray ray,float maxDistance,int layerMask)。我们在该API中可以看到,有些射线的碰撞检测方法中包含了layerMask(图层遮罩)选项,而layerMask可以让我们设定该射线对哪个图层或不与哪个图层进行碰撞检测。
例如,将地面图层设置为10,那么在layerMask参数中填写1<<10就代表只与第10个图层进行碰撞检测,这样即便射线碰撞到房屋也不会进行检测。这种方法非常简单而且十分实用。
Comments NOTHING