Unity 2D基础

环境搭建

https://docs.unity.cn

第一次安装:失败

Unity的官网是https://unity.com

打开之后,找到下载界面,可以在网址后加/cn将界面切换为中文更好找一些

下载并安装,运行

这里写着什么创建账户,我们点开

我看到了email,保险起见我们使用Gmail

果然需要邮件验证什么的,通过之后会变成这样

应该是创建成功了,我们拿刚刚创建的账户登录一下

进去之后弹出了一个网页,应该是我们的用户信息界面吧,我也没打开,这时客户端让我们下载Unity编辑器:

默认选择了个人版,免费的,免费!

稍稍等待安装好,安装好之后创建项目

点击Projects,创建一个项目,等待创建完成,点开

点的不是很开……网上也找不到错误的原因……

创建项目的时候,选一下template为3D core,再试一下,还是不行……

离谱的是,工程目录里面根本没有我的项目文件夹

找不到相关问题的解决方法啊

只好使用重启大法,然后尝试直接点开Unity,出现了这个:

先不管,重新打开Unity Hub,上来界面和之前一样,感觉还是没用,试着再创建一个,果然一模一样的错

无解了啊,又去找了B站上的安装视频,发现别人装的都是中文版,界面完全不一样,唉,有个人说项目打不开可能是许可证的问题,我重新获取了许可证,并且重启了,还是不行……

操,装个软件有这么难吗,每次都这样……

卸载卸载,从头再来

第二次安装

还是装好了Unityhub,这次的安装包和上次的不同,少了个beta

终于和大家安装的界面一致了啊,虽说上次那个不一定就错,但教程上找不到啊

安装一个编辑器

找了个网课看,但网课上的版本我不知道是几几年的了,所以我直接下载了Unity推荐的版本,希望不会有坑吧

安装到一半,突然VS跳出来了,想了想要用C#,它跳出来也算正常

安装好了,创建项目又默认勾选了一个PlasticSCM客户端,离谱,只好装了

创建之后,似乎成功了,出现了一个进度条

进度条加载完成,终于进入我心心念念的界面了!

但为啥我的C盘一下子满了啊!可能是不小心把什么东西的缓存路径放进C盘了吧,没办法,把重达17G的虚拟机搬出来,暂时缓解一下吧

初体验

https://docs.unity.cn

场景

场景就是游戏的环境。

新项目会自带一个场景,名叫SampleScene

如果想要创建新场景,可以在Assets文件夹里面添加

鼠标放入场景,滚轮可以完成放大缩小的功能

鼠标右键可以拖动场景

场景中有很多网格,每个网格就是1unit,对应100像素

场景中y轴向上为正,x轴向右为正

场景中的白框称为主摄像机Main Camera,代表摄像机的可见范围

Assets

assets意义为“资产”,“资源”,这里存放着项目所有的资源文件。

常见的资源有音频,图片,C#代码等

每一条素材文件都对应一个描述文件,后缀为.meta

常用的素材文件夹命名:Texture图片AudioClip音频C# Script代码脚本

添加素材可以直接拖进assets

游戏对象

Game Object

基本操作

如何添加图片作为游戏对象:直接拖入场景即可

删除:选中后delete

撤销:ctrl+z

隐藏:去掉inspector窗口种名字左边的√

可以使用移动按钮使对象进行移动,也可以在右边直接修改坐标,坐标单位是1unit

拖动红轴仅进行水平移动,拖动绿轴仅进行垂直移动,修改position可以精确修改其坐标

旋转按钮:

红绿蓝三个环分别代表沿着xyz三个轴进行旋转,可以修改Rotation来精确控制

缩放按钮:

红轴缩放水平方向,绿轴缩放竖直方向,拖动中间的方框可以等比例缩放。可以通过Scale属性控制

矩形工具:

三合一的工具,可以拖动,缩放,按shift可以等比缩放,选中一个角后朝着斜对角的方向移动鼠标可以旋转

修改对象的显示顺序,调节其Order in Layer属性,越大的越显示在上面,也可以修改z坐标

轴心(pivot)

对象定位的位置,旋转的时候,也是绕过轴心的坐标轴来旋转的

点击下方assets里面的对象,右边的inspector属性栏目种会有一个sprite editor按钮,点开它可以进入对象编辑器,在里面可以修改轴心的位置

父子关系

在左侧hierarchy(层级)栏中,拖动一个对象到另一个对象的下方,可以使其构成父子关系。

图中的22,小电视和边框构成了以边框为父对象的父子关系,当移动边框时,整个系统整体移动

子对象以父对象的轴心为原点,其坐标和旋转角度均为相对的,意味着使用transform组件修改子对象的位置,旋转角度时,均以父对象的轴心为参考

父对象的移动,选择,缩放会同步到子对象上

更改父对象的坐标,子对象的坐标不变,旋转父对象,子对象的旋转参数不变,但缩放父对象时,子对象的缩放参数也会发生相应的变化

坐标

x,y轴坐标以左下角为原点,x向右为正,y向左为正

2D项目也有z坐标,以垂直屏幕向里为正

单位1unit

点上面的2d可以切换3维

摄像机

摄像机默认放置在屏幕中心,z轴-10的地方进行拍摄

摄像机的属性可以设置

background是背景色的意思,表示游戏背景的填充色

size广角:摄像机拍摄的范围(高度半径)

长宽比可以在game放映模式里面修改

换句话说,摄像机的高度由size直接设定,但宽度则由size和长宽比共同确定

图片素材

切割操作

当一张图片上有多个素材时,除了使用ps之外,还可以直接拖入assets,然后把sprite mode设置为multiple,再打开sprite editor,左上角的split自动切割后保存apply

然后发现图片点开之后会出现其切割好的素材

切割好的素材会自动命名为原图片名_0,_1……

只包含一个sprite的图片素材模式为single

图片渲染器

Sprite Renderer组件

可以把图片拖动到sprite renderer里的sprite里面,选择具体要渲染的图片

https://docs.unity.cn

组件

component,表示功能

每个对象Game Object可以挂载多个组件

可以先创建一个空的游戏对象,然后add component,添加sprite renderer(图片渲染),(脚本)等

Transform组件

基本组件,每个游戏对象都必须包含transform,包含Position,Rotation,Scale三个基本属性

脚本

创建脚本

在Assets里面右键,create一个C#脚本

双击打开可以使用代码编辑器编辑它(又回到了熟悉的vs)

编辑脚本

可以看到新建里的脚本自带了两个方法——start和update

Unity代码在vs中的自动补全设置

在unity中edit->preference->extern tools,选中vs即可

start方法

创建对象时首先会调用一次的方法

update

每个游戏刻都会调用一次

挂载到游戏对象

在游戏对象的Inspector里面点击添加组件add component,把我们写好的脚本加进去

也可以直接拖动挂载

Debug.Log()

输出调试信息,在console可以看到

同一个脚本可以挂载在不同的游戏对象下,实际上脚本只是一个类,挂载时该类实例化出一个对象,挂载的只是实例化出来的对象而已

帧率

Unity的帧率是不固定的,所以每一帧直接间隔可能不一样

获取上一帧间隔的方法:Time.deltaTime

尽量固定帧率:Application.targetFrameRate = 50;

上面的单位都是毫秒ms

简单的移动操作

this.Transfrom.translate(dx, dy, dz)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Hello : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
Debug.Log("这是我的第一个游戏,fuck you!");
Application.targetFrameRate = 50;
}

// Update is called once per frame
void Update()
{
this.transform.Translate(0, 0.05f, 0);
}
}

优化移动速度:

1
2
float step = 0.8f * Time.deltaTime;
this.transform.Translate(0, step, 0);

注意:Unity的浮点数要用float不能用double,所以习惯性地在数字后面加f

获取游戏对象和组件

this获取当前游戏组件

this.gameObject当前游戏对象

this.GetComponent<组件类型>();

获取组件

1
SpriteRender render = this.GetComponent<SpriteRender>();

也可以写成:

1
SpriteRender render = this.gameObject.GetComponent<>();

或者

1
SpriteRender render = GetComponent<>();

翻转对象;render.flipY = true

在当前对象里面获取其它对象

1
GameObject obj = GameObject.Find("路径");

MonoBehavior

所有脚本类都会继承于MonoBehavior

可以查看官方文档来查看具体属性

enabled:启用或禁用

获取父子节点

在代码中,通过transform组件来获取父子节点

样例代码:遍历一个父节点的所有子节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
using UnityEngine;

public class Example : MonoBehaviour
{
// Moves all transform children 10 units upwards!
void Start()
{
foreach (Transform child in transform)
{
child.position += Vector3.up * 10.0f;
}
}
}

name属性,组件的名字(路径中的文件名)

更改父子关系

1
2
3
4
GameObject obj1 = GameObject.Find("小飞机");
GameObject obj2 = GameObject.Find("物体");

obj1.transform.SetParent(obj1.transform);

如果想设计父级为根节点,直接设为null

游戏运行起来时,窗口上的数据会发生变化,显示的是实时数据,如果结束了,那么数据又会回到编辑时的状态

添加脚本属性

在脚本类中设置public类型的属性,就可以在inspector里面显示并修改其初始值

驼峰命名法的属性名会被自动拆分并加大写

也可以使用引用类型的属性,比如图片,游戏对象,也可以在inspector里面修改

1
2
public Sprite sprite1;
public Sprite sprite2;

然后就可以在inspector里面看到两个sprite了,也可以把图片拖进去了

如果想要切换图片,可以取出SpriteRender然后更改它的值为sprite1和sprite2

调试脚本

1、设置断点

2、附加到unity(调试,添加到unity调试程序)

3、unity中运行

4、开始调试

5、调试结束,vs中停止调试

6、unity停止运行

物体运动

Vector3

向量类型,也称为坐标类型

1
2
\\创建一个向量
Vector3 vet = new Vector3(1, 1.0f, 0.5f);

物体的旋转

指定位置:

1
this.transform.position = vet;

欧拉角旋转(相当于直接赋值)

1
2
this.transform.eulerAngles = new Vector3(0, 0, 45f);
//向左旋转45度

Rotate方法:给定一个欧拉角,进行旋转。eulerAngles.z度围绕z轴,eulerAngles.x度围绕x轴,eulerAngles.y度围绕y轴

逆时针为正(右手握拳拇指指向轴,其余四指指向)

Rotate和欧拉角的区别是欧拉角直接赋值和初始无关,但Rotate是以当前状态开始旋转

世界坐标和本地坐标

position在世界里的绝对坐标

localPosition相对于父节点的坐标

很多属性前加local都可以切换为对应的本地坐标,如:localEulerAngles

移动物体

相对运动:Translate(dx, dy, dz)

默认物体沿着自己的坐标系运动Translate(dx, dy, dz, Spece.Self)

沿着世界坐标系:Space.World

向量运算

向量Vector2,Vector3,Vector4,求长度v.magnitude

标准化:v.normalized

静态常量:

Vector3.right = Vector3(1, 0, 0)

Vector3.up = Vector3(0, 1, 0)

Vector3.forward = Vector3(0, 0, 1)

加减法:直接使用±号

数乘:使用*

点乘:Vector3.Dot(Vector3 v)

叉乘:Vector3.Cross(Vector3 v)

使用向量结合计算几何知识可以获取有用的信息

如:求距离

1
2
3
4
GameObject obj1 = GameObject.Find("第一个物体");
GameObject obj2 = GameObject.Find("第二个物体");
Vector3 v = obj1.transform.position - obj2.transform.position;
Debug.Log(v.magnitude);

求夹角

1
2
3
4
5
float angle = Vector3.SignedAngle(a, b, Vector3.forward);
//以z轴的方向为旋转轴

float angle = Vector3.Angel(a, b);
//不带符号

物体的指向

transform.up 物体y轴的指向

transform.forward 物体z轴的指向

transform.right 物体x轴的指向

屏幕

屏幕坐标

屏幕左下角是原点,屏幕坐标的单位是像素

屏幕长度和宽度,大小和实际大小有关(缩放一下就变了):

Screen.width

Screen.height

1
2
Vector3 v = Camera.main.WorldToScreenPoint(worldPos);
//把当前坐标转为屏幕坐标

屏幕边界

指定了高度无法指定宽度,所以无法使用世界坐标来判断

我们可以使用屏幕坐标来判断左右

事件

鼠标处理

常用的方式

检测鼠标事件/状态

在update里面检测是否有鼠标事件发生

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if(Input.GetMouseButtonDown(0)){
Debug.Log(Input.mousePosition);
//鼠标的位置,屏幕位置
}
//检测鼠标按下事件:0左键,1右键,2滚轮
if(Input.GetMouseButtonUp(0)){
Debug.Log(Input.mousePosition);
//鼠标的位置,屏幕位置
}
//检测鼠标抬起事件:0左键,1右键,2滚轮
if(Input.GetMouseButton(0)){
Debug.Log(Input.mousePosition);
//鼠标的位置,屏幕位置
}
//检测鼠标状态是否按下:0左键,1右键,2滚轮

注意事件和状态的区别:事件只会触发一次,状态是持续的

处理

Camera.main.ScreenToWorldPoint(Input.mousePosition);

注意2D游戏下的z值是相机的位置,需要手工设置一下

鼠标事件可以被很多游戏对象处理,鼠标事件发生时,大家都是知道的

键盘

类似鼠标:

Input.GetKeyDown(key);

Input.GetKeyUp(key);

Input.GetKey(key);

键盘编号KeyCode.按键名称

常用的键盘名称:

1
2
LeftArrow	左箭头
RightArrow 右箭头

其它事件

自动调用的方法:

Awake(),Start(),Update(),Debug()

事件发生时的回调方法:前面带On的一般都是

组件声命周期可以看官方文档

常见事件顺序Awake->Enable->Start

当组件被禁用时,不执行Start和OnEnable,只执行Awake

每次启用时执行OnEnalbe,Start在启用后执行一次,再次启用不会再次使用Start

脚本的执行顺序

有多个脚本时,先遍历执行Awake,再遍历执行Enable,再执行Start,每一帧Update也是遍历

默认情况下,脚本的执行优先级是一样的,都是0,所有脚本无序执行。显示指定脚本的执行顺序:在inspector里面调节脚本的Execution Order属性,越小的越先执行

UI事件处理

Button组件

直接添加OnClick事件处理

方法:

查看inspector,点击OnClick中的+,添加一个None的事件

把游戏对象拖动过来,选择其脚本中的方法

方法的要求:public void 方法()

不能带参数

简单的登录

在按钮处理脚本对象里面public一个TextField类型的变量,然后把输入框拖动进来

就可以在按钮的处理脚本里面直接使用TextField.text访问了

图片或文本的事件处理

在脚本中实现相应的接口就可以进行事件处理

实现接口的方法:在类名后面加:接口名,接口名

预制体

预制体prefab,预先制作好的模板,可以快速创建相同的对象

先在场景里做好一个普通的游戏对象,然后直接把它拖到project里面,就自动生成了一个预制体

Prefab instance:根据预制体创作出来的对象

双击预制体,就可以进入预制体编辑

对预制体进行修改的话,所有根据预制体创建出来的对象都会自动应用修改

在场景中编辑预制体实例

在场景中修改预制体实例,选择Override里面的Apply

如果想和预制体切断联系,可以右键Unpack

动态创建实例

例子:飞机发射子弹

先准备好子弹预制体

在飞机中定义一个public GameObject型变量

然后把预制体作为引用属性挂载到飞机上

创建的代码

1
2
3
4
5
6
GameObject bullet = Instantiate(myPrefab);
//创建一个游戏实例,position和rotation和当前游戏对象相当,挂载在根节点下
GameObject bullet = Instantiate(myPrefab, transform);
//创建一个游戏实例,position和rotation和当前游戏对象相当,挂载在transform下
GameObject bullet = Instantiate(myPrefab, position, rotation, transform);
//创建一个游戏实例,指定其position,rotation,父节点

实例的销毁

1
2
Destory(this.GameObject);
//销毁游戏对象,不是this,this是组件

定时器

使用帧间隔来计时

1
2
float interval = 0.4f;
float time = 0;

在Update中:

1
2
3
4
5
time += Time.deltaTime;
if(time > interval){
//某个事件触发
time = 0;
}

物理系统

RigidBody刚体

为物体添加组件:Physics2D->RigidBody2D

在该组件里面设置属性

BodyType

Dynamic普通刚体,有质量有速度

Static不动的刚体,质量无穷大,没有速度

Kinematic运动学刚体,无质量

Collider

碰撞组件

Box Collider 方形

Circle Collider 圆形

Edge Collider 不规则型

Capsule Collider 胶囊型

点Edit Collider可以编辑碰撞的范围和形状

反弹

建立材质,在Assets里面右键,弹出Create->Physics Material 2D

设置其摩擦系数和弹性系数

拖动添加到组件的Material属性里

碰撞检测

两种碰撞检测:Dynamic碰撞检测和Kinematic运动学刚体碰撞检测

Kinematic碰撞检测

添加碰撞组件Box Collider2d,勾选Is Trigger (碰撞触发器)

意思是:两个刚体相撞时会触发一个事件回调

添加脚本组件,重写事件函数OnTriggerEnter2D()

1
2
3
4
void OnTriggerEnter2D(Collider2D collision)
{
Debug.Log("飞机:探测到了碰撞 ... ");
}

OnTriggerStay2D()

两个相遇时不停发生

OnTriggerExit2D()

分开

一般来说,我们只需要写Enter函数,因为一般来说组件碰撞后会消失或反弹,立刻结束碰撞,不会发生后面两个事件

碰撞事件的参数:collision

.gameObject对方节点

.transfrom对方的transform

.name对方的名字

碰撞目标身份识别

使用tag字段来表明对方的身份

碰撞时,查看对方身份collision.gameObject.tag或collision.tag

在inspector里面可以add tag

标签管理:

Edit->Project Settings->Tags and Layers

碰撞的规避

使用Layer规避一些不必要的碰撞

Edit->Project Settings->Tags and Layers

添加几个自定义的Layer

Edit->Project Settings->Physics 2D->Layer Collision Matrix碰撞矩阵:规定谁和谁可以碰撞,谁和谁的碰撞会被忽略

声音

音频资源:https://www.aigei.com/music/

2D音效:声音始终一样大

3D:近大远小

拖动音乐文件到assets里面

播放声音

AudioListener:声音接收者

AudioSource:声音源

都是组件,默认在主摄像机中有一个AudioListener

添加音源

为游戏对象添加一个AudioSource组件,然后拖动音频到AudioClip,勾选自动播放Play On Awake

使用api的方式播放音频

AudioSource的参数:

Play On Awake:唤醒后播放

loop:循环

mute:静音

volume:音量0.0~1.0

clip:音频资源

常用的方法和参数:

Play()从头播放

Stop()停止播放

Pause()暂停

PlayOneShot(clip)新开一个播放,完整播放,叠加效果

isPlaying:是否正在播放

例子:

1
2
AudioSource audio = this.gameObject.GetComponent<AudioSource>();
audio.Play();

其它常用API

Invoke

延迟调用

Invoke(“方法名”, 时间(以秒为单位));

先执行update再执行invoke,没有并发,也可以自己计时

连续多次调用invoke不会相互覆盖

取消延迟调用

CancelInvoke

延迟调用并重复

InvokeRepeating(方法名, 延迟时间, 重复时间)

是否有正在等待的延迟调用

isInvoking(name)

消息调用

调用其它游戏对象的方法:

1
2
3
GameObject main = GameObject.Find("游戏主控");
Mygame mygame = main.getcomponent<Mygame>();
mygame.addscore(1);

消息调用

1
2
GameObject main = GameObject.Find("游戏主控");
main.sendMessage("addscore", 1);

搜索main下的组件中的addscore方法

是同步调用,不是异步调用

UI

User Interface,UI不属于游戏空间

添加Canvas

右键Hierarchy,添加UI->Canvas

添加Canvas之后,会再自动添加一个EventSystem

对Canvas进行一番设置

选中Canvas,在右边inspector的canvas属性下

Render Mode改成Screen Space Camera

然后把主摄像机拖动到Camera

Plane Distance改为5个单位(UI平面和摄像机之间的距离)

在Canvas里面添加控件

右键Hierarchy里面的canvas,添加控件

添加Text(文本控件)

右键canvas,添加UI->Text

属性:

Font Size:字体大小

Alignment:对齐

Best Fit:根据Rect自动调节字体大小

Color:颜色

Font:字体,支持TTF和OTF,可以从资源网站上下载,也是直接拖动进来.ttf,.otf

添加Image(图片)

右键canvas,添加UI->Image

把图片拖动到Soucre Image

Image Type:

Simple:拉伸

Sliced:九宫格

Tiled:平铺

Filled:充满

九宫格的设置方法:

选中图片:Splid editor,切出九宫格

添加Button(按钮)

右键canvas,添加UI->Button

添加按钮后,Button组件自带了一个子节点Text

右边Transition表示按钮在不同状态下的变化,默认是颜色变化Color Tint

可以设置按钮的填充颜色,包含常规颜色,高亮(鼠标放置)颜色,按下颜色等等

想要交换图片的话选中Sprite Swap

添加InputField(文本框)

右键canvas,添加UI->InputField

Image组件,调整颜色和背景图片

Placeholder:文本框为空时,默认的填充文本

Line Type:选择单行或多行模式

布局

UI元素都有的组件是Rect Transform,是transform的子类

posx,posy都是该组件中心点相对canvas中心点的坐标(像素)

width和height都是像素

anchor

点击,自由选择元素的定位

选择之后,位置参数会根据具体选择的位置为基准

panel

使用面板来把UI组件分成一个个区域。

面板上的子节点都会以面板位参考,跟着面板移动