?它是一套具有完善体系与编辑器的跨平台游戏开发工具,也称之为游戏引擎。游戏引擎是指一些编写好的可重复利用的代码与开发游戏所用的各功能编辑器。
右上边可以切换布局。
左边选择Shaded wireframe,可以看到3D物体的都是由三角形组成的。
Unity使用左手坐标系。
空间中统一的坐标系叫世界坐标系,每个物体有自己的本地坐标系,该坐标系为相对于父级物体的位置而不是世界坐标系的位置。
网格(三角形构成)+ 材质
Windows -> Asset Store
挑出喜欢的资源之后,点击”添加至我的资源”。
然后在Unity的Window -> Package Manager中找到刚才的资源。
点击下载之后再导入到untiy中。
选中“手形”按钮,然后按住Alt键,此时拖动鼠标即可旋转xyz坐标系。
一般先通过“Set Height”功能设置全局的高度,然后再通过“Raise or Lower Terrain”来抬高或者降低地形,就可以绘制高山和峡谷了。
首先,下载unity的资源包
然后,绘制纹理
创建地形层:
Edit Terrain Layers -> Create Layer,选择一个素材后地形如下:
可再添加一个地形层,用于绘制道路如下:
接着,绘制树
按住鼠标右键,然后通过A、W、S、D按键可以切换观察的视角。
在Unity中,功能基本上都是由组件来实现的。如果想要一个物体移动,那么为它添加一个有移动功能的组件就可以了。
每个游戏的物体都是一个空物体,它之所以表现为不同的形式,都是由它的组件表现出来的。由于组件(cube组件、light组件)不同,所以表现出来的形态也不同。
选择物体后,通过右下角的“Add Component”功能,选择Rigidbody组件,然后选择执行,那么该物体就会垂直下落。
创建C#脚本,然后放到物体下面就可以了。
创建一个脚本之后,会看到默认的两个方法Start()和Update()方法。
全部的生命周期方法为:
Awake:最早调用,一般可以在此实现单例模式
OnEnable:组件激活后调用,在Awake后会调用一次
Start:在Update之前调用一次,在OnEnable之后调用,可以在此设置一些初始值
FixedUpdate:固定频率调用方法,每次调用与上次调用的时间间隔相同
Update:帧率调用方法,每帧调用一次,每次调用与上次调用的时间间隔不相同
LateUpdate:在Update每调用完一次后,紧跟着调用一次。
OnDisable:与OnEnable相反,组件未激活时调用
OnDestory:被销毁后调用一次
在方法中打印日志使用Debug.Log(“”);
然后在Window -> General -> Console 看到日志输出
如果针对一个物体创建了两个脚本,在默认情况下脚本的执行顺序是不定的。
有两种方法可以指定方法的执行顺序。
方法一:
在每个脚本不同的生命周期函数中编写功能,比如脚本1的Awake函数中执行功能1,脚本2的Start函数中执行功能2,这样可以保证先执行功能1,再执行功能2。
所有的脚本都是按照生命周期,先执行完所有的Awake函数,再执行所有的OnEnable函数,依此类推。
方法二:
直接指定各个脚本的优先级。
标签的作用:一般是自己用的,可以用来区分每个的角色,比如是敌人还是玩家。
图层的作用:一类内容,通过图层可以判断哪一类显示,哪一类不显示。比如某个图层的物体需要检测碰撞,而另一个图层的内容( 比如地面)不需要检测碰撞。
标量:只有大小的量,1 85 888 999
向量:既有大小,也有方向
向量的模:向量的大小
单位向量:大小为1 的向量
单位化、归一化:把向量转为单位向量的过程。
向量的点乘:A向量 点乘 B向量 = 数值n = |A||B|cos&,得到两个向量之间的夹角。
现在做了如下的物体(总共由4个部分组成),
假如现在需要4个这种物体,
如果在SampleScene界面直接再复制3个出来,那么假如这个物体需要改动,那么需要改动4个物体,怎么才能只修改一个物体,其他的同步生效?
这就需要用到预制体。
把物体从SampleScene复制到Assets中,然后再复制3个Enemy到SampleScene中,如下图所示:
?此时如果修改Assets中的Enemy,则直接双击进行修改,修改完成后所有的都会生效。
所有都生效如下图所示:
现在有新的任务:需要新增一个物体,新物体比之前4个物体只增加一顶帽子,其他的部分需要与之前4个保持一致。即除了帽子外,4个物体做了改动,当前新物体也需要同步做改动。此时需要用到变体的功能。
当在SampleScene中创建了新物体,然后拖动到Assets时提示如下,选择Prefab Variant(预制体变体)。
此时在Assets的Enemy中做修改,5个会同时生效,比如把红色嘴巴变小了之后,
此时,预制体修改会影响变体,但是变体修改不会影响预制体。
第一,Vector3可以表示:
1)向量(坐标)
Vector3 v = new Vector3(1, 1, 1);
2)旋转
Vector3 v = new Vector3(45, 90, 0);
3)缩放
Vector3 v = new Vector3(1, 1, 0.5f);
第二,系统提供的静态初始化方法
v = Vector3.zero;
第三,计算两个向量之间的关系
// 向量,坐标,旋转,缩放
Vector3 v = new Vector3(1, 1, 0.5f);
v = Vector3.left;
Vector3 v2 = Vector3.forward;
// 计算两个向量的夹角
Debug.Log(Vector3.Angle(v, v2));
// 计算两个向量的举例
Debug.Log(Vector3.Distance(v, v2));
// 点乘
Debug.Log(Vector3.Dot(v, v2));
得到的结果:
欧拉角:从0到360度
四元数:比欧拉角要强大很多,效率更高,也不会造成万向节死锁
代码示例:
// 欧拉角
Vector3 rotate = new Vector3(0, 30, 0);
// 四元数
Quaternion quaternion = Quaternion.identity; // 创建了一个四元数,但是无旋转
quaternion = Quaternion.Euler(rotate); // 通过欧拉角创建四元数
// 看向一个物体
quaternion = Quaternion.LookRotation(new Vector3(0, 0, 0)); // 看向一个物体肯定要做旋转
// 四元数转欧拉角
rotate = quaternion.eulerAngles
(1)打印日志的方式
Debug.Log("test1");
Debug.LogWarning("test2");
Debug.LogError("test3");
(2)绘制线条的方式
// 绘制一条线(起点,终点)
Debug.DrawLine(Vector3.zero, Vector3.one, Color.blue);
// 绘制一条射线(起点,射线)
Debug.DrawRay(Vector3.zero, Vector3.up, Color.red);
一个游戏物体,上面包含很多组件,每个组件不同的功能,这样一个物体就有不同的功能。每一个游戏物体都是GameObject类。
(1)拿到当前脚本所挂载的游戏物体
GameObject go = this.gameObject;
Debug.Log(go.name);
(2)在当前脚本中操作子物体(另一个物体)
首先,是在脚本中定义一个子物体。
然后,当前物体就增加了一个属性
接着,在SamplesScene中创建子物体Cube,并拖到Script的Cube中,即实现了物体和属性的关联,此时脚本中的Cube有值了。
(3)获取游戏物体并进行操作
// 获取Transform组件
Debug.Log(transform.position);
// 获取其他组件
BoxCollider bc = GetComponent<BoxCollider>();
// 添加一个组件
Cube.AddComponent<AudioSource>();
// 通过游戏物体的名称来获取游戏物体
GameObject test = GameObject.Find("Test");
Debug.Log(test.name);
// 通过游戏标签获取游戏物体
test = GameObject.FindWithTag("Enemy");
Debug.Log(test.name);
test.SetActive(false); // 设置激活状态
(4)创建物体
通过预设体(类)创建物体(实例)。
首先,声明一个物体
然后,把一个预设体关联上该变量,操作同《在当前脚本中操作子物体(另一个物体)》
接着,脚本中创建物体
Instantiate(Prefab);
或者实例化一个物体并指定位置信息:Instantiate(Prefab, Vector3.zero, Quaternion.identity);
(5)销毁物体
// 通过预设体来实例化一个游戏物体
GameObject go = Instantiate(Prefab, Vector3.zero, Quaternion.identity);
// 销毁
Destroy(go);
时间统计方法
// 游戏开始到现在所花的时间
Debug.Log(Time.time);
// 时间缩放值
Debug.Log(Time.timeScale);
// 固定时间间隔
Debug.Log(Time.fixedDeltaTime);
// 上一帧到这一帧所用的游戏时间
Debug.Log(Time.deltaTime);
// 游戏数据文件夹路径(当前Assets所处的路径,只读且加密压缩)
Debug.Log(Application.dataPath);
比如:D:/workspace/unity/FirstProject/Assets
// 持久化文件夹路径(系统给应用程序分配的存放数据的空间,可写)
Debug.Log(Application.persistentDataPath);
比如:C:/Users/hugh/AppData/LocalLow/DefaultCompany/FirstProject
// StreamingAssets文件夹路径(只读不压缩,可放配置文件等不需要压缩的文件)
Debug.Log(Application.streamingAssetsPath);
比如:D:/workspace/unity/FirstProject/Assets/StreamingAssets
// 临时文件夹
Debug.Log(Application.temporaryCachePath);
比如:C:/Users/hugh/AppData/Local/Temp/DefaultCompany/FirstProject
// 控制是否在后台运行
Debug.Log(Application.runInBackground);
// 打开url
Application.OpenURL("http://www.baidu.com");
// 退出游戏
Application.Quit();
Scenes目录右击 -> Create -> Scene
从SampleScene场景跳转到MyScene场景。
第1步,生成场景的索引号
File -> Build Settings
拖动场景到”Scenes In Build”中。
此时就生成了场景的索引号信息。
第2步,在脚本中实现跳转
场景涉及到Scene、SceneManager两个类。
同一时间可以存在多个场景,场景是可以叠加在一起的。
示例代码如下:
//场景跳转
//SceneManager.LoadScene(0); // 通过索引方式跳转
//SceneManager.LoadScene("MyScene"); // 通过名称方式跳转
// 获取当前场景
Scene scene = SceneManager.GetActiveScene();
// 场景名称
Debug.Log(scene.name);
// 场景是否已经加载
Debug.Log(scene.isLoaded);
// 场景路径
Debug.Log(scene.path);
// 场景索引
Debug.Log(scene.buildIndex);
GameObject[] gos = scene.GetRootGameObjects();
Debug.Log(gos.Length);
// 场景管理类
// 创建新场景
Scene newScene = SceneManager.CreateScene("newScene");
Debug.Log(SceneManager.sceneCount); // 当前激活的场景个数
// 卸载场景
SceneManager.UnloadSceneAsync(newScene);
// 加载场景的两种方式
//SceneManager.LoadScene("MyScene", LoadSceneMode.Single); // 替换方式的加载
SceneManager.LoadScene("MyScene", LoadSceneMode.Additive); // 叠加方式的加载
打印的日志:(测试场景资源太少,所以立马就加载完毕了,显示0.9)
它有两个作用:
1)控制物体的位置、旋转、缩放
2)控制父子级的从属关系
演示Transform的使用
首先,创建父子孙物体Parent\Sphere\Child,并将脚本TransformTest挂在Sphere物体上。
// 获取位置
Debug.Log(transform.position); // 世界坐标
Debug.Log(transform.localPosition); // 相对父级的位置
// 获取旋转
Debug.Log(transform.rotation);
Debug.Log(transform.localRotation);
Debug.Log(transform.eulerAngles);
Debug.Log(transform.localEulerAngles);
// 获取缩放
Debug.Log(transform.localScale);
// 向量
Debug.Log(transform.forward);
Debug.Log(transform.right);
Debug.Log(transform.up);
// 时时刻刻看向000点
?transform.LookAt(Vector3.zero);
?// 旋转(自转)
?transform.Rotate(Vector3.up, 1);
?// 旋转(公转)
?transform.RotateAround(Vector3.zero, Vector3.up, 1);
?// 移动
?transform.Translate(Vector3.forward * 0.1f);
// 获取父物体
GameObject go = transform.parent.gameObject;
// 子物体个数
Debug.Log(transform.childCount);
// 解除与子物体的父子关系
transform.DetachChildren();
// 获取子物体
Transform trans = transform.Find("Child"); // 根据名称获取
trans = trans.GetChild(0); // 根据索引获取
// 判断一个物体是不是另外一个物体的子物体
bool res = trans.IsChildOf(transform);
// 设置为父物体
trans.SetParent(transform);
对于这两个设备的监听,需要每一帧都监听,所以逻辑要写在Update()方法中。
// 鼠标的点击
// 按下鼠标 0左键 1右键 2滚轮
if (Input.GetMouseButtonDown(0))
{
Debug.Log("按下了鼠标左键");
}
// 持续按下鼠标
if (Input.GetMouseButton(0))
{
Debug.Log("持续按下鼠标左键");
}
// 抬起鼠标
if (Input.GetMouseButtonUp(0))
{
Debug.Log("抬起了鼠标左键");
}
// 按下键盘按钮
if (Input.GetKeyDown(KeyCode.A))
{
Debug.Log("按下了A");
}
if (Input.GetKey(KeyCode.A))
{
Debug.Log("持续按下了A");
}
if (Input.GetKeyUp(KeyCode.A))
{
Debug.Log("松开了A");
}
适配各个平台,通过虚拟轴来控制上下左右、跳跃等操作。否则电脑上使用WSAD控制上下左右,而在游戏手柄中使用摇杆。
打开Edit -> Project Settings
在Input Manager中,只有Horizontal和Vertical是虚拟轴(有多个值),其他的是虚拟按钮。
// 获取水平/垂直轴
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
Debug.Log(horizontal + " " + vertical);
// 虚拟按键
if (Input.GetButtonDown("Jump")) {
Debug.Log("空格");
}
创建灯光:Light -> Point Light
四种光源类型:
此时移动光源,阴影的角度不会发生改变
当旋转光源时,阴影的方向是会变化的。
随着光源的移动,照射的位置会发生变化
类似摄影棚的聚光灯效果
硬阴影:阴影有锯齿状,系统性能开销较低
软阴影:边缘有羽化模糊效果,系统性能开销大一些
实时:实时计算出来的,比如赛车时车灯照射到的地方,非常耗性能
烘培:提前保存灯光照射数据,然后把灯光去掉,灯光效果仍然保留
点击如下按钮:
该按钮的意思:toggle visibility of all Gizmos in the Scene view
即:在“场景”视图中切换所有Gizmo的可见性
两种摄像机类型:透视摄像机、正交摄像机
该摄像机有着近大远小的特点,与我们现实中看到的相同。
??????
没有近大远小的效果,当两个同样大小的物体到摄像机的距离不同时,其显示出的大小仍然是相同的。
透视和正交的选项如下:
3D的一般是透视,2D的一般是正交。多个摄像机可以融合在一起,深度的处理。
音乐:比较长的背景音乐,在场景中往往是循环播放
音效:比较短的声音,比如射击的声音
一个场景中只能同时存在一个音乐,但是可以同时存在很多的音效。
如果希望游戏中可以听到声音,必须要有一个组件在场景中存在,即”Audio Listener”,该组件用于接收声音。位于摄像机的物体中,同时该组件在一个场景中只能存在一次。
方法一:直接在Inspector中进行配置
选中一个物体,然后添加“Audio Source”的组件。
AudioClip:音频剪辑,所有的声音都属于音频剪辑。
方法二:在脚本中控制播放
代码如下:
public class AudioTest : MonoBehaviour
{
// Start is called before the first frame update
// AudioClip
public AudioClip music;
public AudioClip se;
// 播放器组件
private AudioSource player;
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.Pause();
}
else {
player.UnPause();
}
}
// 按鼠标左键播放声音
if (Input.GetMouseButtonDown(0))
{
player.PlayOneShot(se);
}
}
}
第一步,创建渲染器纹理(Assets -> Create -> Render Texture)
第二步,创建平面,后面我们希望在平面上显示视频(SampleScene -> 3D Object -> Plane)
第三步,在平面上增加组件“Video Player”,执行Video Clip为我们准备的视频文件,然后在Render Mode中选择Render Texture,然后把第一步创建的纹理拖过来。
第四步,把纹理应用到平面上,拖过去即可。
最后,点击运行即可。
角色控制的三种解决方案:
本次使用第二种即角色控制器。
首先,创建画板和胶囊
然后,添加角色控制组件,Character Controller。
接着,创建脚本。
public class PlayerController : MonoBehaviour
{
// Start is called before the first frame update
private CharacterController player;
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);
// 朝向该方向移动
player.SimpleMove(dir * 2);
}
}
在物体的Mesh Collider中进行设置。
在组件中存在很多的碰撞器,比如Mesh Collider(网格碰撞器)、Terrain Collider(地形碰撞器)等。
两个物体如果要碰撞的前提条件:
1)两个物体都有碰撞器。
2)至少一个物体有RigidBody(刚体),即受重力影响
碰撞示例:
触发的条件同碰撞一样,即都有碰撞器、至少一个有刚体。
触发和碰撞的区别:
示例:
一个胶囊物体+2个触发器的Cube。
胶囊物体(模拟玩家)的代码如下:
左下角隐藏物体的代码:
private void OnTriggerEnter(Collider other)
?{
???? GameObject wall = GameObject.Find("Wall");
???? if (wall != null){
???????? wall.SetActive(false);
???? }
?}
它们都属于物理关节。
铰链:类似于门的关节,通过这个关节实现门的旋转
弹簧:将两个物体连接在一起,两个的运动有弹簧的效果。
铰链的示例:
蓝色的Cube设置铰链的效果。
首先,设置rigidBody刚体属性。
然后,增加Hinge Joint铰链,设置Anchor和Axis属性,分别控制铰链的位置和方向。
在平面上,鼠标按一个位置(鼠标无法在3d上指定一个具体位置),物体就会移动到这个位置上,这个怎么实现的?
点击一个位置后,从摄像机发射一个射线到指定的方向,该射线与平面接触后即得到了具体位置,然后就可以让物体移动到该位置。
代码示例:
void Update()
{
if (Input.GetMouseButtonDown(0))
{
// 按下鼠标左键发射射线
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// 声明一个碰撞信息类
RaycastHit hitInfo;
// 碰撞检测
bool res = Physics.Raycast(ray, out hitInfo);
// 如果碰撞到的情况下,hitInfo就有内容了
if (res)
{
transform.position = hitInfo.point;
}
}
}
特效相关的功能
使用:右击 -> Effects -> Particle System
动画有老版(Animation)和新版(Animator)两个组件。
目前动画组件中缺失动画。
制作动画(一个cube左右移动的动画):Window -> Animation -> Animation打开制作面板。
首先,创建动画剪辑。
然后,添加属性(比如Transform的Position)
点击播放可以进行预览。
然后将该动画放到Animation组件的Animation Clip中。
使用示例:
首先,创建Animator组件,该组件需要输入Animator Controller信息。
然后,在Assets中Create -> Animator Controller,该控制器如下图所示,同时将该控制器拖入Animator组件的Controller选项中。
接着,创建Animation。选中物体后Window -> Animation,创建right
接着,继续创建动画脚本left。
接着,再点击Animator的Controller选项中的Animator Controller,会出现如下画面。
Right高亮,这表明程序进入后直接执行right动画。
最终,通过代码演示点击鼠标后切换为left动画。
public class AnimatorTest : MonoBehaviour
{
private Animator animator;
// Start is called before the first frame update
void Start()
{
animator = GetComponent<Animator>();
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0)) {
animator.Play("left");
}
}
}
首先,下载资源Character Pack: Free Sample
然后,创建Plane,然后把上一步的角色放进去。角色位于:Prefabs -> High Quality -> MaleFree1.
接着,创建动画控制器,并将该控制器拖到角色的Animator的Controller里面。
双击Controller之后,将FBX的动画文件拖到Animator中即可执行。
如果想要一个角色执行两个动作,就拖入两个动画脚本,然后通过“Make Transition”关联起来。如下所示:
此时,角色会循环执行第1、2个动作。如何实现通过按键在第1、2个动作之间进行过度?此时需要在第1、2个动作之间增加条件。
在Animator面板的Parameters-> “+” -> Trigger,
此时点击左上角的pickup就会立马触发第2个动作。
怎么在代码中实现触发?
补充说明:Trigger只是触发一下,没有持续性。
参数类型选择bool,会永久性进行切换
public class PlayerController4 : MonoBehaviour
{
// Start is called before the first frame update
private Animator animator;
void Start()
{
animator = GetComponent<Animator>();
}
// 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);
// 当用户按下方向键
if(dir != Vector3.zero)
{
// 面向向量
transform.rotation = Quaternion.LookRotation(dir);
// 播放跑步动画
animator.SetBool("isRun", true);
// 朝向前方移动
transform.Translate(Vector3.forward * 2 * Time.deltaTime);
}
else
{
animator.SetBool("isRun", false);
}
}
}
Model:模型相关的信息
Rig:动画类型:无(当前文件不包含动画),旧版(只能使用Animation组件),其他的泛型和人形都是Animator才有的。泛型支持所有类型。人形:官网提供的各种人形动画,可以套在人形的物体中,这样对于人形比较通用。
Animation:动画,可以生成新的动画剪辑。
Curves:曲线,该曲线对动画不产生影响,该曲线是为了外部能感受到动画的细节以便做出相应的指令。比如,角色挥拳产生火焰,可以根据曲线决定火焰的大小。
Events:事件。当运动到某一动作时指定一个触发事件(回调函数)。
然后在脚本中编写leftFoot的功能。
void leftFoot()
{
??? Debug.Log("左脚!");
}
在Animator面板的Base Layer中右击 -> Create State -> From New Blend Tree
双击Blend Tree可以进入子目录。
在右侧的List中增加2个motion如下:
通过blend的值控制融合的权重。
默认情况下是一个Base的图层,如下所示:
它有3个状态,
还可以创建子状态集如下:
子状态集进去后如下:
后续可以针对一个人物创建多个子状态集,比如拿刀的是一个子状态,拿枪的是一个子状态,然后人物可以在状态之间相互切换。
功能:我们希望这个人物走到哪里,都可以看向这个球,并且手也指向这个球。
何为反向动力学:
在正向动力学中,一个人抬起手的顺序是1、2、3,即先抬起胳膊、然后抬起手臂、再抬起手。而反向动力学是反过来,即3、2、1。
IK动画:脚本控制模型骨骼动画。
代码示例(在第40节中增加如下代码):
public Transform target; // 人物指向的物体
// IK写到这个方法内
private void OnAnimatorIK(int layerIndex)
{
// 设置头部IK
animator.SetLookAtWeight(1);
animator.SetLookAtPosition(target.position);
// 设置右手IK权重
animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1); // 除了头部其他都采用枚举值
// 设置右手IK权重
// 旋转(举起手旋转)
animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1);
// 设置右手IK
animator.SetIKPosition(AvatarIKGoal.RightHand, target.position);
animator.SetIKRotation(AvatarIKGoal.RightHand, target.rotation);
}
效果如下:
导航组件是用来智能寻路的。
哪些区域可以走,哪些区域不可以走,是对当前的场景生成网格来实现的,网格内的可以走,网格外的不可以走。
首先,选中需要制作网格的物体后,点击Navigation->Object,Navigation Static打勾。
然后,点击Base选项,设置参数完毕后,点击最下面的bake
就生成了网格区域。
Agent Radius:代理半径,就是模拟人的宽度
Agent Height:代理高度,就是模拟人的高度
Max Slope:允许行走的倾斜度
Step Height:允许人走的每一步的高度
首先,给胶囊(模拟人)添加导航代理组件。
Agent Type选择”Open Agent Settings”可以设置多个代理,比如人有高有矮,就可以设置不同参数的代理。
然后,通过脚本控制胶囊移动
当前场景上下两部分被中间的墙隔断了。
有时我们希望一个城墙移动后,两边就打通了,做成动态障碍物,而不是现在的静态障碍物。
做法:
第1步,把Door的navigation static反勾选
?
?第2步,给Door添加组件Nav Mesh Obstacle,在该组件中勾选Carve,此时就会进行动态切割,也就是动态烘焙。
此时,把中间的door打开,物体就可以两边自由行走。而把door关闭,就只能单边行走了。
任务一:从一个区域直接跳到另一个区域,比如直接从台上跳到地面上。
首先,选择需要跳的台面,点击该台面的Navigation,勾选Generate OffMeshLinks,就创建了网络链接。
然后,选择胶囊,点击Navigation,在Generated Off Mesh Links,填写Drop Height的值(允许跳落得高度),就会在Scene上看到可挑落点。
任务二:从一个地方跳到另一个指定得位置
比如,从白点1的位置,希望可以瞬间转移到白点2的位置。
做法:
首先,准备素材
搜索资源“Fantasy Free GUI”
然后,创建画布
创建完毕后如下图所示:
当创建完画布Canvas后,系统检测到没有事件系统,会自动创建EventSystem。后续在画布上增加按钮,就可以响应按钮的点击事件了。
画布在3D世界中就是如上图的白色线框,默认线框的比例和下方游戏窗口的比例是一致的。
Canvas组件的RenderMode参数:
右击Canvas,选择UI -> Image,然后在Source Image中选择一张图片
1)Screen Space-Overlay 屏幕空间-覆盖模式
覆盖模式跟摄像机无关,画布上的图像优先级最高,总能出现在屏幕的最前方,覆盖所有其他的物体。
2)Screen Space-Camera 屏幕空间-摄像机
就是根据摄像机拍摄的角度,显示内容。需要在Render Camera中拖入一个摄像机,如下图所示,可以看到cube挡住了画布上的图像。
3)World Space 世界空间
与Camera相似,但是可以旋转画布,此时就可以操作Rect Transform的参数。
Canvas Scaler组件的UI Scale Mode参数
此时无论游戏屏幕怎么变化,最终游戏都会根据1920*1080进行适配显示。
下图中左边红框的为画布的瞄点,而图片的PointX、PointY的坐标即为图片的轴心相对瞄点的坐标。
假如将画布的瞄点拆分为矩形的四个顶点,那么随着设备的分辨率变化,图片会进行相应的伸缩。否则图片大小固定,在不同分辨率下的设备呈现不同的显示。
有两个地方可创建文本和按钮。
旧版UI->Legacy,两者的区别是组件不同。
更改按钮的图标:
Button设置中的Transition可以设置为Sprite Swap(精灵类型,可包含图片)。
按钮触发事件:可把脚本挂在到Canvas上。
然后点击Button,选择对象上的函数进行关联。此时选择Canvas对象的对应的ButtonTest的ButtonClick1函数,其他几个button可依次这样进行操作。
位于UI -> Legacy -> Input Field
输入框有3个事件,使用方法和Button的事件相似。
新旧版输入框脚本上的差异:
位于UI -> Toggle
怎么保证只能2选1呢?
选择Canvas后,添加组件Toggle Group
然后分别选择Toggle1和Toggle2,Group参数选择刚才Canvas中创建的组件
位于:UI -> Legacy -> Dropdown
动态添加选项的方法:
在界面中我们作图,完成后如下图所示:
在不同的分辨率下会呈现不同的结果,如下图所示:
此时需要通过Panel来固定位置:
选择UI -> Panel,然后选择左上角对齐锚点,反选Image。
此时改变显示屏的分辨率,显示的图像位置都不会发生变化。