unity
unity
基础
常识
重要的几个面板
- Hierachy所有游戏中对象的层级面板
- Sence当前的游戏场景
- Game当前游戏画面会如何
- Project项目中的所有资源
- Inspector某一个对象/资源的属性
- Window->Package Manager 管理安装的第三方包
Sence中常用快捷键
- alt+滚轮缩放
- 鼠标右键+wasd移动
- 鼠标中键 原地不动360度拖动场景
- ctrl+D快速复制
- ctrl+shift+F快速对焦摄像机到编辑模式视窗
游戏是由多个后缀为unity文件的Sence组合而成。新建的对象会存放在Sence里,所以一个Sence里面的对象过多会消耗性能。
unity是基于组件设计的,多个相似功能的组件会组成某一方面的系统。
3D中默认的模型用的不多,cube的长宽高都是1(体积是1立方米),可以用来堆叠测距离。 材质也是一种美术资源。
模型是由顶点组成的网格数据,双击Object的Inspector->Mesh
可以查看网格信息,也可以预览网格,实际上他是个什么模型都不要紧改Mesh就修改了模型的实际形状
材质决定了外观。渲染材质在Inspector->Mesh Renderer
中可以修改,默认材质是default材质白的,如果为None渲染丢失Unity会把它变成紫红色。
材质是创建的mat文件,材质可以附着在多个Object上,只要材质改变Object的显示都会改
对象编组的时候中心有两种模式,Povit(以主模型为中心),Center(整组的中心)。
对象旋转的时候朝向会发生改变,此时有两种模式,即Global(世界坐标方向),Local(项目自身旋转后的朝向)。
对象缩放的时候子物体也会同步缩放。
按住ctrl可以按照固定值拖动缩放旋转,在 Edit->Gride and Snap
中编辑每次多少距离
按住V可以选择吸附,双击和F可以定位场景内的物体
组件
自己写的脚本实际上也是组件(Component)继承了MonoBehaviuor的都是组件,脚本组件挂载到其他物体上就可以把物体和业务功能整合起来。 一个脚本可以挂在同一个对象多次,只有组件可以挂载到游戏物体上。
MonoBehaviuor的继承链是MonoBehaviuor <- Behaviour <- Component
,继承Component的就是组件。
其实上面游戏物体的Inspector->Transform,MeshFIiter,MeshRenderer,BoxCollider
都是内置的默认组件。C#脚本可以在面板上调整数值也可以获取其他组件的API进行动态调整
#todo 获取组件 单个获取组件,直接get,如果你在同一个 GameObject 上挂载了多个 AudioSource
组件,使用脚本获取时可以通过 GetComponents<AudioSource>()
来获取所有的 AudioSource
组件,返回一个 AudioSource
数组。
using UnityEngine;
public class GetMultipleAudioSources : MonoBehaviour
{
private AudioSource[] audioSources;
void Start()
{
// 获取所有挂载在此 GameObject 上的 AudioSource 组件
audioSources = GetComponents<AudioSource>();
// 检查是否成功获取到多个 AudioSource
if (audioSources.Length > 0)
{
// 访问第一个 AudioSource
AudioSource firstAudioSource = audioSources[0];
firstAudioSource.Play(); // 播放第一个 AudioSource
// 访问第二个 AudioSource
if (audioSources.Length > 1)
{
AudioSource secondAudioSource = audioSources[1];
secondAudioSource.Play(); // 播放第二个 AudioSource
}
}
else
{
Debug.Log("未找到任何 AudioSource 组件");
}
}
}
Transform
直接引用就行,是Transform transform
属性去里面看,之前提到的东西都在属性里有对应 API参考Transform - Unity 脚本 API
Click to see more
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TBehaviourScript : MonoBehaviour
{
public string Name="V"; // 类的public变量会直接暴露在unity的Inspector中
public int Age = 0;
private float init_x = 1f;
private float init_y = 1f;
private float init_z = 1f;
private float angles_x = 0f;
private float angles_y = 0f;
private float angles_z = 0f;
// Start is called before the first frame update
void Start()
{
Debug.Log("Start Sence");
}
// Update is called once per frame
void Update()
{ // 位置的数据类型是UnityEngine.Vector3,重新赋值一下
transform.position = new Vector3(init_x,init_y,init_z); // 重新设置物体的位置
// transform.Translate(new Vector3(0.0001f,0.0002f,0.0003f)); // 约等于transform.position+=new Vector3(0.0001f,0.0002f,0.0003f) todo 不太懂为什么不抵消
init_x += 0.0001f;
init_y += 0.0002f;
init_z += 0.0003f; // 想想会怎么移动
transform.eulerAngles = new Vector3(angles_x%360,angles_y%360,angles_z%360); // 取余数值不会一直增加,转起来了
angles_x += 0.1f;
angles_y += 0.1f;
angles_z += 0.1f; // 想想会怎么旋转
transform.Rotate(new Vector3(3,0,0)); // 每秒转一个角
transform.LookAt(transform.parent); // xyz都为45度角正对着自己的父物体飞走,但是角度就没法调了,因为其实在一直刷新Rotation
#if UNITY_EDITOR // 只有在Unity编辑器下才会打印,可以节省性能
Debug.Log($"{this.Name} age is {this.Age}");
Debug.Log($"position is : {transform.position}"); // 世界座标
Debug.Log($"localPosition is : {transform.localPosition}"); // 自己的座标
Debug.Log($"rotation is : {transform.rotation}"); // 旋转角
Debug.Log($"localRotation is : {transform.localRotation}"); // 自己旋转角
Debug.Log($"lossyScale is : {transform.lossyScale}"); // 自身缩放
Debug.Log($"localScale is : {transform.localScale}"); // 相对于上级对象的缩放
Debug.Log($"eurlerAngles is : {transform.eulerAngles}"); // 这个是真正控制旋转三维角Rotation的
Debug.Log($"The parent object has {transform.parent.childCount} child "); // 应该输出的是1
Debug.Log($"The root object has {transform.root.childCount} child "); // 应该输出的是1
#endif
}
}
运行结果
[23:41:10] V age is 0
[23:41:10] position is : (1.06, 1.13, 1.19)<br/>
[23:41:10] localPosition is : (1.09, 1.26, 1.34)<br/>
[23:41:10] rotation is : (-0.10416, 0.89548, -0.29240, -0.31900)<br/>
[23:41:10] localRotation is : (-0.10416, 0.89548, -0.29240, -0.31900)<br/>
[23:41:10] lossyScale is : (1.00, 1.00, 1.00)<br/>
[23:41:10] localScale is : (1.00, 1.00, 1.00)<br/>
[23:41:10] eulerAngles is : (36.17, 219.22, 0.00)<br/>
[23:41:10] The parent object has 1 child
[23:41:10] The root object has 1 child
GameObject
包括摄像机光照等所有的对象,都是GameObject,直接在菜单中Create Empty就是一个空的GameObject,同时也是Unity中的一个类。
在脚本中新建一个GameObject,然后在Sence中新建一个物体,可直接拖拽到Inspector中的GameObject,也可以在脚本中赋值
使用Compoent->gameObject
对象可以获得当前组件所在的GameObject,与transform不同,transform是一个组件,而gameObject更像是self
的概念,但是可以套娃获得属性(怎么避免循环依赖的)。
📌Tip
自定义的组件字段和组件名称Unity都会自动给你大写首字母和自动加空格go->Go,BoxCollider->Box Collider
Click to see more
public class TestGameObject : MonoBehaviour
{
public GameObject go; // 新建了一个通用的GameObject对象,可以赋值获得任意一个其他对象
// Start is called before the first frame update
void Start()
{ }
// Update is called once per frame
void Update()
{ Debug.Log($"my name {gameObject.name}"); // 获得和面板中一样的对象名字
Debug.Log($"my tag {gameObject.tag}"); // 相当于给分类,比如不同职业的角色,都是角色类但是可以根据tag来判断
Debug.Log($"my activeInHierarchy {gameObject.activeInHierarchy}"); // 获得在场景中的显示状态
Debug.Log($"my position {gameObject.transform.position}");
Debug.Log($"another position {go.transform.position}"); // 当我赋值的时候会打印被赋值的对象,子对象的座标是相对于父物体而不是世界座标
go = GameObject.Find("Sphere/SphereSub"); // 默认是从Sence的根目录下查找 todo 获取不到
Debug.Log($"go name {go.name}"); // 获得和面板中一样的对象名字
Transform tempTransFrom = gameObject.GetComponent<Transform>(); // 通过泛型获得属性,应该用的是反射,性能不高吧
Debug.Log($"acquire my position {tempTransFrom.position}");
gameObject.SetActive(false); // 隐藏自己
}
}
运行结果
Prefab预制体
Make VirtualFunction Grate Again !
直接把GameObject(组合)拖到Assets中会注册成为一个预制体。预制体相当于模板,修改Prefab的时候所有引用Prefab的都改变属性。
预制体的嵌套和类的虚函数类似,首先制作两个预制体A,B。把A拖到B的预制体空间中,B空间中的A被修改的属性优先级高,其余的和A一样
如果预制体被删除,那么被放进去的对象会变成没有引用的预制体不会消失,(右键对象)断开引用就变成了常规的GameObject
如过把Perfab再拖到Assets中,unity会提示是否以此为蓝本制作新的Perfab,此时被注册的Perfab会成为新Perfab变体的第一个成员,同样遵循虚函数的模式变体重写的属性会覆盖原Perfab,其余不变
Perfab的操作是无法用Ctrl+Z的,
生命周期
一个GameObject的产生到销毁的全过程。 gameObject.activeInHierarchy
是控制GameObject的启用/禁用状态(使用SetActive更改)。此状态并不影响禁用的游戏对象不会被渲染,也不会执行其上的脚本的Update方法,但是它们仍然占用内存,可以使用Unity的资源管理系统,如Resources.UnloadUnusedAssets方法,或者使用AssetBundle来动态加载和卸载资源。
文档看这里MonoBehaviour-Awake() - Unity 脚本 API,其他的一样查就行
生命周期函数:
- Awake:首次被产生或者从不可见变为可见的前(核心就是首次启用)执行一次,再次启用就不会执行,例如查找资源用
- OnEnable: 每次启用前的函数,可以用来Fetch资源
- Start:首次启用后执行一次
- Update: 每帧前执行一次(注意性能
- FixedUpdate: 固定时间(0.2)刷新一次,可在edit->project setting->time更改间隔
- LateUptedate: 每帧后执行一次
- OnDisalbe: 每次禁用事件执行,在OnDestory也会执行一次
- OnDestory:销毁的时候执行
如果生命周期无法满足需求那就需要使用任务调度,时间任务调度函数Invoke
InvokeRepeating
CancelInvoke
(参数都简单易懂
Click to see more
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestLifeCycle : MonoBehaviour
{
void Awake() // 首次被产生或者从不可见变为可见的前(核心就是首次启用)执行一次,再次启用就不会执行,例如查找资源用
{
Debug.Log("I'm awake");
}
private void OnEnable() // 每次启用前的函数,可以用来Fetch资源
{
Debug.Log("I'm Ready!");
} void CurryingDelyDemo(string s){Debug.Log($"be Invoked {s}");}
public void InvokeDemo(){Debug.Log("be Invoked");}
public void CalcelInvokeDemo(){Debug.Log("Invoke is Canceled");CancelInvoke("InvokeDemo");}
void Start() // GameObject加载完毕后所有的Awake执行完,的第一帧执行一次
{
Action myInvokeDemo = () => CurryingDelyDemo("Hello, World!");
Debug.Log("Object start successfully Complete");
// 定时任务
Invoke("InvokeDemo",3f); // 3秒(浮点)后通过反射来执行函数
InvokeRepeating("InvokeDemo",2f,3f); // 从调用的2秒后每3s执行一次
Invoke("CalcelInvokeDemo",10f); //10秒(浮点)后通过反射来执行取消
Invoke(myInvokeDemo.Method.Name,3f); // 通过委托的闭包传递参数
}
private float timedelta = 0.2f;
void FixedUpdate()
{ Debug.Log($"every {timedelta} second Update once");
} // Update is called once per frame
void Update()
{ Debug.Log("frame start");
}
void LateUpdate() // 每帧之后
{
Debug.Log("frame end");
}
void OnDisalbe()
{ Debug.Log("I'm Disabled");
}
void OnDestory()
{ Debug.Log("I'm Destory");
}}
运行结果
[21:35:31] I'm awake
[21:35:31] I'm Ready!
[21:35:31] Object start successfully Complete
[21:35:44] every 0.2 second Update once
[21:35:44] frame start
[21:35:44] frame end
[21:35:40] be Invoked
[21:35:35] be Invoked Hello, World!
[21:35:42] Invoke is Canceled
协程
📝Note
** 柯里化复习**
柯里化(Currying)是一种将接受多个参数的函数转换成接受一个单一参数的函数,并且返回接受余下参数的函数的技术。这种技术由逻辑学家Haskell Curry提出,因此得名柯里化。
假设有一个函数 f(a, b, c),经过柯里化处理后,它会被转换成 f(a)(b)(c) 的形式。
反柯里化(Uncurrying)是柯里化的逆操作,它将嵌套的函数转换为一个接受多个参数的函数。反柯里化可以使得嵌套的单参数函数变得更易用,特别是在需要处理多个参数的情况下。
假设有一个柯里化函数 f(a)(b)(c),经过反柯里化处理后,它会被转换成 f(a, b, c) 的形式。
Unity中的协程是一种简化异步行为的方法,通常用于在一段时间内执行某些任务或在帧之间分配工作。协程通过 IEnumerator
接口和 StartCoroutine
方法来实现。
- 是非阻塞的,适合需要在多个帧之间执行的任务。
- 依赖于Unity的更新循环,无法脱离Unity环境使用。
- 不支持真正的并行执行,只是在不同帧之间分配工作。
Click to see more
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AsyncTest : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{ Coroutine cor=StartCoroutine(AsyncDemo(10)); // 去执行一个协程
StopCoroutine(cor); // 结束此协程调用
StopAllCoroutines(); // 停止掉携程池里面所有协程
// StartCoroutine(AsyncDemo(10)); // 也可以这样,但是这样不知道在哪里用了几次。
// 所以建议直接不要这样用,做一件事最好的方法有且仅有一个
}
// Update is called once per frame
void Update()
{ }
public IEnumerator AsyncDemo(int num) // 协程的固定返回类型
{
Debug.Log("wait for return ");
yield return new WaitForSeconds(3f); // 3s之后返回,有很多不同的返回函数,用到再看
Debug.Log("done return 1s");
yield return null;
Debug.Log($"done next return , out {num}");
transform.position = new Vector3(10, 10, 10); // 做一次位移
yield return AsyncDemo2(); // 携程是可以嵌套的
}
public IEnumerator AsyncDemo2() // 利用携程做动画,以及携程嵌套,携程本质就是一个函数栈暂停,控制权移交
{
Debug.Log("start async2");
while (true)
{ transform.Rotate(new Vector3(10,0,0)); // 调整角度,配合协程达到旋转的效果,不过这个旋转不是平滑的
yield return new WaitForSeconds(0.5f); // 0.5秒返回一次
}
}
}
运行结果
[22:40:01] wait for return
[22:40:04] done return 1s
[22:40:04] done next return, out 10
[22:40:04] start async2
协程常用的几个返回
等待固定时间:yield return new WaitForSeconds(2);
暂停协程2秒。
等待下一帧:yield return null;
暂停协程直到下一帧。
等待特定条件:yield return new WaitUntil(() => someCondition);
暂停协程直到 someCondition 为真。
等待另一个协程:yield return StartCoroutine(AnotherCoroutine());
暂停协程直到另一个协程执行完毕。
协程与Unity的游戏循环紧密集成。MonoBehaviour
提供的 StartCoroutine
方法会注册一个协程,让它在每帧游戏循环中执行。尽管协程可被认为是一种异步编程方式,但它们始终在Unity的主线程上运行。所以对线是Unity协程安全的
Mathf和Time
Mathf就是常见的数学函数,看文档弄就行了。
Time是时间静态类。注意Time.deltaTime
的用法,它用来获得1/帧率
秒。当使用transform.Translate(new Vector3(0,1f,0))
也就是Y轴每帧上升1,但是不同配置刷新率不一样导致帧率高的移动快。此时使用这个函数一秒移动1单位不受帧率影响。 transform.Translate(new Vector3(0,1f,0) * Time.deltaTime)
就可以了。乘积结果就是Y: 1f
如果 Time.timeScale 的值为0.5,那么 Time.deltaTime 的值将会是实际经过的时间的一半。
Click to see more
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MathAndTime : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{ // 到时候直接看Unity.Mathf里面有没有对应运算就行
Debug.Log(Mathf.Ceil(2.5f)); // 四舍五入上取整3
Debug.Log(Mathf.Floor(2.5f)); // 四舍五入下取整2
Debug.Log(Mathf.Abs(-6));
Debug.Log(Mathf.Round(2.5f)); // 四舍六入 五取偶2.5=2,3.5=4
Debug.Log(Mathf.PI);
Random.Range(0,5);// int有头没尾
Random.Range(0, 0.5f); // float就头尾有了
}
// Update is called once per frame
void Update()
{ Debug.Log($"gameworld time is {Time.time}");// 游戏世界开始运行到现在的时间,时间暂停了就砸瓦鲁多了
Debug.Log($"Time.deltaTime is {Time.deltaTime}"); // 一帧的时间,如时间缩放timeScale=0了
transform.Translate(new Vector3(0, 1f, 0));
transform.Translate(new Vector3(0, 1f, 0) * Time.deltaTime); // 一秒移动1单位不受帧率影响
Debug.Log($" real time {Time.realtimeSinceStartup}"); // 游戏开始到现在的真实运行时间
Time.timeScale=0;// 游戏世界时间倍率,0就是停止,1是正常,负数是放慢多少倍(最好不要设置负数)
}
}
运行结果
[23:31:07] 3
[23:31:07] 2
[23:31:07] 6
[23:31:07] 2
[23:31:07] 3.141593
[23:31:07] gameworld time is 0
[23:31:07] Time.deltaTime is 0.02
[23:31:07] real time 1.488957
[23:31:07] gameworld time is 0
[23:31:07] Time.deltaTime is 0
[23:31:07] real time 1.786187
[23:31:07] gameworld time is 0
[23:31:07] Time.deltaTime is 0
2D系统
📌Tip
Unity里面的组件 类 和传统派的程序不一样,更偏向于工具使用,而没有和程序耦合的太严重,所以使用属性的时候不用都知道,对着工具面板现看现查;
不需要太过关注实现,会用,想想能利用这个组件做到什么。
Sprite和SpriteRenderer
把摄像机的Inspector->Projection
设置为Orthographic
(正交),此时摄像机是正前方视角且没有近大远小透视视角.此时可以做2D了,所有的对象都是堆叠的。 素材直接拖进来,PNG的素材是含有透明通道的,此时在素材Inspector->Texture Type: Sprite(2D and UI)
实际上会给素材创建一个Sprite的副本(本体保留的)放进去的时候实际上是把Sprite放进去了。 Sprite组件的一些属性:
- SpriteMode
- Pixels Per Unit 每像素相当于游戏内多少米
Window->Package Manager->Sprite 2D
安装上包,SpriteMode: Multiple,就可以编辑图片生成多个Sprite。也可以根绝边缘自动切,或者根据格子尺寸切,Sprite的中心点是可以调整的,拖动或者用图片内座标调整 SpriteRenderer相当于属性集,可以直接在Sprite中换素材而保留属性集
- SortingLayers相当于PS中层的概念,每个Object属于哪个层,最下面的从层最靠前
- Order in Layer在层中的优先级
Click to see more
public class SpriteTest : MonoBehaviour
{
// 挂在Sprite对象上的类
// Start is called before the first frame update
private SpriteRenderer spriteRenderer; // 声明一个spriteRenderer
public Sprite sprite; // 声明一个Sprite对象
void Start()
{ spriteRenderer = gameObject.GetComponent<SpriteRenderer>(); // 获得本Sprite的Renderer
spriteRenderer.sprite = sprite; // 可以替换掉
spriteRenderer.color = Color.cyan; // 其他属性参考着吗面板改
}
// Update is called once per frame
void Update()
{ }
}
运行结果
2D物理系统
Edit->Project setting->Physics2D
在这里设置工程的物理面板 2D 物理 (Physics 2D) - Unity 手册 在2D中XY的值是重力的程度,方向和二维数轴类似。值大小代表重力大小,默认为9.8N(正好是物理中的G)
#todo 这个地方东西很多,后面详细看看,这里完全不会!!!!!!他只讲了几个API,互相作用是什么完全不知道
刚体 BoxCollider2D 碰撞体 Rigidbody2D 物理材质
触发 是Collider的属性,设置了触发之后就没有碰撞体的效果了,但是会触发。 子弹大部分时候用的是触发的逻辑,触发之后减hp等效果
有三个主要的生命周期函数。
一些刚体的属性:
- Drag下落空气阻力
- AngularDrag转动角空气阻力
- IsKinematic是否直接用代码接管物理特性
- CollisionDetection碰撞检测频率算法
- Discrete默认频率较低,有时候速度比较快可能没检测到,就会穿模
- Continuous连续判断
- Continuous Dynamic动态的?
- Continuous Speculative
- Constraints可以冻结某个轴的旋转或座标,全冻结就失去了物理特性
public class Test3Dfunc : MonoBehaviour{ // 不一定两个都得有Collider才会触发,只有一个也可以
private void OnCollisionEnter(Collision other) { Debug.Log("Enter Collider"); } // other就是另一个碰撞gameObject
private void OnCollisionStay(Collision other) { Debug.Log("Stay Collider"); }
private void OnCollisionExit(Collision other) { Debug.Log("Exit Collider"); }
}
- Collider->Tirgger的事件,Trigger勾上之后挂载脚本才能生效,Trigger脚本也是挂双方任何一个就行,不用两个都挂
- OnTriggerEnter
- OnTriggerStay
- OnTriggerExit 碰撞事件和触发事件必须两者都有碰撞体,其中一个有刚体,如果两个都是碰撞体但是没勾Trigger那触发的是
碰撞事件
,有一个勾选了Trigger的触发的就是触发事件
,即这个碰撞体就会作为触发器,而不是物理碰撞。 射线
public class Rayddemo : MonoBehaviour
{
// 测试功能的时候挂载在了Camera上
void Update() // 挂载到脚本上之后,相当于从这个gameObject中发指定向量的射射线
{
// Ray ray = new Ray(Vector3.zero, new Vector3(0, 1, 0)); // 先创建一个y轴方向的射线对象
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);// 创建鼠标指向的射线,没有碰到任何物体就是空的,注意这里可能为空
if (Physics.Raycast(ray,out RaycastHit hitInfo,1000)) // 使用物理模块去检测这个射线是否命中,检测距离是1000米,返回的是bool值
{
Debug.Log(hitInfo.ToString());// 射线碰到的什么物体
Debug.Log("Catch!");
Debug.DrawLine(ray.origin,hitInfo.point,Color.red);
Debug.Log($"wryyyyy {ray.origin},{hitInfo.point}");
}
}
}
游戏输入
有戏输入是按照有游戏客户端分类的,PC 平板手机 主机手柄 VR
输入系统一般是Update中,不断的更新。
键盘:
Input.GetKeyDown(KeyCode.X) // 获得某个键的按下事件
Input.GetKeyUp(KeyCode.X) // 获得某个键的弹起事件
Input.GetKey(KeyCode.D) // 理论上是一帧一次,但是会帧率太高的话Unity中会优化,让触发频率降低
Input.GetKeyDown('b')//也可以使用函数调用而不是枚举
Input.GetMouseButtonDown(0) // 0左键1右键
Input.GetMouseButtonUp(0)
Input.GetMouseButton(0) // 持续按下
常用的是ProjectSetting和Preference
输入管理器 Edit->ProjectSetting->InputManager 使用的是Axes->Name/NegativeButton/PositiveButton 相当于是输入输出的hook,会实时的返回指定的值,主要使用的俩
Click to see more
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class bollMove : MonoBehaviour
{
private Rigidbody2D rb;
private bool isOnGround = true;
public float Speed = 0.8f;
public float junpForce = 300f;
void Start()
{
// 获取 Rigidbody2D 组件
rb = GetComponent<Rigidbody2D>();
if (rb == null)
{
Debug.LogError("Rigidbody2D 组件未附加到对象上!");
}
}
void Update()
{
Move();
Jump();
}
void Move()
{
float horiztonal = Input.GetAxisRaw("Horizontal");
float vertical = Input.GetAxisRaw("Vertical");
Vector3 moveVetcor = new Vector3(horiztonal, vertical, 0);
transform.Translate(moveVetcor * (Time.deltaTime * Speed));
}
void Jump()
{
if (Input.GetKeyDown(KeyCode.Space) && isOnGround)
{
rb.AddForce(Vector2.up * junpForce);
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "ground")
{
isOnGround = true;
}
}
private void OnCollisionExit2D(Collision2D collision)
{
if (collision.gameObject.tag == "ground")
{
isOnGround = false;
}
}
}
运行
实现接触地面的时候能跳跃
UI系统介绍
游戏的UI不会随着摄像机变化,Unity2D主要是使用SpriteRender和2D的物理系统组成
UI使用的是Canvas和UI组件框架
在Scene中右键添加UI可以查看所有的UI
在设置中可以提前查看不同分辨率的UI位置,可以提前调整
所有的UI都是在Canvas中绘制的,Canvas的默认大小是和摄像机大小一样且不跟随摄像机,在创建Canvas的时候会自动创建EventSystem,一般是全局只有一个(实际上可以创建多个但不会这么做), GPT给的解释:
📌Tip
EventSystem与Canvas的交互
EventSystem
与 Canvas
的交互主要体现在以下几个方面:
- 事件分发:
EventSystem
负责捕捉用户输入,并将这些输入事件分发到Canvas
上的各个 UI 元素。例如,当用户点击按钮时,EventSystem
会检测到点击事件,并将该事件发送给包含在Canvas
中的按钮组件。
- Raycasting:
EventSystem
使用 Raycasting(射线投射)来确定用户点击或触摸的是哪个 UI 元素。当用户在屏幕上点击时,EventSystem
会从点击位置发出一条射线,并检测它是否与Canvas
上的 UI 元素碰撞。如果射线与某个 UI 元素相交,那么该元素就会接收到点击事件。Canvas
的渲染模式(例如Screen Space - Overlay
或World Space
)会影响 Raycasting 的行为。
- Input Modules:
EventSystem
可以使用不同的输入模块(如StandaloneInputModule
)来处理不同类型的输入。每个输入模块可以配置不同的输入方式(如鼠标、触摸、键盘),这些输入方式会被EventSystem
转换为 UI 事件。- 这些 UI 事件会被传递给
Canvas
上的 UI 元素,从而触发相应的交互行为。
- UI 组件响应事件:
Canvas
中的 UI 组件,如按钮、滑块、输入框等,都通过继承自MonoBehaviour
的接口(如IPointerClickHandler
、IDragHandler
)来处理EventSystem
分发的事件。- 当
EventSystem
检测到一个点击事件,它会检查点击位置是否命中Canvas
上的某个 UI 组件。如果命中,EventSystem
会调用该组件上的事件处理函数(如OnPointerClick
)。
- 示例场景
- 按钮点击:用户点击
Canvas
上的按钮。EventSystem
检测到鼠标点击事件,并通过 Raycasting 确定点击的位置与按钮重叠。然后,EventSystem
调用按钮的OnPointerClick
方法,触发按钮的点击响应。 - 拖动滑块:用户在
Canvas
上的滑块上拖动鼠标或手指。EventSystem
捕捉到拖动事件,并调用滑块的OnDrag
方法,更新滑块的值。
- 按钮点击:用户点击
可以创建多个Canvas,多个Canvas的渲染优先级不受unity的层级影响,在Canvas组件的SortOrder控制渲染顺序,越大越优先渲染,canvas有三个模式
- Canvas
- ScreenSpaceOverlay 永远置于最上层,相对于屏幕固定
- ScreenSpaceCamera 相对相机位置,在其他Object下层
- WorldSpace 作为一个对象存在场景内,很多时候在世界中做跟随角色血条用
- 使用世界空间的时候,既处理元素与其他元素的位置,又要处理相对摄像机视角位置,不同设备分辨率又不一样很麻烦
- Canvas Scaler
- ConstantPixelSize默认固定大小
- ScaleWithScreenSize随屏幕等比例缩放
- ReferenceResolution参考分辨率,先在这下做会随着缩放
- Graphic Raycaster 使用EventSystems进行事件捕获
using UnityEngine.EventSystems;
挂载在gameObject上,此时类继承MonoBehaviour之外还需要继承事件接口
:(可以点进UnityEngine.EventSystems去看具体有什么) - IPointerMoveHandler这几个顾名思义Pointer是鼠标
- IPointerEnterHandler
- IPointerExitHandler
- IPointerDownHandler
- IPointerUpHandler
- IPointerClickHandler
- IBeginDragHandler 开始拖拽
- IDragHandler 拖拽中
- IEndDragHandler 结束拖拽 一般来说物理相关的建议在FixedUpdate更新。
IMAGE组件
image的颜色属性是叠加色,不是直接给ia纯色。 UI一般是一张图过来的然后使用Sprite自动切分
在使用脚本改变UI的变量的时候,需要使用using UnityEngine.UI
更改
#todo 切UI不太会切Sprite不太会用啊
Text组件
[Unity干货]使用TextMeshPro动态字体节省时间_哔哩哔哩_bilibili
先看下面这段代码,这是一段Button的代码
public class ButtonTrigger : MonoBehaviour
{
private Button button;
private void Start()
{
button = GetComponent<Button>(); // 这是怎么获取上的
button.onClick.AddListener(ButtonTriggered2);
}
// Start is called before the first frame update
public void ButtonTriggered1()// 在UI中绑定脚本之后选择挂上触发
{
Debug.Log("func1 triggered");
}
public void ButtonTriggered2() // 使用代码直接绑上
{
Debug.Log("func2 triggered");
}
}
一般来说挂载在组件上的脚本都是这种挂载方式,声明一个对应类型的变量然后更改它的属性
挂载之后,会先通过GetComponent<Button>()
会在当前的 GameObject
上查找 Button
组件。如果这个 GameObject
上有多个组件(无论是 Transform
、Rigidbody
、Collider
还是其他类型的组件),GetComponent<Button>()
只会找到并返回第一个匹配的 Button
组件。如果当前 GameObject
上没有 Button
组件,GetComponent<Button>()
将返回 null
。因此,你在实际开发中通常需要检查 button
是否为 null
,以避免空指针异常。 在 Unity 中,GetComponent<T>()
方法用于查找当前 GameObject
上的指定类型的组件,查找的顺序是按组件在 GameObject
Inspector 面板中的排列顺序进行的。通常情况下,每个组件对应一个脚本 是更好的实践,而不是让一个脚本负责查找和操作多个组件
方向正前方应该X轴指向右边,Y轴指向正上方,Z轴垂直纸面(屏幕)向里
举个栗子
- 的人向玩家走来
- 点击屏幕可以发射子弹
- 子弹击中敌人两次则死亡销毁
- 敌人到达指定位置,玩家失败,重新玩
渲染基础
创建场景的时候Unity会默认创建一个相机。摄像机可以移动,移动也是通过transform来实现的。
- BackGroundColor在非Skybox模式下会添加纯色
- CullingMask可以选择Layer可视,在tags边上,规定对象的layer
- field_of_view默认60常用90 45 75。 就是视锥角度大小
- ClippingPlanes
- Near往前的视锥切面(10米内看不到,10米的平面外才进来)
- Far最远看多远,视锥的高。(在游戏中可以动态变化,太远的地方没必要看到,会额外耗性能)
- ctrl+shift+F快速对焦摄像机到编辑模式视窗(GameObject->Align with view) 上面的值都可以使用GetCompoent获得对象更改属性,一般会用transform进行操作而不是改奇奇怪怪的属性
灯光默认会创建一个Directional Light平行光,这个gameObject position是没有影响的,相当于太阳,但是Rotation角度有影响
全去掉其实也有点亮度,因为Skybox是有一点亮度的。
Point Light点光源,就是有个东西在发光,Range是光照范围大小,越靠近光源越亮
Spot Light聚光灯,顾名思义,大手电筒。
Area Light区域光只对静态物体管用,静态的物体在游戏开始之后的渲染就静止了,position和rotation只能更改Collider,画面是不会变的。大多数时候静态的都是场景等不会变的东西,会减少性能消耗。
灯光可以烘焙,在Window->Rendering->Lighting面板中,GenerateLighting,会在sense下生成一个文件夹, #todo 烘焙这个可以整理一波,但是先跟下来
阴影类型:
- Hard Shadows阴影边缘是锐利的,过渡区域没有平滑过渡,计算简单性能好
- Soft Shadows阴影边缘有柔和的过渡,过渡区域根据距离光源的远近而变化,增加了阴影的自然感,性能消耗大一点。
Shader
shader不是所有管线通用的,shader是和材质绑定的,一般来说会创建一个新的材质然后绑定shader,再让游戏物体选择这个材质。 shader材质改变会影响所有使用这个材质的物体。和调一般的材质属性一样。
特性 | 内置渲染管线(Built-in RP) | 通用渲染管线(URP) | 高清渲染管线(HDRP) |
---|---|---|---|
图像质量 | 中等 | 中高 | 极高 |
性能 | 中等 | 高 | 低 |
定制化 | 低 | 中 | 高 |
平台支持 | 全平台支持 | 全平台支持 | 仅限高端PC和主机 |
适用项目类型 | 小型项目,移动端 | 中型项目,跨平台 | AAA游戏,高质量视觉场景 |
渲染特性 | 基本 | 现代渲染技术支持 | 高级渲染技术支持 |
复杂度 | 简单 | 适中 | 复杂 |
- 内置渲染管线适合资源有限的小型项目或需要跨多个低端平台的项目。
- 通用渲染管线(URP) 性能和图像质量的平衡点,适用于大部分中小型项目和跨平台。支持大多数现代渲染技术,如物理光照(PBR)、屏幕空间环境光遮蔽(SSAO)、后处理效果(Post-processing)等
- 高清渲染管线(HDRP) 适合要求极高图像质量且针对高端硬件的项目,比如AAA级游戏、建筑可视化和电影特效项目。支持复杂的光照系统、体积光照、屏幕空间反射(SSR)、全局光照、细致的材质处理等高级图形特性。
其他的之后再说
粒子
类似下雪下雨法球之类的,很多都用到粒子,程序主要是使用粒子,而不需要自己做粒子。
粒子就是普通的空gameObject,但是挂载了Particle System(Create->Effects->ParticleSystem) 在ParticleSystem组件下,以下几个属性常用:
- Duration持续时间
- looping是否循环
- startLifetime每个粒子的存活时间
- playOnAwake是否一开始就播放,如果设置false,那就只能在代码里获得然后播放
- stopAction选项很明确,Disable播放完后禁用自己,Destory播放完之后销毁自己,Callback要自己去绑定回调方法了但很少这么做。
动画
一般是模型的动作动画不涉及到计算,攻击和结算还是用Collider,主要是 配置 切换 代码控制动作。注意Animation组件已经弃用了。
涉及到的几个对象:
- gameObject->Animator组件
- Animator Controller绑定到Animator上
- Animation(动画片段)
- StateMachine状态机 State状态和Transition控制条件
首先是给gameObject加上Animator组件,在资源文件中创建Animator Controller,绑定到Animator上。双击可以直接编辑AnimatorController,再创建Animation(动画片段),就可以在里面组合动画了,然后用代码去控制动画状态转换。
在Unity中制作动画Window->Animation->Animation。此时会打开Animation编辑面板。此时选中gameObject,他会读取此gameObject所有Animation(动画片段)就可以编辑动画片段了,这个面板可以操作gameObject的绝大部分组件属性,也可以在组件属性上右键add key。制作起来和AE差不多。
动画资源的Animation(动画片段)Inspector中,Loop是控制是否循环播放,Loop Pose 是在处理循环动画(如角色的跑步、走路、游泳等循环动作)时。它的作用是让动画在循环时保持姿势的平滑过渡,避免动画首尾衔接时出现明显的跳跃或不自然的切换。有时候有用有时候没用,不好使的时候勾上试试。
把Animation(动画片段)拖进去的时候实际上是创建了一个持有该Animation(动画片段)的State状态,连连看就可以控制动画状态播放,控制条件Transition->HasExitTime是控制是否只播放一次,不勾上可能会导致退不出来状态, 从Entry进去是DefaultState,可以使用parameters控制状态转换条件。
状态State的一些属性:
- Motion持有哪个Animation(动画片段)
- Speed播放速度
- Mirror镜像反转播放
- Transitions:Solo/Mute很明显,可以控制和他相连的状态,调试的时候常用
动画资源相关 unitypackage就是Unity资源包,拖进去就能导入,不明白的跟着18.4操作一下,回头给这个截个图说明一下操作
把自己Asset中的资源右键导出,他会自动关联自己需要的依赖,可以选择连着什么导出。
资源导入之后,有些动作会绑定在一个白模上,直接在需要导入的模型上使用Animator Controller就可以了。Avater就是什么的使用
#todo 以后再说
动画控制器CharacterController,用了这个就不用Collider和Rigidbody, 自带一个Collider不过形状未知。
- Slope Limit: 控制角色最大的爬坡斜率
- Step Offset: 控制角色可以迈上最大的台阶高度
- Skin Width: 在角色的外圈包裹着一层“皮肤”的厚度,如果Skin Width设置为1米,那么角色当然就会“浮空”1米,一般保持默认即可
- Min Move Distance: 最小移动距离,默认为1毫米,如果该数值过大但代码中单位移动速度很慢,角色就不会动
- Center/Radius/Height: 角色控制器组件在Scene面板中体现为一个“胶囊碰撞器”的形状,这也导致其他的碰撞体我们并不需要,Center为胶囊中心点位置,Radius为半径,Height为高度
组件的两个属性,characterController.isGrounded返回布尔值,检测是否在地面上。 characterController.Move(vector3); 和transform.Translate类似,没有重力,需要传递向量,数值大小会直接影响移动速度,所以要乘Time.deltaTime characterController.SimpleMove(vector3);自动应用重力,需要传递一个速度,不受帧率影响,不需要乘Time.deltaTime
导航系统
包括寻路,点击之后自动以最短路径到达并且避开障碍物 obsolete,好像更新了版本,已经换代了?
unity的AI自动寻路Navigation,及其组件详解_ai navigation-CSDN博客 Navigation组件窗口中可以bake,会自动读取环境中所有static的物体,然后生成一张哪里能去哪里不能去的地图。
在需要使用导航的物体上,添加NavMeshAgent组件,在代码中使用NavMeshAgent(using UnityEngine.AI;
)类,获得身上的对象,设置需要追踪的position进行追踪。
最简单的导航代码示例
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class NavigateTerst : MonoBehaviour
{
private NavMeshAgent _agent;
public Transform target;
// Start is called before the first frame update
void Start()
{
_agent = GetComponent<NavMeshAgent>();
}
// Update is called once per frame
void Update()
{
_agent.SetDestination(target.position); // 设置导航的目的地
if(Input.GetKeyDown(KeyCode.Space))
{
_agent.isStopped = !_agent.isStopped; // 暂停导航
}
}
}
常用的面板属性:
- Agent Type: 代理类型,我们在Navigation中添加类型,主要是设置这个角色的尺寸(Humanoid是人形,用其他的形状需要使用插件完成)
- Base Offset: 碰撞体和角色的高度偏移
- Speed: 导航时的最大移动速度
- Angular Speed: 导航时的最大转弯速度
- Acceleration: 导航从开始加速到Speed需要的加速度
- Stopping Distance: 距离终点多远停止
- Auto Braking: 勾选后表示到达目标点或离目标点的距离满足Stopping Distance时自动停止,如果不勾选则到达终点,角色也围着终点转来转去 障碍回避设置:
- Radius: 碰撞体半径
- Height: 碰撞体高度
- Quality: 躲避障碍物的行为质量,质量越高躲避行为越好,越智能
- Priority: 优先级,范围为0-99,(0优先级最高,99优先级最低),高优先级的Agent不会与低优先级的Agent进行碰撞
Areas区域,可以给区域在Navigation->Object中设置代价,导航计算路线的时候会使用代价和距离进行计算,但是在区域内的速度和阻力并不会受影响,还是需要自己实现。
使用NavMeshObstacle组件可以设置导航过程中的墙
面板属性:
- Shape形状
- Size大小
- Carve勾选的时候,会相当于导航代价正无穷,否则不参与代价计算,但是仍旧会按照Obstacle去阻挡(我还没搞明白他和Collider的区别)
下面是鼠标点击导航的做法,注意怎么解决导航完成后的漂移和导航完成的距离计算(确保动画播放暂停正常)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class HumanNav : MonoBehaviour
{
private NavMeshAgent _agent;
// Start is called before the first frame update
void Start()
{
_agent = transform.GetComponent<NavMeshAgent>();
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(1))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hitInfo, 1000))
{
Debug.Log($"{ray.origin},{hitInfo.point},{Color.green}");
_agent.SetDestination(hitInfo.point);
Debug.Log("strat run");
_agent.isStopped = false;
}
}
if (Vector3.Distance(transform.position, _agent.destination) <= _agent.stoppingDistance)
{// if中的条件是检测是否到目标点的做法
Debug.Log("stop run");
_agent.isStopped = true;// 防止到目标点之后产生漂移的办法
}
}
}
unity中的特殊文件夹Resources,是资源文件夹,可以直接使用Resources类读取资源。Resources可以在任意层级任意地方,但必须在Assets文件夹下,多个Resources会被合并起来。
当不同Resources中文件重名,则会加载第一个找到的文件,如果有文件夹重名,则会报警告提示重复的资源路径 。在Start中使用prefab_cube = Resources.Load<GameObject>("Perfabs/s2");
即可加载(非实例化仅加载对象)。
存档系统
Unity自带的本地存储是playerprefs
void Start()
{
// PlayerPrefs.SetString("PlayerName","Tom");
// PlayerPrefs.SetInt("PlayerAge",32); PlayerPrefs.DeleteAll();
string PlayerName = PlayerPrefs.GetString("PlayerName"); // 没有的时候就返回默认值
int PlayerAge = PlayerPrefs.GetInt("PlayerAge");
Debug.Log($"{PlayerName} is {PlayerAge}");
if (PlayerPrefs.HasKey("PlayerName")) // 返回的是bool,类似于js的本地存储
{
string PlayerName2 = PlayerPrefs.GetString("PlayerName");
}
}
Warning
playerprefs会存储在注册表,是持久化存储,同一个项目都可以读取
在windows中是HKEY_CURRENT_USER\Software\[CompanyName]\[ProductName]
linux下存储在~/.config/unity3d/prefs
文件夹下
场景切换
场景可以切换的前提是场景打包给了玩家(有些测试场景等是不用打包的)File->BuildSetting,排序第一的场景会被作为游戏的开始场景,在脚本中使用
using UnityEngine.SceneManagement;
public class changedemo : MonoBehaviour
{
private void Awake()
{
GameObject.DontDestroyOnLoad(gameObject); // 挂载者自己在场景切换的时候不销毁
}
void Update()
{
if (Input.GetMouseButtonDown(0)) // 点击切换
{
SceneManager.LoadScene(1); // 使用场景编号
SceneManager.LoadScene("SceneTwo"); // 使用场景名
}
}
}
这个类同时提供了创建场景异步加载等,前面的区域以后再来探索吧。
场景切换会导致前一个场景所有的物体被销毁,使用DontDestroyOnLoad带到下一个场景
音效相关
AudioListener是声音组件,可以理解为声音总线,一般会跟随摄像机挂载。 想在Object上发出声音就给这个Object挂载AudioSource组件,导入声音文件会变为AudioClip声音片段(基本不用程序动),绑定到Audio上就可以了 面板常用:
- AudioClip (音频剪辑):播放的音频
- Mute (是否静音):占用内存小可以放很多音效,因此可以让画面快速响应关闭和恢复当前音效,只是静音并不会卸载和暂停
- Play On Awake (启动播放开关):物体Awake事件时立即播放
- Loop (循环播放开关)
- Priority (播放优先级) 决定音源在场景中存在的所有音源中的播放优先级(?不过不常用)
- Volume (音量) 最大为1
- Pitch (音调) 倍速越大音调越高
- Stereo Pan (声道占比) 调节左右声道
- Spatial Blend (空间混合) 2D 音源(0)例如给UI用固定、3D 音源(1)随GameObject有远近,或者是二者插值的复合音源。
- Reverb Zone Mix (回音混合)
- 3D Sound Settings (3D 音频设置)
使用代码控制
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AudioTestScript : MonoBehaviour
{
private AudioSource _audioSource;
private AudioSource _sfxaudioSource;
public AudioClip audioclip;
public AudioClip audioclipAttack;
// Start is called before the first frame update
void Start()
{
AudioSource[] audioSources = GetComponents<AudioSource>(); // 获取多个组件
// 分别为背景音乐和音效赋值
_audioSource = audioSources[0]; // 第一个 AudioSource
_sfxaudioSource = audioSources[1]; // 第二个 AudioSource
_audioSource = transform.GetComponent<AudioSource>(); // 这样会获取到第一个组件
_audioSource.clip = audioclip;
_audioSource.Play(); // 直接切换clip之后不会播放,需要手动播放一次
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyUp(KeyCode.A))
{
_audioSource.Pause();
}
if (Input.GetKeyDown(KeyCode.A))
{
_audioSource.Play();
}
if (Input.GetMouseButtonDown(0))
{
_sfxaudioSource.PlayOneShot(audioclipAttack);
}
}
}
Warning
一个AudioSource管一个音效,如果使用同一个, 那PlayOneShot和Play可能会会被一块触发导致重叠播放,比如上面想用按键控制攻击音效的时候,背景音乐同时也会跟着起停。所以改为了获取两个组件
打包和发布
File->BuildSettings->PlayerSettings就可以打开窗口,可以规定鼠标游戏版本等奇奇怪怪的东西,可以用到再去查。 重点注意以下几个:
- Company Name:企业或发布者名称,可以是你的公司或个人名称。
- Product Name:产品名称,也可以理解为游戏名称,代表这个应用或游戏的名字。
- Version:游戏的版本号,表示你当前的应用版本。
- Default Icon:游戏图标,用于展示在应用图标或任务栏上。
- Default Cursor:游戏的鼠标指针样式,可以定制鼠标指针。
- Resolution and Presentation:分辨率和显示,设置游戏运行时的显示分辨率。
- Fullscreen Mode:显示模式,主要是设置游戏是否以全屏模式运行。
- Default is Native Resolution:默认情况下,分辨率为设备的原生分辨率。如果取消此选项,用户可以手动设置游戏的分辨率。
有问题再翻再找再看
unity整理功能点的架子
1. Unity部分 Unity组件思想(1)
- 组件介绍
- Unity内部高级模型和材质
- Scene开顶遮挡物体操作
- 自定义组件
- Transform组件
- GameObject
2. Unity部分 Unity组件思想(2)
- 预制体
- Unity生命周期函数
- Invoke
- 协程
- 常用工具类
3. Unity部分 Unity2D
- Unity2D介绍
- Sprite和SpriteRenderer
- 2D物理系统:刚体
- 2D物理系统:碰撞
- 2D物理系统:触发
4. Unity部分 输入系统
- 输入系统介绍
- 键盘输入检测
- 触摸输入检测
- InputManager
- 案例:小球移动
- 案例:小球跳跃
5. Unity部分 UI系统(1)
- UI系统介绍
- Image组件
- Text组件
- Button组件
- InputField组件
- Toggle组件
6. Unity部分 UI系统(2)
- DropDown组件
- Scroll View组件
- 复杂布局
7. Unity部分 Unity3D
- 3D坐标系
- 3D物理系统:刚体
- 3D物理系统:碰撞
- 3D物理系统:触发
- 3D物理系统:物理材质
- 物理挤出线
- 刚体移动
- 案例:方块保卫战—场景搭建
- 案例:方块保卫战—小球逻辑
- 案例:方块保卫战—UI逻辑
8. Unity部分 动画系统
- 动画系统介绍
- Unity内动画组件
- Animator Controller
- 动画系统参数设置
- 动画系统的使用
- 动画状态机
- 动画过渡
- 动画事件
- 案例:3D角色动画
9. Unity部分 导航系统
- NavMeshAgent组件
- Navigation面板
- NavMeshObstacle组件
- 案例:敌人巡逻
10. Unity部分 Unity渲染基础
- 摄像机
- 灯光
- Shader概念
- 粒子系统
11. Unity部分 Unity资源管理
- 动态导入与导出
- 资源实例化
- Resource资源加载
- 常用存档类—PlayerPrefs
- 游戏对象池
- 玩家保存系统
- 案例:玩家保存系统
unity的资源在每个平台会自动把能优化的资源优化成合适的格式,所以资源打包进去可能会改变格式 idle(空闲状态常用的词,算黑话吧) 复杂动作,比如边跑边跳,可以用子状态机,或者BlendTree混合树
多语言的时候,文本框自适应,可以避免显示不全的问题 如果每次调试很麻烦就写成public变量,实时调试
奇怪的组件积累
Shadow组件,可以给对象加阴影