Unity
Unity是一套完善体系与编辑器的跨平台游戏开发工具,也可以称之为游戏引擎。游戏引擎是指一些编写好的可以重复利用的代码与开发游戏所用的各功能编辑器。
- 基于C#编程,易上手,高安全性
- 独特的面向组件游戏开发思想让游戏开发更加简单易复用
- 十分成熟的所见即所得开发编辑器
- 良好的生态圈
- 强大的跨平台,可以制作PC、主机、手机、AR、VR等多平台游戏
下载安装
在中文网站中https://unity.cn/releases
下载Unity Hub即可。
下载好之后,点击进入软件,然后选择Unity Editor
的安装地址等待下载安装。
可以选择使用中文语言。上图中的设置按钮,点击后
完成后,还需要记得要申请一个许可证,才可以使用,个人版的即可。
安装一个LTS版本的编辑器
勾选上visual studio的开发工具,如果有,可以不用再去安装了
整完后,创建一个项目,然后摸索下它里面的功能菜单。。
模型商店
在页面中找子集需要的素材即可
脚本声明周期
在unity中创建一个C#脚本
进入vs编辑器
public class Test : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
包含两个方法,start()和Update(),而其他的生命周期方法
- Awake():最早调用,一般在此实现单例模式
- OnEnable():组件激活后会被调用,在Awake()后会调用一次
- Start():在Update()前调用一次,在OnEnable()后调用,可以在这里设置一些初始值
- FixedUpdate():固定频率调用方法,每次调用与上次调用的时间间隔相同
- Update():帧率调用方法,每一帧调用一次,每次调用与上次调用的时间间隔不相同
- LateUpdate():在Update()每调用一次后,紧跟着调用一次
- OnDisable():与OnEnable()相反,组件未激活时调用
- OnDestory():被销毁后调用一次
OnEnable和OnDisable()的触发
Update()和LateUpdate()
当程序开始执行时,每走一帧都会调一次Update()方法,而Update()执行完毕后,LateUpdate()跟着执行一次
FixedUpdate()
每秒走多少帧,此方法就调用多少次
OnDestory()
当对象的组件被移除或者程序结束时,会调用OnDestory()。
脚本执行顺序
当一个对象加上了两个脚本后,他们的执行顺序是从下向上运行的,并且它会先执行全部脚本
的Awake()、OnEnable()、Start()...
那么,我们可以将先执行的脚本方法实现,放入到生命周期最优先的方法中即可。
或者,我们可以给脚本设置执行的顺序:
设置后,它的执行顺序是:先执行Test1的Awake(),再执行Test2的Awake(),然后执行Test1的Start(),执行Test2的Start();
物体做标记
给物体打个Tag,以便分类,方便查找。
当找不到想要的tag标签是,可以自定义创建一些标签进行使用。
还有Layer图层, 可以更大方面作区分,比如区分是人物、场景等,可以用Layer图层作区分。
而像人物又可以分为玩家和敌人,再细分至Tag标签。
在摄像机对象中,可以设置要拍摄的图层。
勾掉后的图层,在实际相机拍摄中就不会再显示出来了。
向量
标量:只有大小的量
向量:既有大小,又有方向。
向量的摸:就是向量的大小
单位向量:大小为1的向量
单位化,归一化:把向量转为单位向量的过程。
点乘:得到两个向量之间的夹角:A向量*B向量=x1*x2+y1*y2=cosθ
,而这个θ就是点乘的值(度数)
预制体与变体
当给预设体中增加组件时,场景中的物体也会自动增加上,但是当场景中的物体增加组件时,它是场景中物体独有的组件,可以添加至预设体中。
在场景物体属性栏中,新增的组件时呈蓝色边。
从场景物体中,添加至预设体中
完成后,蓝边会消失。
注意后缀是prefab的文件是预制体,可以导出给别人使用,也可以导入到自己的项目中使用。
如果预制体需要再次呈现出不同的预制体,那么可以使用在原始预制体的基础上进行叠加,那么当原始预制体改变时,新的预制体跟着变更
Vector3的使用
创建一个C#脚本,在start()方法中
public class VectorTest : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
// 向量
Vector3 v = Vector3.right;
Vector3 v2 = Vector3.forward;
// 计算两点之间的角度
Debug.Log(Vector3.Angle(v, v2));
// 计算两点之间的距离
Debug.Log(Vector3.Distance(v, v2));
// 点乘
Debug.Log(Vector3.Dot(v, v2));
// 叉乘
Debug.Log(Vector3.Cross(v, v2));
}
// Update is called once per frame
void Update()
{
}
}
方向描述,欧拉角与四元数
常用方法
public class RotateTest : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
// 旋转:欧拉角,四元数
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;
}
// Update is called once per frame
void Update()
{
}
}
Debug
开发中,可能会遇到需要调试的情况。
public class DebugTest : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
Debug.Log("test");
Debug.LogWarning("test2");
Debug.LogError("test3");
}
// Update is called once per frame
void Update()
{
// 画一条直线,起点和终点
Debug.DrawLine(new Vector3(1,0,0), new Vector3(1,1,0), Color.blue);
// 画一条射线 起点,射线
Debug.DrawRay(new Vector3(1,0,0), new Vector3(1,1,0), Color.red);
}
}
动态修改物体属性,物体类的使用
我们需要获取当前脚本所挂载的游戏物体。
给物体绑定脚本后,脚本内部自动会获取到物体实例并且放入到 gameObject
对象中。
// 此脚本会自动获取到游戏物体
// GameObject go = this.gameObject;
// 游戏物体的名称
Debug.Log(gameObject.name);
// 游戏标签
Debug.Log(gameObject.tag);
// 图层 , 注意这里返回的是索引值 0是default层
Debug.Log(gameObject.layer);
当物体中有子物体时,需要在脚本中声明子物体变量,然后再父物体中设置子物体的游戏变量。
public class EmptyTest : MonoBehaviour
{
public GameObject cube;
// Start is called before the first frame update
void Start()
{
// 此脚本会自动获取到游戏物体
// GameObject go = this.gameObject;
// 游戏物体的名称
Debug.Log(gameObject.name);
// 游戏标签
Debug.Log(gameObject.tag);
// 图层 , 注意这里返回的是索引值 0是default层
Debug.Log(gameObject.layer);
// 立方体名称
Debug.Log(cube.name);
}
// Update is called once per frame
void Update()
{
}
}
上面创建了一个cube的GameObject对象。然后给设置上值。
获取对象的激活状态
// 当前真正的激活状态(跟父物体的状态有关系)
Debug.Log(cube.activeInHierarchy);
// 当前自身物体的激活状态
Debug.Log(cube.activeSelf);
获取对象的组件
同对象一样,当脚本和对象(物体)绑定后,它会自动获取到对象的各种组成,直接this.调用即可。
// 获取物体的transform组件
Debug.Log(transform.position);
// 获取其他的组件
BoxCollider bc = GetComponent<BoxCollider>();
// 获取当前物体的子物体身上的某个组件
// GetComponentInChildren<CapsuleCollider>(bc);
// 获取当前物体的父物体身上的某个组件
// GetComponentInParent<BoxCollider>();
// 手动代码添加组件
gameObject.AddComponent<AudioSource>();
通过其他方式获取物体
// 通过游戏物体的名称获取游戏物体
GameObject test = GameObject.Find("Test");
Debug.Log(test.name);
// 通过标签获取物体
test = GameObject.FindWithTag("Enemy");
// 设置游戏物体不可用
test.setActive(false);
预设体动态生成实体
先给脚本中设置一个预设体的变量,然后在物体中绑定好预设体对象。
public class EmptyTest : MonoBehaviour
{
public GameObject cube;
public GameObject Prefab;
// Start is called before the first frame update
void Start()
{
// 使用预设体动态生成物体,会返回一个GameObject对象
Instantiate(Prefab);
}
// Update is called once per frame
void Update()
{
}
}
游戏时间的使用
创建一个物体,绑定上脚本,在脚本中使用游戏时间的计数
public class TimeTest : MonoBehaviour
{
// Start is called before the first frame update
float timer = 0;
void Start()
{
// 游戏开始到现在所花的时间
Debug.Log(Time.time);
// 时间缩放值(时间倍速)
Debug.Log(Time.timeScale);
// 固定时间间隔(每一帧之间的间隔时间数)
Debug.Log(Time.fixedDeltaTime);
}
// Update is called once per frame
void Update()
{
// timer可以用来计数游戏总时间
timer += Time.deltaTime;
// 上一帧到这一帧所用的游戏时间
Debug.Log(Time.deltaTime);
// 如果总时间 > 3s,我可以做一些事情
if (timer > 3)
{
}
}
}
路径权限要理清,Application很重要
假如在Project中新建了一个a.txt文件,在代码中如何读取呢
public class ApplicationTest : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
// s游戏数据文件夹路径(只读的,打包后加密压缩,所以看不到)
Debug.Log(Application.dataPath +"/a.txt");
// 持久化文件夹路径(可写的)
Debug.Log(Application.persistentDataPath);
// StreamingAssets文件夹路径(只读,打包后不会加密压缩,这些文件可以放入此目录下,可以自己创建一个StreamingAssets的目录)
Debug.Log(Application.streamingAssetsPath);
// 临时文件夹,可以将临时数据写入
Debug.Log(Application.temporaryCachePath);
// 控制是否在后台运行
Debug.Log(Application.runInBackground);
// 用浏览器打开一个URL
Application.OpenURL("www.baidu.com");
// 退出游戏
Application.Quit();
}
// Update is called once per frame
void Update()
{
}
}
切换场景
在Assets的scenes中创建一个新的场景
然后将这两个场景,放入到打包的场景中,并且会带有索引
在SampleScene场景中跳转到MyScene中
using UnityEngine.SceneManagement;
public class SceneTest : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
// 场景跳转,可以根据名称或索引进行跳转
// SceneManager.LoadScene(1);
// Additive:多场景叠加
// Single:单场景
SceneManager.LoadScene("MyScene", LoadSceneMode.Additive);
// 获取当前场景
Scene scene = SceneManager.GetActiveScene();
// 场景名
Debug.Log(scene.name);
// 场景路径
Debug.Log(scene.path);
// 场景是否已经被加载
Debug.Log(scene.isLoaded);
// 场景索引
Debug.Log(scene.buildIndex);
// 获取场景中的所有游戏对象(根级别的)
GameObject[] gos = scene.GetRootGameObjects();
Debug.Log(gos.Length);
// 场景管理类
// 创建新场景
Scene newScene = SceneManager.CreateScene("newScene");
// 已加载场景的数量
Debug.Log(SceneManager.sceneCount);
// 卸载场景
SceneManager.UnloadSceneAsync(newScene);
}
// Update is called once per frame
void Update()
{
}
}
异步加载场景获取进度
using UnityEngine.SceneManagement;
public class SceneTest : MonoBehaviour
{
AsyncOperation operation;
// Start is called before the first frame update
void Start()
{
StartCoroutine(loadScene());
}
// 协程方法用来异步加载场景
IEnumerator loadScene() {
operation = SceneManager.LoadSceneAsync(1);
// 让用户手动跳转场景
operation.allowSceneActivation = false;
yield return operation;
}
float timer = 0;
// Update is called once per frame
void Update()
{
// 每一帧都记录 加载的进度 值范围 0-0.9
Debug.Log(operation.progress);
timer += Time.deltaTime;
// 5s后跳转
if (timer > 5)
{
operation.allowSceneActivation = true;
}
}
}
了解transform
游戏物体的父子集关系,是由transform控制的。
创建一个三层关系的几个物体,然后在中间层绑定上脚本代码
常用属性:
// 获取世界位置
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);
// 旋转 相对于local旋转
transform.Rotate(Vector3.up, 1);
// 绕物体旋转 绕着(0,0,0)的up轴,每一帧转5度
transform.RotateAround(Vector3.zero, Vector3.up, 5);
// 移动 每一帧移动0.1
transform.Translate(Vector3.forward * 0.1f);
父子关系:
// 获取父物体
transform.parent.gameObject;
// 获取子物体的数量
transform.childCount;
// 解除与子物体的父子关系
transform.DetachChildren();
// 获取子物体
// 方式一 通过子物体的名称获取子物体的transform
Transform trans = transform.Find("Child")
// 方式二 通过子物体的索引获取子物体的transform
trans = transform.GetChild(0);
// 判断物体是不是另一个物体的子物体
bool res = trans.IsChildOf(transform);
Debug.Log(res);
// 设置为父物体
trans.SetParent(transform);
基本键鼠操作
键鼠操作,要在Update()中做监测,每一帧都要检测到。
void 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");
}
}