Unity 脚本开发入门

Unity

Unity脚本开发入门

生命周期函数

Awake()

在对象出生时调用,类似构造函数,一个对象只会调用一次;

OnEnable()

依附的GameObject对象每次激活时调用

Start()

从自己被创建出来后,第一次帧更新之前调用一个对象只会调用一次

FixedUpdate()

物理帧更新,固定间隔时间执行,间隔时间可以在unity界面中进行设置

Update()

逻辑帧更新,每帧执行

LateUpdate()

每帧执行,于Update()之后执行

OnDisabel()

依附的GameObject对象每次激活时调用

OnDestroy()

对象销毁时调用,依附的GameObject对象被删除时调用

Inspector窗口的显示(让自定义的成员可以在Inspector窗口中显示并被调试)

私有和保护作用域下的成员变量无法被显示在Inspector窗口上
如果要让私有和保护的成员变量也被显示(在声明变量之前(代码上一行中)添加[SerializeField])

公共的变量可以被显示并被编辑(在Inspector窗口上)
如果不想让公共的被显示、编辑:在声明之前加[HideInspector]
大部分类型都可以显示编辑(注意不可以显示字典、结构体!!!)
如何让自定义类也可以被访问:在代码上一行加上序列化特性[System.Serializable]
字典无论如何都不可以被显示在窗口中!!!!

一些辅助特性

//[Header]:分组说明特性,说明成员特性以及进行分组;

MonoBehaviour中的重要特性

重要成员

获取依附的GameObject:

print(this.gameObject.name); //(获取GameObeject对象的名字)

获取依附的GameObject的位置信息:

print(this.gameObject.position);    //获取位置
print(this.gameObject.eulerAngles);   //获取角度
printf(this.gameObject.lossyScale);   //获取缩放大小

获取脚本是否激活

this.enable = true;   //激活
this.enable = false;  //失活

获取其他脚本对象依附的GameObject信息

print(otherLesson.gameObject.transform);  //获取otherLesson中的GameObject的transform的信息

重要方法

得到自己挂载的单个脚本
通过脚本名获取:

   Lesson3_test t = this.GetComponent("Lesson3_test") as Lesson_test;  //获取Lesson3_test的脚本,如果获取失败,则是没有对应脚本,默认返回空
   print(t);

通过Type来获取:

   Lesson3_test t = this.GetComponent(typeof(Lesson3_test)) as Lesson3_test;

通过泛型来获取:(不用二次转换)

   t = this.GetComponent<Lesson3_test>();
   if (t != null)
   {
    //进行逻辑处理   (更为保险安全)
   }

得到自己挂载的多个脚本

Lesson3[] array = this.GetComponents<Lesson3>();
printf(array.Length);
List<Lesson3> list = new List<Lesson3>();
this.GetComponents<Lesson3>(list);
print(list.Count);

得到子对象挂载的脚本(它默认也会找自己身上是否挂载了该脚本)

//函数有一个参数的,默认不传 是false 意思为如果子对象失活,不会去找这个对象上是否有某个脚本
//而如果传true 即使失活了也会进行寻找脚本
//得单个
Lesson3_test t = this.GetComponentInChildren<Lesson3_test>(true);
print(t);

//得多个
Lesson3_test[] lts = this.    //通过数组来获取     GetComponentsInChildren<Lesson3_test>(true);
print(lts.Length);

List<Lesson3_Test> list2 = new List<Lesson3_test>(); //()中默认为false
this.GetComponentsInChildren<Lesson3_test>(true , list2);
print(list2.Count);

得到父对象挂载的脚本

t = this.GetComponentInParent<Lesson3_test>();
print(t);

寻找时也会找儿子的儿子、父亲的父亲……
尝试获取脚本

Lesson3_test list;
if(this.TryGetComponent<Lesson3_test>(out list))
{
   //进行逻辑处理
}

GameObject

GameObject中的成员变量

①名字(this.gameObject.name); ②是否激活(this.gameObject.activeSelf); ③是否是静态(this.gameObject.static);
④层级(this.gameObject.layer); ⑤标签(this.gameObject.tag); ⑥transform(this.transform.position等);

GameObject中的静态方法

创建自带几何体:

GameObject Cube = GameObject.CreatPrimitive(PrimitiveType.Cube(等)) (可以用变量进行储存);
Cube.name = "cube";
//只要得到了一个GameObject对象,我们就可以得到挂在其上的脚本的所有信息

查找对象相关知识点:

//1.查找单个对象
//通过对象名查找
//没有找到就会返回null
GameObject obj = GameObject.Find("obj2");  //效率较低
if (obj != null)
{
   print(obj.name);
}
else
{
   print("Not Found");
}
//通过标签(tag)来查找
GameObject obj1 =GameObject.FindWithTag("obj2");   //同上面那个方法效果一样
if (obj1 != null)
{
   print("Find with tag : "+obj2.name);
}
else
{
   print("Not found with tag.");
}
//注意!!!无法找到失活的对象!!!
//如果场景中存在多个满足条件的对象,我们无法确定找到具体哪个!!!

//2.查找多个对象
//找多个对象的API,只能通过tag来寻找
GameObject[] objs = GameObject.FindGameObjectsWithTag("Player");
printf("找到tag为player对象的个数"+objs.Length);
//只能找到激活的对象!!!无法找到失活的对象!!!

实例化对象(克隆)的方法:

public GameObject obj;
GameObject.Instantiate(obj);

删除对象的方法:

GameObject.Destroy(obj , 5);  //延迟5秒删除对象
GameObject.Destroy(this);  //删除脚本对象
//注意!!Destroy方法不会马上移除对象,一般在下一帧进行移除 (先给对象添加一个移除标识) -> 这样可以避免卡顿
//如果要马上移除
GameObject.DestroyImmediate(obj);

//过场景不移除
//默认情况在切换场景时场景对象都会被自动删除出,如果不想移除:
GameObject.DontDestroyOnLoad(obj);

Time

Time相关内容的用途

主要用于游戏中参与位移、计时、时间暂停等等

时间缩放比例

①时间停止:

Time.timeScale  = 0;

②时间回复正常

Time.timeScale = 1;

③时间设置为2倍速

Time.timeScale = 2;

帧间隔时间

用途:主要用来计算位移(x = v*t); 可根据需求选择参与计算的间隔时间;如果希望游戏暂停时就不动的,就使用deltaTime ; 而如果希望不受暂停影响,就使用unscaledDeltatime。

含义:最近的一帧用了多长时间(second)
①受scale影响 (scale – > 时间缩放比例)

Time.deltaTime  //Or: Time.time

②不受scale影响

Time.unscaledDeltaTime

物理帧间隔时间 FixedUpdate

①受scale影响

Time.fixedDeltaTime

②不受scale影响

Time.fixedUnscaledDeltaTime

帧数

可以将一帧视为一个循环~

Time.frameCount  //为int类型的成员

Transform

Transform 的主要用途

游戏对象(GameObject)位移、旋转、缩放、父子关系、坐标转换等相关操作都由它来处理,是unity提供的极其重要的类

必备:Vector3基础

用途:主要用于表示三位坐标系中的一个点或者一个向量
上代码看看示例:

Vector3 v = new Vector3();
v.x = 10;
v.y = 10;
v.z = 10;
//如果只传x,y,那么默认z为0
Vector3 v2 = new Vector3(10 , 10);
//直接传三个
Vector3 v3 = new Vector3(10 , 10 , 10);
//或者
Vector3 v4;
v4.x = 10;
//略

//------运算--------
print(v1 + v2); //输出结果为v1 、 v2的x , y , z坐标互相对应相加的结果(其他的同理)

//常用
Vector3.zero //000
Vector3.right  //100
Vector3.left   //-100
Vector3.forward  //001 面朝向(上)
Vector3.back     //00-1 面朝向(后)
Vector3.up        //010面朝上
Vector3.down     //0-10 面朝下

//常用的方法
//计算两点之间距离
Vector3.Distance(v1 , v2);  //自动计算v1和v2两点之间的距离
//其他方法日后再看吧~

位置

直接看代码

//相对世界坐标
this.transform.position;  //Or  this.gameObject.transform.position
//注意!通过.position得到的坐标,如果对象有父子关系,且父对象位置不在原点,那么得到的坐标和面板上的有所不同(面板上的是相对于父坐标的)

//相对父坐标
this.transform.localPosition;

位置的赋值不可以直接改x,y,z某一个值,只能整体改变

//如果想只改变其中几个/一个
//直接赋值
this.transform.position = new Vector3(19, this.transform.position.y , this.transform.position.z);
//或者也可以先取出来再赋值
//虽然不可以直接改transform的xyz , 但是Vector3是可以直接改xyz的,因此可以先取出来改Vector3再重新赋值
Vector3 vPos = this.transform.localPosition;
vPos.x = 10;
this.transform.localPosition = vPos;

对象当前的各朝向:(是实时变化的!!!)

//对象当前面朝向
this.transform.forward;
//对象当前头顶朝向
this.transform.up;
//对象当前右手边
this.transform.right;
//注意!如果想的对象当前的一个朝向,那么就是通过transform.来得到!!!

位移

用公式来计算进行移动
this.position += this.transform.forward*MoveSpeed*Time.deltaTime; //其中MoveSpeed是自己添加的变量,示例中用了forward,那么就会一直朝向forward这个方向进行移动。
用API来进行移动
this.transform.Translate(this.transform.forward*MoveSpeed*Time.deltaTime,Space.World);  //第二个参数是相对于世界坐标的位置
//如果要相对于自身:Space.World 替换成 Space.Self

角度与旋转

角度相关

相对世界坐标角度:

this.transform.eulerAngle;

相对父对象角度:

this.transform.localEulerAngles;

设置角度和设置位置一样,不可以单独设置xyz,要一起设置

this.transform.eulerAngle = new Vector3(10,10,10);

如果希望改变Inspector面板上的内容,那要改变相对父对象的!

旋转相关

通过计算公式:同位置的移动运算写法差不多哦~
利用API计算:
自转:
围绕每个轴转

this.transform.Rotate(new Vector3(10,10,10) * Time.deltaTime , Space.World);  //相对世界坐标系来旋转

围绕某个轴转

this.transform.Rotate(Vector3.up,10*Time.deltaTime);  //默认围绕自己的轴
this.transform.Rotate(Vector3.up,10*Time.deltaTime , Space.World);  //围绕相对于世界的轴来旋转

相对于某一个点来转:

//参数一:相对于哪一个点来转
//参数二:相对于某一个点的某一个轴来旋转
//参数三:旋转的速度、角度 (速度*时间)
this.transform.Rotate(Vector3.zero , Vecto3.right , 10*Time.deltaTime);

缩放与看向

缩放
//相对世界坐标系
this.transform.lossyScale;
//相对父对象坐标系
this.transform.localScale;

Unity没有提供有关缩放的API!!!
那么如何改变物体的缩放呢?

this.transform.localScale += Vector3.one * Time.deltaTime;  //每个轴的方向都进行缩放,其中Vector3.one = (1,1,1)
看向

让一个对象的面朝向 一直朝向一个点或者一个对象

//看向一个点
this.transform.LookAt(Vector3.zero);
//OR
this.transform.LookAt(new Vector3(0f,0.53f,0f));
//看向一个对象
//先在上方声明一个对象:
public Transform Target;
//而后让物体看向这个对象
this.transform.LookAt(Target);

父子关系

获取父对象:

this.transform.parent.name;

断绝父子关系:

this.transform.parent = null;

设置父子关系(认爹)

this.transform.parent = GameObject.Find("父类对象名").transform;

还可以通过API来设置父子关系

//断绝关系
this.transform.Setparent(null);
//建立关系
this.transform.Setparent(GameObject.Find("父类对象名").transform);

父类对象同子类对象断绝联系:

this.transform.DetachChildren();  //与所有子对象断绝关系(但是不可以影响和孙子的关系)

获取子对象:

//按照名字查找子对象
//Find方法可以找到失活的对象!!!(和GameObject的方法有所不同哦!)
//Find不可以找到孙子!!!
this.transform.Find("子对象名字");  //找到儿子的transform信息

//遍历子对象
//如何找到有多少个儿子?
this.transform.childenCount;   //注意!失活的子对象也会被计入在内!(还有,依旧找不到自己的孙子!)
//通过索引来找儿子
this.transform.GetChild(number); //number是要找多少个儿子的数量
儿子的操作(作为儿子可以干什么)

判断自己的爹是谁:

if (this.IsChildOf("父类对象名"))
{
   print(Fathername+"是我爹");
}

得到自己作为儿子的编号:

this.GetSiblingIndex();

将自己设置为第一个儿子:

this.SetAsFirstSibling();

把自己设置为最后一个儿子:

this.SetAsLastSibling();

把自己设置为指定个儿子:

this.SetSiblingIndex(number);  //number为编号,注意不要超过范围!!!(如果溢出了,那就设置成最后一个儿子)

坐标系转换

世界坐标转本地坐标:
点转换为相对对象本身(本地)坐标系的点

//受缩放影响
print("转换后的点"+this.tranform.InverseTransformPoint(Vector3.forward));

世界坐标系的方向转换为相对对象本身坐标系的方向

//受缩放的影响
print("转换后的方向"+this.tranform.InverseTransformDirection(Vector3.forward));
//不受缩放的影响
print("转换后的方向"+this.tranform.InverseTransformVector(Vector3.forward));

本地坐标转换为世界坐标:
本地坐标的点转化为相对于世界坐标的点

//受缩放影响
print("转换后的点"+this.transform.TransformPoint(Vector3.foward));

本地坐标的方向转换为相对于世界坐标的方向

//受缩放影响
print("转换后的方向"+this.transform.TransformDirection(Vector3.forward));
//不受缩放影响
print("转换后的方向"+this.transform.TransformVector(Vector3.forward));

鼠标键盘输入

获取鼠标在屏幕的位置

Input.mousePosition;

检测鼠标输入

//鼠标按下的一瞬间:
Input.GetMouseButtonDown(0);   //0-左键;1-中键;2-右键
//返回值为布尔值!!!!

//鼠标松开的一瞬间:
Input.GetMouseButtonUp(0);

//鼠标长按、按下、松开的时候:
Input.GetMouseButton(0);

//中键滚动(返回值:y -> -1向下,0没有滚动,1向上)
Input.MouseScrollDelta;   //返回值是Vector的值,中键的滚动会改变其中的Y值

检测键盘输入

//获取键盘输入
Input.GetKeyDown(KeyCode.W);   //检测到W键被按下

//传入字符串的重载
Input.GetKeyDown("Q"); //会报错,只能传入小写的字符串!
Input.GetKeyDown("q");  //正确写法

//键盘抬起
Input.GetKeyUp(KeyCode.W);  //检测到W键抬起

//键盘长按
Input.GetKey(KeyCode.Space);  //检测到空格键被长按

检测默认轴输入

直接上示例:

//键盘AD键切换时返回 -1 到 1 之间的变换
print(Input.GetAxis("Horizontal"));
//键盘WS键切换时返回 -1 到 1 之间的变换
print(Input.GetAxis("Vertical"));
//鼠标横向移动时 -1 到 1 左右
print(Input.GetAxis("Mouse X"));
//鼠标竖向移动时 -1 到 1 左后
print(Input.GetAxis("Mouse Y"));

//GetAxis方法的返回值会有渐变感(-1慢慢加到1那样~)
//GetAxisRaw 方法和 GetAxis 方法相同,但返回值仅有-1 0 1 ,不会有中间值!

Screen

屏幕相关

常用静态属性

直接来看代码

//获取屏幕分辨率
Resolution r = Screen.Currentresolution;
print("当前屏幕宽"+r.width+"高"+r.height);

//屏幕当前窗口宽高
print(Screen.width+Screen.height);     //Game 窗口

//屏幕休眠模式(两个常量!注意!)
Screen.sleepTimeout = SleepTime.NeverSleep;  //从不息屏
Screen.sleepTimeout = SleepTime.SystemSetting;
不常用静态属性

上代码

//运行时是否全屏模式
Screen.fullScreen = true;
//独占全屏
Screen.fullScreenMode = FullScreenMode.ExclusiveFullScreen;
//全屏窗口
Screen.fullScreenMode = FullScreenMode.FullScreenWindow;
//最大化窗口
Screen.fullScreenMode = FullScreenMode.MaximizedScreenWindow;
//窗口模式
Screen.fullScreenMode = FullScreenMode.Windowed;

//移动设备屏幕转向相关
//允许自动旋转为左横向
Screen.autorotateToLandscpeLeft = true;
//其他方向:Rigth .......
静态方法

设置分辨率

Screen.SetResolution(1920 , 1080 , true);  //填true默认全屏!

Camera

Camera上的可编辑参数

Camera的可编辑参数

Camera代码相关

重要静态成员
//获取摄像机
Camera.main.name   //注意,摄像机的tag(标签)应该为maincamera!!!!!!
//获取摄像机数量
Camera.allCamerasCount
//得到所有摄像机
Camera[] allCamera = Camera.allCameras;
//渲染相关委托
//摄像机剔除前处理的委托函数
Camera.onPreCull += (c) =>{};
//摄像机渲染前处理的委托
Camera.onPreRender += (c) => {};
//渲染后处理的委托
CameraonPosRender += (c) => {};
重要成员
//1.界面上的参数 都可以在Camera中获取到
//如:获取主摄像机上的深度并设置
Camera.main.depth = 10;
//2.世界坐标转屏幕坐标
//转换过后,x和y对应的就是屏幕坐标,z对应的就是这个3D物体离摄像机有多远
Vector3 v = Camera.main.WorldToScreenPoint(this.transform.position);
//屏幕坐标转世界坐标
//示例:让一个物体跟随鼠标移动
Vector3 v = Input.mousesPosition;
v.z = 10;
obj.position = Camera.main.ScreenToWorldPoint(v);

碰撞检测

拥有特殊的生命周期函数!

物理碰撞检测响应函数
//碰撞触发时接触会自动执行这个函数!
private void OnCollisionEnter(Collision collision)    //固定写法!(参数名collision可以改变)
{
   //Collision类型的参数包含了碰到自身物体的相关信息:(自动传入到该函数中!)
   //1-碰到的对象的碰撞器的相关信息(collision.collider)
   //2-碰到对象的依附对象(比如collision.gameObject)
   //3-碰撞对象的依附对象的位置信息(如collision.transform)
   //4-触碰点数相关(collision.contactCount)
   //    如获取接触点的坐标:
   ContactPoint[] pos = collision.contacts;

   //只要得到了碰撞到的物体的任何一个信息,就可以得到其所有的信息!!!(在下面两个函数中也是一样的!!!)
}
//碰撞结束分离时会自动执行的函数
private void OnCollisionExit(Collision collision)
{

}
//两个物体相互接触摩擦时会不停调用该函数
private void OnCollisionStay(Collision collision)
{

}
触发器检测响应函数
//触发开始的函数 当第一次接触时会自动调用
private void OnTriggerEnter(Collider other)
{

}
//两个对象分离时调用
private void OnTriggerExit(Collider other)
{

}
//两个对象一直接触时一直调用
private void OnTriggerStay(Collider other)
{

}
//同理,当得到与自己碰撞的物体(的脚本)后可以得到关于其的任何信息!!!
要明确什么时候会相应碰撞函数

① – 只要挂载的对象 能和别的物体产生碰撞或者触发 那么对应的上面6个函数就能够被响应
② – 6个函数并非都要写,一般根据开发需求来写
③ – 如果是一个异形物体,刚体在父对象上,如果想通过子对象上挂挂脚本检测碰撞是不行的,必须挂载到这个刚体的父对象上才行
④ – 要明确物理碰撞和触发器碰撞的区别

刚体加力

刚体自带添加力的方法
void Start()
{
   //1-首先获取刚体组件
   rigidBody = this.GetComponent<RIgidbody>();

   //2-添加力
   //相对世界坐标
   //!!!!加力过后对象停止快慢由阻力来影响!!!!
   rigidBody.AddForce(Vector3.forward * 10);  //沿着相对世界坐标系的Z轴正方向添加一个力
   //相对本地坐标
   rigidBody.AddRelativeForce(Vector3.forward*10);   //沿着物体自己的面朝向(物体自身z轴的正方向)添加一个力
   //如果想在相对世界坐标系的方法中让物体相对于本地坐标系添加力
   rigidBody.AddForce(this.transform.forward*10);    //同上一句代码效果相同

   //3-添加扭矩力,让物体旋转
   //相对世界坐标系
   //同2中一样,物体旋转的停止和阻力有关!!!
   rigidBody.AddTorque(Vector3.up*10);   //绕着世界坐标系的y轴旋转
   //相对本地坐标系
   rigidBody.AddRelativeTorque(Vector3.up*10);
   //Or
   rigidBody.AddTorque(this.transform.up*10);

   //4-直接改变速度
   rigidBody.velocity = Vector3.forward*5;   //朝向世界坐标系的z轴正方向添加一个速度(添加速度一般都是相对世界坐标系!)

   //5-模拟爆炸效果
   //第一个参数:爆炸力的大小 ; 第二个参数:爆炸中兴 ; 第三个参数:爆炸的半径大小.
   rigidBody.AddExplosionForce(10 , Vector3.zero , 100);
}
void Update()
{
   //如果有阻力但想让物体一直移动(相对世界坐标)
   rigidBody.AddForce(Vector3.forward*10);  //每一帧都给物体添加一个力,一直推动物体运动
   //想让物体一直旋转(有阻力的话)(相对世界坐标)
   rigidBody.AddTorque(Vector3.up*10);
}
力的几种模式
//第一个参数:力的方向 * 力的大小 ; 第二个参数:力的模式(影响力的计算方式)
rigidBody.AddForce(Vector3.forward*10 , ForceMode.Acceleration);

不同模式的说明:
Acceleration – 给物体增加一个持续的加速度,忽略物体的质量;
Force – 给物体添加一个持续的力,与物体的质量有关;
Impulse – 给物体添加一个瞬时的力,与物体的质量有关,忽略时间(默认为1);
VelocityChange – 给物体添加一个瞬时速度,忽略质量;

Application类

直接上代码看看示例:

void Start()
{
   //输出游戏路径(只读,文件中的内容会被加密压缩 -- 游戏被打包后找不到此文件)
   Debug.Log(Application.dataPath + "/新建文本文档.txt");
   //持久化游戏路径(显示系统给游戏分配的空间,可写)
   Debug.Log(Application.persistentDatePath);
   //StreamingAssets文件夹路径(只读,但打包后文件会被显示)
   Debug.Log(Application.streamingAssestsPath);
   //临时文件夹
   Debug.Log(Application.temporaryCachePath);
   //控制是否在后台运行 (输出值为布尔值!!!)
   Debug.Log(Application.runInBackGround);
   //打开URL
   Application.OpenURL("https://www.bilibili.com/video/BV1gQ4y1e7SS?spm_id_from=333.788.videopod.episodes&vd_source=76c44b47d9618773be055bc93a039529&p=26");
   //退出游戏
   Application.Quit();
}

Scene类 & Scene管理类

直接上代码看看:

//注意导入:
using UnityEngine.SceneManagement;
void Start()
{
   //同步加载场景(可以通过索引和名称的方式加载特定场景)
   //场景跳转
   SceneManager.LoadScene("场景名称");
   //获取当前场景
   Scene scene = SceneManager.GetActiveScene();
   //验证是否获取了正确场景(打印场景名称)
   Debug.Log(scene.name);
   //检测场景是否已经被加载
   Debug.Log(scene.isLoaded);
   //获取场景路径
   Debug.Log(scene.path);
   //场景索引
   Debug.Log(scene.buildIndex);
   GameObject[] gos = scene.GetRootGameObjects();      //获取场景中所有的GameObject对象

   //场景管理类
   //获取当前加载的场景数量
   Debug.Log(SceneManager.sceneCount);
   //创建新场景
   Scene NewScene = SceneManager.CreatScene("NewScene");
   //删除场景
   SceneManager.UnloadSceneAsync(NewScene);
   //加载场景
   SceneManager.LoadScene("MyScene" , LoadSceneMode.Additive);   //将新场景与现在的场景融合(如果是single,则表示替换)
}
异步加载场景并显示进度

看代码:

using UnityEngine.SceneManagement;

public AsyncTest : MonoBehaviour
{
   AsyncOperation operation;

   void Start()
   {
      StartCoroutine(loadScene());
   }

   //协程方法用来异步加载场景
   IEnumerator loadScene()
   {
      SceneManager.LoadSceneAsync("要加载的场景名称");
      //加载完场景不要自动跳转
      operation.allowSceneActivation = false; //默认为true,加载完场景后会自动跳转到场景,设置为false则不会自动跳转
      //设置为5秒后再跳转:
      timer += Time.deltaTime;
      if (timer > 5)
      {
         operation.allowSceneActivation = true;
      }
      yield return operation;
   }

   void Update()
   {
      //获取并输出加载进度(0 - 0.9)
      Debug.Log(operation.progress);
   }
}

声音

声音音频分为音乐、音效,先看看对于音乐的播放:
要想有音频的播放,需要在想要播放音频的对象物体上添加
直接看代码:

class AudioTest : MonoBehaviour
{
   //AudioClip
   public AudioClip music;

   //Add player
   private AudioSource player;

   void Start()
   {
      player = GetComponent<AudioSource>();
      //设定播放的音频片段
      player.clip = music;
      //循环播放
      player.loop = true;
      //设置音量
      player.volume = 0.5f;
      //播放
      player.Play();
   }

   void Update()
   {
      //按空格切换声音的播放和暂停
      if (Input.GetKeyDown(KeyCode.Space))
      {
         //如果音频正在播放
         if (player.isPlaying)     //获取音频是否在播放的信息(如果播放返回值为true)
         {
            //停止播放音频
            player.Pause();
            //另一种方法(终止播放)
            player.Stop();
         }
         else if (!player.isPlaying)
         {
            //解除暂停,继续播放
            player.UnPause();
            //开始播放(对应Stop的方法,注意是从头开始播放的!!!)
            player.Play();
         }
      }
   }
}

那么对于音效又如何控制呢?
接着看代码示例:

class AudioTest2 : MonoBehaviour
{
   //Add sound effect.
   public AudioClip touchedsound;

   //Add player.
   private AudioSource player;

   void Start()
   {
      player = GetComponent<AudioSource>();
   }

   void Update()
   {
      //按下鼠标左键时播放音效
      if (Input.GetMouseDown(0))
      {
         PlaySoundEffect();
      }
   }

   void PlaySoundEffect()
   {
      player.PlayOneShot(touchedsound);
   }
}

视频

Unity中可以很方便的添加并控制声音,视频也是如此!下面就来看看如何用脚本来控制视频的播放吧~
话不多说直接上代码:

//注意要额外引用名称空间!!!
using UnityEngine.Video;
class VideoTest : MonoBehaviour
{
   //public VideoClip testvd;
   public VideoSource player;
   void Start()
   {
      player = GetComponent<VideoPlayer>();
   }

   void Update()
   {
      //如果鼠标左键被按下就播放视频
      if (Input.GetMouseButtonDown(0))
      {
         player.Play();
      }
   }
}

各种用法同音频的播放大致相同,二者互相参考即可!

射线检测

何为射线检测?

射线检测就是用户鼠标点击屏幕时,相机视图的对应位置会发射一条红外射线,而这条射线的落点就是用户鼠标点击点在游戏实际3D平面沿着这条射线的映射点(此处解释可能不太准确),这样就可以知道用户鼠标点击的是哪一个地方了。

利用脚本来实现并控制射线检测

废话不多说,我们直接来看代码~

class RayTest : MonoBehaviour
{
   public GameObject Target;

   void Start()
   {
      //创建射线
      //方式I
      Ray test_ray_1 = new Ray(Vector3.zero , Vector3.up);  //创建一条从(0,0)点向正上方发射的一条射线
      //方式II
      Ray test_ray_2 = Camera.main.ScreenPointToRay(Input.mousePosition);  //参数含义————获取鼠标在屏幕上的坐标
   }

   void Update()
   {
      //当鼠标点击时发射射线进行检测
      if (Input.GetMouseButtonDown(0))
      {
         test_ray_2 = Camera.main.ScreenPointToRay(Input.mousePosition);
         //判断点击后是否碰到了游戏物体(注意!游戏物体必须要挂载碰撞组件!!!)
         //要实现碰撞检测,先声明一个碰撞信息类
         RaycastHit hit;
         //进行碰撞检测
         bool res = Physics.Raycast(ray , out hit);
         //上行代码执行后,hit就会有内容(如果返回值为true——碰到了东西),hit的内容包含碰撞到的位置
         //结合示例,让效果更明显看看
         Target.transform.position = hit.point;  //让游戏物体移动到鼠标点击到的位置
      }
   }
}

上述的代码只可以检测射线碰撞到的第一个物体,不能检测多个物体,那么如何实现多检测呢?下面来看看吧~

//基于上述代码的补充程序
//创建一个多检测碰撞信息类(利用数组的形式)
RaycastHit[] = hitall;
//参数二、三:检测距离限制(射线最大发射距离) & 设置可供检测的图层(示例中,只检测第十个图层的物体)
bool res_all = Physics.RaycastAll(ray , 100 , 1<<10);      //每碰撞到一个物体,就把碰撞的信息进行返回

动画

游戏里炫酷的动作往往是一大亮点,而实现方式就是动画,现在就来学习,恭喜你打开了动画的大门~!
(动画的绘制可以参考flash8和blender中的操作,本人有这两款软件的基础,且这里以脚本编写为主,因此这里不再过多赘述)。
直接上代码,看看示例~(旧版):

class AnimationTest : MonoBehaviour
{
   void Start()
   {

   }

   void Update()
   {
      //当鼠标左键被按下
      if (Input.GetMouseButtonDown(0))
      {
         GetComponent<Animation>().Play("动画片段的名字");
      }
   }
}

看完了旧版的动画组件,现在来看看新版的,找一找有什么不同:
在unity界面中的操作:
给物体挂载Animator组件 — 于Assets中添加一个动画控制器 — 绑定 — 双击绑定框中的控制器 — 在动画器中进行控制
那么如何用脚本来控制动画呢?下面就来看看吧:

//前面的代码省略
private Animator animator
void Start()
{
   animator = GetComponent<Animator>();
}
void Update()
{
   if (Input.GetMouseButtonDown(0))
   {
      animator.Play("要播放的动画片段的名称");

      //实现特定条件下不同动画的过渡:需要用到触发器(动画器上的参数按钮 -- Trigger)
      animator.SetTrigger("触发器名称");
      //注意!默认情况下当按键被按下时,要等第一个动画播放完毕才可以过渡到下一个动画,如果希望马上执行过渡,就在过渡inspector面板中取消退出时间的勾选
   }
}

反向动力学 IK

IK是unity中非常强大实用的一个功能,可以让角色实现许多出乎意料的效果,例如:挥手和跑步等多个动画融合、角色看向某个方向————比如GTA中的路人会看向玩家……这些细节效果往往更能吸人眼球、大大增加游戏体验~!下面就来学习学习叭:
要使用IK动画,就要先在动画控制器面板上相应层设置中勾选IK!!!
代码示例:

class IKTest : MonoBehaviour
{
   private Animator animator;
   public GameObject target;
   void Start()
   {
      animator = GetComponent<Animator>();  //获取动画组件
   }

   void Update()
   {

   }

   //IK要写到这个方法内
   private void OnAnimatorIK(int layerIndex)      //层的索引,代表控制哪一层的IK(默认控制第0层)
   {
      //设置头部IK
      animator.SetLookAtWeight(1);  //设置IK权重(1 - 生效 ; 0 - 不生效)
      animator.SetLookAtPosition(target.position);
      //设置右手IK权重
      animator.SetIKPositionWeight(AvatarIKGoal.RightHand , 1);
      //旋转权重
      animator.SetIKRotationWeight(AvatarIKGoal.RightHand , 1);
      //设置右手IK
      animator.SetIKPosition(AvatarIKGoal.RightHand , target.position);
      animator.SetIKRotation(AvatarIKGoal.RightHand , target.rotation);
   }
}

导航系统

游戏场景中经常会出现复杂的地形,而要手动设置角色可以移动的区域或移动路线不仅难度较大且费时费力,不过不用担心,强大的Unity引擎已经帮我们想到了,那就是导航功能!下面就来看看导航能干些什么吧(注意:即将叩开新世界的大门!):

在Unity面板中创建导航

流程如下:
先创建一个空物体,给空物体挂载上NavMeshSurface组件,然后建立地形,把要参与地形组成的模型加到空物体的子级中并每个都挂载上NavMeshModifier组件,然后在空物体的NavMeshSurface组件上进行相应设置并烘焙即可
如果要连接碰撞区域(比如实现远程跳跃或者跳下来的效果),就在对应物体的NavMeshModifier组件上勾选创建连接,然后再在空物体的NavMeshSurface组件上进行相应设置并再次烘焙就行啦!
以下是测试用的代码示例,顺便也来复习一下射线检测的知识:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    private UnityEngine.AI.NavMeshAgent agent;
    // Start is called before the first frame update
    void Start()
    {
        //获取导航组件
        agent = GetComponent<UnityEngine.AI.NavMeshAgent>();
    }

    // Update is called once per frame
    void Update()
    {
        //获取鼠标在地面上的点击位置
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;
        if (Physics.Raycast(ray , out hit))
        {
            //设置点击位置
            Vector3 point = hit.point;
            //将点击位置设置为目标点
            agent.SetDestination(point);
        }
    }
}

UI

我超,恭喜你坚持到了这里!!!看到游戏中的一个个按钮图标、配上流畅炫酷的切换动画,你是不是很好奇这些是如何做出来的?这些类似的东西统称为UI,UI可以实现玩家和游戏的交互(比如三角洲的仓库管理、设置),让游戏可玩性、可变动性更高,一个好的UI系统能给游戏锦上添花,接下来就看看如何进行制作吧!
(鄙人非常期待这部分的学习,因为自己也是很喜欢做一些桌面应用小程序~~~)
如果你学过Python的tkinter库,那么搭配起来食用更佳哦!

画布以及UI基础知识

所有UI控件的绘制都在画布上进行,而要确定并控制UI控件的位置,需要用到锚点,锚点位于画布上,相当于给UI的位置确定提供了一个参照物,而UI控件的位置就是UI控件中心点和锚点的相对位置,不仅如此,对UI和锚点进行设置可以完成程序在不同大小、分辨率的屏幕上运行的适配,可以确保UI控件的位置、大小不会出现偏差并都能达到我们想要的视觉效果。

添加文本————文本框的使用

按下按钮————按钮的使用

因为按钮的相关操作在Unity的Inspector面板上都有说明而且容易懂,这里主要介绍按钮交互的相关内容:
如何实现按钮交互?流程如下:
在Inspector面板中给按钮添加事件,然后选一个对象,新建一个脚本并挂载到对象上,将对象拖到事件对应接口中,选择脚本中要触发的事件的函数,就可以了!
示例代码:

//其他代码省略
public void ButtonClick()
{
   Debug.Log("按钮被按下!!!");
}

输入内容————文本输入框的使用

想知道怎么实现登录的相关功能吗?今天它来了,文本输入框是实现类似功能的第一步哦~(个人理解)
同上,这里还是主要介绍脚本控制相关的内容:
示例代码来咯~

//注意,一些特定的功能(比如获取输入框中输入的内容)要调用API等实现的话,一定要使用特定作用域
using UnityEngine.UI;   //老版
using TMPro;            //新版
class UItest : MonoBehaviour
{
   public InputField inputfield; //老版
   public TMP_InputField newinputfield;   //新版
   void Start()
   {

   }
   void Update()
   {

   }
   public void InputChange()
   {
      Debug.Log(inputfield.text);
      //Debug.Log(newinputfield.text);
   }
}

让用户进行选择————选项与下拉框的使用

选项组件:

重点:如何实现多选一的效果
给选项控件添加Toggle Group组件,并将组件拖到控件Inspector面板中的Group控制项中(多个选项控件应该关联一个toggle group组件,要注意!!!),就可以实现控制只能选择一个选项的效果了~~~

下拉框组件:

以下是脚本控制相关,直接看代码吧~

using UnityEngine.UI;
class DropDownTest : MonoBehaviour
{
   void Start()
   {
      //获取下拉组件
      Dropdown dropdown = GetComponent<Dropdown>();
      //获取组件中的选项
      List<Dropdown.OptionData>options = dropdown.options;
      //修改选项
      options.Add(new Dorpdown.OptionData("New Option"));
      dropdown.options = options;
   }
   void Update()
   {

   }
}

如何给下拉框组件添加上图像来进行美化:
看视频:
![https://www.bilibili.com/video/BV1gQ4y1e7SS?spm_id_from=333.788.player.switch&vd_source=76c44b47d9618773be055bc93a039529&p=65]

动画进阶————Animation Rigging 的使用

Animation Rigging是Unity官方开发的一个专门用于动画控制的插件,可以为动画角色绘制出骨骼,让开发者告别在面板中寻找骨骼的苦恼,同时还可以动态进行动画的修正,让动画的精细化更为直观且简单,接下来就来看看吧:

要给角色绘制出骨骼,首先在Unity中最上方的菜单栏点击Animation Rigging然后选择渲染出骨骼,这时候就会发现角色多挂载了一个Rig Builder组件,然后就可以看到角色的骨骼被绘制出来,并且可以直接进行选中,非常的方便!!!

利用Animation Rigging来调试角色动画

角色身上持有或者带有的物品在角色运动时难免会出现位置等方面上的偏差,这时候就需要人为进行调试,使用Animaiton Rigging可以大大提升调试的效率!以下是步骤(以角色右手持枪为示例):
懒得写步骤了,具体的参考本人学习文件(AShotGame)。

为动画文件添加Curve

在角色动画播放的过程中,角色的各项数据都会发生变化,比如角色蜷缩起来的时候碰撞体应该要变小,而要把其他的信息写入角色的动画中,有一个较为方便的方法/功能就是叫Curve,通过给动画添加Curve,我们可以在动画进行中实现不同参数的写入以及修改。

举例:角色拔出枪后,我们以及给右手设置了一个目标位置(IK Target),而角色在游戏过程中要收枪的话,这个目标位置会对角色的动画产生影响,因此,我们需要在动画过程中改变右手的IK权重,来改变Ik Target对右手动画的干扰程度,因此,可以添加Curve,找到拔枪和收枪动画中合适的时间点/帧,通过Curve来设置Ik权重数值————添加一个数值变量(命名为Right Hand Weight)(开始拔枪,不受IK影响,权重设置为0;拔枪结束后,右手应该正确握住握把,处于IK Target的位置上,IK权重设置为1),Curve制作完毕后点击下方的apply进行保存,然后在动画控制面板(状态机)中新建一个和这个数值变量名相同的变量,然后在脚本中读取这个参数的值并赋值给右手的IK权重中去:

using UnityEngine.Animations.Rigging;

public Animator animator;
public TwoBoneIKConstrain RightHandConstrain;
//前面的代码略过
void SetRightHandIKWeight()
{
   RightHandConstrain.weight = animator.GetFloat("Right Hand Weight");
}

发表评论