Unity-2D-roughlike

之前写好的文档乱码了(爆炸)…这里重新写一份好了QAQ


Unity 学习笔记

Unity 3d 作为一款非常流行的游戏引擎,里面集成了游戏制作需要的大部分内容。

Unity里面有几个比较重要的概念(自认为)

  • Component
  • MonoBehavior
  • GamObject

MonoBehavior

这个是Unity的一个核心类,通过集成这个类,我们的对象能够操作Unity的相关属性,并且集成相关的类来使用。其中使用的最多的就是GameObject – 这个对象就是Unity里面最基本的操作对象,通过设置这个属性,我们就能够将Editor中的对象与当前Behavior进行关联,从而进行一系列操作。然后我们可以通过在Behavior中定义Component – 对象的属性,从而来对gameObject制定的对象属性进行操作。

其中常用的属性为:

  • gameObject: 通过将这个属性attach到Editor中的对象,从而完成操作

GameObject

Unity的核心类之一。在MonoBehaivor中定义Component,就能够操作此时已经Attach到当前gameObject的Component身上。设定的Component可以通过右上角的齿轮按钮进行属性的重置。
其中常见的Component有:

  • Transform: 用于存放制定Object的坐标,旋转角度的Component。一般来说任意对象创建都会带上这个属性。这个属性在第一次创建的时候最好先重置一次
  • Sprite Render: 用于绘制当前对象,可以通过直接将图片拖动至Editor中进行创建,如上对这个对象创建的时候大小可能会有一点不合适,可以进行调整。(如果创建了Animator的话,这个对象必定被创建)
  • Animator: 用于处理动画播放的Component。当我们将多幅图片拖动至同一个对象上的时候会创建。可以管理动画的播放时机,播放出发条件等等。当创建后会在本地生成一个Animator文件(存储动画)和AnimatorController文件(管理动画)
  • Camera: 用于处理当前玩家视觉的Component。这个对象一般创建在一个独立的Game ObjectCamera上,这个对象将会决定玩家能够看到的游戏范围。一般来说会通过各种方式让其跟随某个游戏对象,从而实现视觉跟随
  • Physics: 管理物理系列的Component。分为2D和3D两种。这个属性是一个大类,底下有很多具体的物理模型。可以模拟和质量,力等相关的属性
  • Audio Source: 音频管理的对象,可以管理音频播放的方式(是否循环播放等等)

属性

  • Tag:当前gameObject标签名
  • Layer:当前所在物理层。只有在同一层的对象能够被碰撞检测到
    当我们想要为选中的对象添加Componeng的时候,可以通过点击右下方的Add Component进行添加:

    由于Unity中其实有很多的函数和属性,我们要学会查询手册。在任何属性右上角的小本子按钮能够打开当前的查询资料页面

Transform

如同前面提到的,存放了物体关于【位置大小】的所有信息。包括平移,旋转,大小。当我们创建对象后,为了保证我们的参考系正确,最好通过右上角的gear对其进行reset

SpriteRender

  • Sorting Layer: 这个属性是用来决定绘制的顺序,越靠上,绘制越靠前,相应的,绘制的内容也越容易被遮盖。

Animator

当通过拖动多个图片到一个游戏对象上来创建这个对象的时候,一定会创建一个SpriteRender。同时,我们会在本地创建一个.anim用于动画内容的Animation文件,以及一个Animation Controller,用于管理当前的动画播放顺序,以及触发条件。当我们添加了多组不同的图片的时候,就会有不同的动画资源,其中默认的是第一组拖进去的图片动画。但是只会创建一个Animator Controller

Animator的触发可以在Parameter处进行设置。通过一侧的小箭头能够添加我们指定的parameter:

Parameter有几种:

  • Trigger:触发器类型,也就是当这个值被设置为True后,动画才会播放,否则就不会播放。同时,触发器就意味着,他的触发是一帧一帧的。当我们调用函数SetTrigger设置其为True后,触发的动画播放后,就会回到False的状态(相当于是一次播放一组)

为了在不同的动画直接实现沟通,我们可以在Editor的Animator中对对象右键,找到Make Transition,就能够指定动画之间的播放关系。设置的Trigger动画最好形成一个【往返的Transition】,从而能够实现触发动画并且恢复的效果

  • Transition Duration:这个是动画播放时间的延迟,一般2D游戏不需要设置,3D主要是为了动画额度渲染才需要
  • Has Exit Time:设置之后,当动画完整播放后才结束。如果不设置的话,那么当发生动画切换的时候直接打断当前的播放进度。
  • Exit Time:退出前播放当前的动画多少次

Animator的Animator Controller管理了动画的播放,这个东西可以实现多个动画播放控制,同时也会存储当前动画播放的特质。利用这点,我们可以处理一些重复播放的动画

  • animator override controller: 重载当前的播放动画。我们可以保留某一个动画原型作为希望的效果的模板,然后我们拖入新的图片并且删除当前的animator,然后我们在Create处新建animator override controller,并且在Controller中拖入我们原先的Animator Controller。并且将里面我们需要重载的播放函数(其实就是Animation文件名)改成我们自己的播发函数名

    即可完成播放的重载。
    Animator还有以下其他的属性
  • Speed:当前动画的播放时间

Camera

摄像机属性,可以决定当前的拍摄位置。通常来说,Camera的x,y,z设置和物体的位置还是有点不一样的,其中的z一般是-10,从而能够【往下看】。在2d游戏中,可以在projection中设置平行摄像机,此时的Camera需要设置Size(orthographicsSize属性)

  • Size:当我们使用了orthographic属性后,我们通过设置这个Size属性(单位为换算过的)

Physics

Physics是一个我们的物理模型,算是模拟中的一个重要的概念。gameObject可以添加的相关Component的内容为xxColliderRigidbodyxx。Unity的物理模型有两套,一个Physics和Physics2D,这两个物理模型是共存的,但是双方不存在交互。

  • Rigidbody:刚体,当一个物体需要【重力】相关的属性的时候,会加上此属性。这个属性创建的时候会带有gravity scale属性(当然,如果选择了Kinematic就不需要)。如果是2d游戏的话需要设置一下。只有设置了Rigidbody的对象才能够接受力的作用,也就是说,碰撞检测中,拥有此Component的对象才有接受碰撞的函数。
  • Collider:碰撞体,设置了这个Component才会进行碰撞测试检测(注意,设置了Rigidbody2d的也要这么做)。碰撞体会纳入碰撞检测中,但是不会收到力的作用(可以放出力)同时碰撞还能够设置是否为trigger,trigger不会产生力,但是会被检测出来。
  • 碰撞是否被检测取决于对象是否在【同一层上**(见gameObject属性部分)**】

Rigidbody详解

无论3d还是2d对象都可以设置,并且两者并不发生交互。
这个Component设置的是一个刚体的概念,也就是说此时该对象会考虑的作用,并且会与Collider发生碰撞检测。这个对象有着现实中的一些基本类型,比如mass(质量),force(力)和accelerate(加速度)

  • Force:作用在该Rigidbidy上的力,如果我们需要让一个物体产生一个推动的过程的话,不应该自己去计算加速度一类的,而是应该使用addForce一类的函数将力作用在对象上。
    为了应付不同的Rigidbody对象,这个对象有不同类型:
  • Static
  • Dynamic
  • Kinematic
    Static为固定的对象使用的属性。由于可以被预先的计算出来,那么不动的物体使用这个属性将会非常的方便。相反,如果Static的一个物体发生位移,那么Unity的每一帧都会去重新绘制Collider,从而增加运算量。只含有Collider的对象会被视为static
    Dynamic为一个动态的对象使用的属性。如果同时含有Rigidbody2d和Collider属性会被认为是Dynamic。
    Kinematic:无论是Static或者Dynamic,这个对象都会被认为是刚体,会受到力的作用。然而有些对象并不需要这些,只需要检测是否发生了碰撞(比如简单的打砖块游戏),此时就可以设置成Kinematic,此对象不存在Mass等力相关的概念,但是可以检测是否发生碰撞。

Pyhsical2D的常见函数

  • Linecast(Vector2 begin, Vector2 ned, LayerMask, kayer, int MaximumDepth, int MinmumDepth)
    从begin到end会射出一条虚拟的射线,如果这条射线其中存在Collider对象,那么会将其封装成RaycastHit2D对象并且返回。如果存在多个Collider对象,就返回第一个遇到的那个。

Scrpit

脚本对象,我们的cs脚本就是通过这个Component对其进行操控。主要的代码就是写在这个位置。由于Script也是Component,于是在管理类的时候,我们可以直接将脚本对象传进去。
一般来说,开发的时候喜欢使用一个GameManager对象(Singleton对象,详见后开发模式)来存储各个类之间的关系,此时我们就能够吧Script作为Component传入。
在Script中获得Component的方法

1
2
3
4
5
6
7

private Rigidbody2D rb2d;
......
void Start(){
rb2d = GetComponent<Rigidbody2D> ();
...
}

如果属性被设置成了public,那么在Editor中将变得可见并且可编辑(如果在最前面跟了[HideInInspector]装饰的话就不会出现)

详细解说见后。

UI

UI是一种处理画面交互内容的Component。与Physics一样,地下也有很多的子类型。每次创建UI对象,就会创建一个Canvas对象在Hierarchy,并且我们的子类型也是在其下,只有Canvas下的UI对象发生更变才能够被绘制出来。UI的坐标与普通的Transform不太一样,是一个类似Pivot(锚点)的选取方式,并且坐标Pos是相对于当前的Canvas而言的。

  • Text: 是一个文本UI,也是一个Component,可以通过修改其中的Text来修改显示的内容,可以用脚本动态的修改text。当Text的长度会变化的时候有可能会超出文本框给定的大小,此时我们需要设置Overflow属性来允许移除。
    UI的子元素在Canvas的Hierarchy中摆放的顺序也象征了其渲染顺序
    在创建了UI的同时,也会创建一个EventSystem对象(用于处理输入,输出和各类事件)。

AudioSource

用于管理音频播放的Component。通常来说,也会使用一个单例模式(单利模式意思见后)来操作,因为所有的音频(视为AudioClip)都要将其放置于对应的AudioSource上才能播放。

相关属性:

  • clip:当前播放的clip。
  • pitch:当前播放音乐音调的高低。

Script详解

由于每个MonoBehavior中的对象多有很多的方法,这里记录一下各个函数的作用:

  • void Awake():当这个脚本被实例化之后(无需enable)就会调用的方法,调用顺序在Start之前。并且只会在整个对象创建的生命周期中被调用一次。这个函数调用的时候,所有的GameObject已经被初始化完成了
  • void Enable():当函数enble后会被调用
  • void Start():开始函数,在Scprit对象可以使用后(enable)的第一帧被调用。同样也是生命周期中只会被调用一次
  • void Update():在每一帧都会被调用的函数,经常用于画面的更新
  • void FixedUpdate():特殊用法,此更新位于每一帧固定的frame中。因为Update是根据frame的渲染速度决定更新的,但是例如力的计算不能根据frame的渲染速度而变化的,送一Unity中提供了一个FixedStep,更新时间是一定的,从而让每次更新能够定时计算。
  • void LateUpdate():特殊用法,会在所有的Update都调用完成后才调用。比如说Camera的跟随,肯定要在所有的对象都渲染完成后才进行跟随,从而才能看到完整的画面。
  • void OnDisable():当前对象为disable或者inactive后会被调用。一般用于在对象退出后清理内存。
  • OnTriggerEnter2D(Collider obj):当一个Trigger对象进入了当前的Conllider的范围后会触发,此时的obj具有基本的信息。

Scrpit中可以将当前Attach的对象以全局变量的方式获取。

C#的一些基本概念

  • 引入规则:C#的包通过using 引入外部的包。当我们想要以自己命名的方式引入包的时候,可以如下写(using Random = UnityEngine.Random)
  • as 关键字,当时用此关键字的时候,相当于显示的进行类型转换
  • where 关键字: 泛型类型约束条件,也就是说,如果我们当前的泛型类型(Template)有所指定的话,只有符合条件额度会被传入:
  • params关键字:使用此关键字后,C#的函数可以传入参数数量的参数,如:
1
2
3
void func(params int[]p);
...
func(1, 2, 3);//此时可以传入多个参数,并且会被当成数组处理。
1
public class MyGenericClass<T> where T:IComparable { }  

此时的只有IComparable类能够传入

Prefebs

这个是一个模型,当我们在某些场景中频繁的需要加入一些重复的物体,或者同一个对象想在不同的scene中创建的时候,我们就可以使用Prefebs。当我们创建了一个对象后,直接将其重Hierarchy栏中拖下来就能够形成一个Prefebs。

每个Prefebs在使用的时候,可以直接通过在函数中实例化其将其放入当前的Scene。
可以在Prefebs上实现Animator override controller,这样会大大减小开发的时间

生命周期的研究

初始化

一个对象以Prefebs的状态存储,然后再脚本中调用的时候,其实此时对象并没有被初始化,只有当我们主动调用了instantiate函数的时候,才会将对象实例化:

1
GameOjbect instantiate(obj, transform, rotation)

此时会从文件中读取这个对象,并且将其实例化。当对象实例化后,就会存放在内存中。

销毁对象

如果是普通的对象,一般建议使用Script对其进行操作,让其在合适的时候调用Destroy。而如果是Scene的话,可以通过调用SceneMangement.UnloadScene将其销毁。当一个Scene对象被销毁的时候,其中的对象也将被销毁

注册委托机制 SceneManage

SceneManage是运行时管理Scene的一个静态类,而我们的游戏就是绘制在一个Scene里面的。所以当我们需要管理关卡的时候,我们就会用到这个类。

  • void LoadScene(index):载入一个Scene,如果是默认的Scene,则为0(就是我们没有特定创建Scene的场合)
  • sceneLoaded对象:重载了符号,当我们希望其在Scene被载入的时候调用函数的话,我们可以直接使用加号将其注册
1
2
3
void OnEnable(){
SceneManager.sceneLoaded += OnLevelFinishedLoading;
}

(在脚本对象载入的时候注册,从而能够及时的读取scene)
当我们将该对象销毁的时候,我们要注意及时的取消注册

1
2
3
void OnDisable(){
SceneManager.sceneLoaded -= OnLevelFinishedLoading;
}

开发模式

Singleton模式

当我们有一些对象(比如管理全局的GameManager)需要实例化的时候,我们显然只希望存在一个独立的GameManager对象,而不是说存在多个会被操作的,而且参数不共享的对象,这样的对象就成为单例对象。为了创建这类对象,我们可以如下创建:

1
2
3
4
5
6
7
8
9
10
11
public static ThisClass instance = null;

void Awake(){
if(instance == null)// 如果不存在,创建一个新的实例对象
instance = this;
else if(instance !=this)// 如果当前的实例对象不对,那么将其销毁
Destroy(gameObject)

// 当新的Scene被读取的时候,原先的Scene以及其上所有对象被销毁,而这个对象我们需要保留,所以要阻止其被破坏
DontDestroyOnnLoad(gameObject)
}

之后,当我们需要使用ThisClass的时候,我们可以通过ThisClass.instance的方式调用此对象。当然,该实例显然不能作为入口,入口函数中应当实例化当前对象:

1
2
if (ThisClass.instance == null)
Instantiate(thisClass)

Coroutine,协程

协程类似进程,但是其并不是位于操作系统的概念,而是编译器优化处理的结果。当我们使用Coroutine的时候,编译器回去维护一个状态机,从而让我们设置为协程的函数能够通过使用yield 关键字暂停。同时,yield return 将会返回一个Corotine对象,从而可以让我们用StartCoroutine将其启动。
使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
private IEnumerator Fun(){//函数返回值定义为IEnumerator,迭代对象

while(true) {
yield return new WaitForSeconds(0.1f)
Console.WriteLine("teset")
}
}
...
private IEnumerator t;
private void test(){
t = Func()
StartCoroutine(t);//之后,每两秒执行一次
}

任务委托

Invoke(“FunctionName”, time);
这里相当于将FunctionName函数委托至time秒后执行。