02显示一张图片

 

本教程由水螅制作,教程内所有提供下载的练习素材皆为水螅制作,只可用于练习,不可用于其他用途。

​​​在avg里最常见的需求之一显示一张背景。当然在我们现在的项目里,如果你点击unity上方的三角play按钮,game页面会显示当游戏启动后玩家能够看到的样子。现在的话,就是直接静态的显示着bg1的图像。

但是这不是我们想要的状态。我们希望的是可以控制显示多张背景中的某一张,而且不要这么简单粗暴的出现。​

再次点击一下三角play按钮将play状态结束。一定不要在play状态进行游戏开发,不然你所做的操作都被视为play状态的临时修改而不会被记录到游戏项目中。

那么首先,我们来做bg1的淡入效果。​

在unity中淡入可以有非常多的手段来进行,如果想要很多种花式淡入的方法,一般来说需要自己额外写一些shader,但是教程是面向懒得做复杂操作也不想搞代码的小伙伴的,那么我就先来介绍一种最简单的不用写任何代码的方法……我们可以给这张背景图录一段淡入动画。​

在开始干这些事情之前,我们先选中hierarchy中的main camera,在inspector里显示的他的各种属性中,将background的颜色设为黑色,这样当屏幕上什么都没有的时候我们会看到黑色而不是之前的蓝色。​

选中hierarchy中的bg1,我们会发现,它的inspector的内容和你选中project里边的bg1时候inspector里边显示的内容非常不同。​

因为当你把bg1的图片拖曳到scene中时,它生成的bg1其实是一个sprite renderer里的sprite指向bg1图片的gameobject。所有会显示在游戏画面中的东西都是gameobject,如果你对面向对象编程没有什么了解,那么请把它想象成一个空的塑料袋,这个袋子里可以放各种东西,而袋子呈现的形状和功能由你放在袋子里的东西们决定。

在inspector里边从上向下看,首先是一个叫做transform的栏,每个gameobject都必然持有transform,在transform栏里你可以设定这个gameobject的位置,旋转度,三方向缩放值。当然在2D游戏里z方向的缩放值其实也看不出来就是了。transform也记录一些其他重要的信息,但是那些需要在代码中设定,在inspector的界面上看不出来,我们也暂时不需要去了解。

再往下是sprite renderer,由于bg1这个袋子里装了这个​,所以屏幕上的bg1能够以bg1.jpg的样子呈现在我们眼前。如果你在sprite renderer上右键remove component将这个东西从bg1这个袋子里删掉,就会看到bg1变成了一个完全透明的点。

你可以任意在场景里新建空的gameobject,然后为它add component选择sprite renderer,然后将sprite renderer里边的sprite选择为bg1这张图,他们都会呈现出bg1.jpg的画面。

我们选中hierarchy里边bg1外的另一个gameobject——main camera,会发现它也只是一个普通的gameobject里边装了camera和audio listener这两个东西。如果你把这两个东西删掉,它也会变成一个透明的点,并且游戏屏幕上将看不到任何东西(因为没有了摄像机)。

所以bg1也好,main camera也好,他们都是相同的东西,只是因为装上了不同的功能组件而变得功能不同。这就意味着,在unity里我们需要了解的其实主要是各个不同的功能组件应该怎么设置。

现在我们选中hierarchy里边的bg1,我们想要它是从完全透明的状态淡入到屏幕上,那么在inspector里边bg1的sprite renderer里的color中,将bg1的透明度拉到0,这样它的初始状态就是完全透明的,这个操作也可以等到开始录制视频的时候再做,但是我们提前放在这里做是为了同时演示动画轴的一个默认操作。

然后点开animation这个页面,点击页面中的create为bg1创建一个属于它的动画,名字就叫做bg1a1好了。​

unity的动画制作页面和常见视频编辑卷轴非常相似,如果你有使用过视频编辑软件的经验的话,这个界面对你来说是一目了然的。​

界面上边的时间轴是以秒为单位的。我们想要做bg1在1秒内从完全透明慢慢淡入出来的动画。

首先点开红点录制按钮​。在这个状态下我们对bg1和它的子gameobject(在当前情况下它并没有任何子gameobject)的操作都会被记录在动画中,在录制结束后这些操作都会被还原,所以你可以放心的对bg1进行任何的改变。

我们将时间选中在1秒的位置,在inspector里边bg1的sprite renderer里的color中,将bg1的透明度拉满

可以看到时间轴里最开始和一秒的位置各生成了一个记录bg1的sprite renderer的color的小菱形。其中最开始的位置记录的是我们bg1默认的状态,也就是透明度为0的时候,1秒的位置记

录的是我们刚才进行的修改,将透明度拉满的状态。​

我们想要录的东西目前只有这些,点一下红色的录制按钮结束录制。如果你点击红色圆点录制按钮后边的三角播放按钮,可以看到我们录好的淡入效果,当然他会不停的循环播放会让你有点眼花……​

在project里边我们能看到刚才的录制生成了两个文件,一个是和bg1这个gameobject同名的叫做bg1的动画管理文件,一个是叫做bg1a1的动画文件,动画管理文件的作用虽然非常重要,但是此时我们还不需要去了解,就让它先维持它默认的样子就好。我们选中bg1a1,看他的inspector,​

显然我们需要它淡入之后就静止不动,所以将loop time​点掉。​

现在我们点击上方的play按钮,可以看到游戏开始后,bg1从黑暗中慢慢淡入了进来。

但是一张背景淡入后总不能一直都是它吧,我们还有淡出的需要呢。​

那么我们再来录制一段淡出的动画。​

在animation里边点击bg1a1的名字,会出现下拉菜单,我们选择给bg1新建一个​动画create new clip,就叫bg1a2好了。​

和刚才的操作差不多,但是这次我们先选中视频开始,建立一个透明度拉满的帧,再选择1秒的位置建立一个透明度为0的帧,然后在project里找到bg1a2把loop time点掉。​

如果你的懒得录制这个动画,也可以在bg1a1里边选中它的关键帧ctrl c ctrl v到新的动画里,毕竟,淡出就只是将淡入的两个关键帧掉了个个而已嘛。

现在我们有了淡入和淡出的动画。在hierarchy中选中bg1的情况下,点开animator页面,我们将可以看到bg1的两个动画bg1a1和bg1a2,并且bg1a1是默认当bg1在屏幕上被激活时播放的动画。

所谓在屏幕上被激活指的是当它存在在屏幕上且active是true的时候的第一时间,我知道这样说有点绕口了,具体来说的话,当bg1本来就在hierarchy里可以看到它,而且它在inspector里这个小勾是勾上的情况下

游戏开始也就是我们唯一的scene——samplescene开始运行的时候,就是它激活的时候,所以我们现在点击play按钮,bg1a1的动画会在游戏开始后立刻运行。如果它一开始不在scene里,到了游戏一半的时候我们才用代码把它放进scene里的话,那就是放进去的时候才算是激活。如果它在scene里但是没有打上勾,那就是打上勾的时候才算是激活。

如果我们并不想让它这个动画在它激活的时候就运行呢?如果我们希望我们做了操作才可选择的播放bg1a1或者bg2a2呢?

首先我们在animator的空白处右键,建立一个空的动画

这个空动画非常不重要所以我连名字都懒得给它改=_=

在enter上右键set stateMachine defaultState,将线连到new state上。这样当bg1被激活的时候,我们可以确保它什么动画都不会放。

点击左上角那个小小的+按钮,为这个动画管理文件增加一个int变量,就叫a_num吧。我们要用这个a_num的值控制bg1播放什么动画。当它是1的时候播放bg1a1,当它是2的时候播放bg1a2,当它是0的时候什么都不发生。

然后我们考虑一下可能发生的情况,我们可能从不播放动画到播放bg1a1,可能从不播放动画到播放bg1a2,可能从播放着bg1a1的时候播放bg1a2,也可能从播放bg1a2的时候播放bg1a1,所以我们就在每个动画块上右键make transition将有可能的过渡线连起来。

然后我们分别点上每条线,在它的inspector里的conditions里设定它的条件。​所有指向bg1a1的线都要求a_num equals 1,所有指向bg1a2的线都要求a_num equals 2。

同时把每根线上的has exit time勾掉,因为我们希望执行时候立刻开始播放我们选择的动画而不是等待给现在的动画留一点结束时间后再播放我们的动画

在bg1a1和bg1a2两个动画之间的线里我们还能看到动画过渡时间的设定。假如播放bg1a1到一半,bg1的透明度正在0.5的时候,我们下令播放bg1a2,bg1a2开头的时候bg1的透明度是完全不透明的1,如果过渡时间设为0的话,就会看到bg1突然从半透明一下子变成不透明,然后慢慢透明化,如果设定了过渡时间的话,就会看到bg1从半透明渐变成不透明再慢慢透明化。但是在我们这个教程里,基本不会出现bg1a1播放到一般就要求切换为bg1a2的情况,所以这里这个过渡时间设为0或者留着它都没有关系,不会有什么看得出来的影响。

我们设定了需要用a_num这个变量来控制bg1是播放哪个动画,但是怎么在游戏过程中改变a_num的值呢?在这里我们必须要开始写我们的第一个脚本。

不用对写脚本太紧张,unity使用的语言c#是一种非常友好易用的语言,在unity里它被作为脚本语言使用,所以对于用户对c#的掌握度也要求的非常低。而且我们现在要做的事情极其简单……就只是改一个变量的值而已。

在这之前我虽然希望没有任何编程基础的小伙伴能够去看一下c#的语法,https://docs.microsoft.com/zh-cn/dotnet/csharp/tour-of-csharp/index

它的语法内容并不多,大概一个小时就可以看完。但是如果不使用只是看的话,语法这种东西就像水一样看完也不会在记忆里留下任何痕迹的…………所以我就预设你并没有看过语法好了,接下来的代码相关的部分我会尽量顺便的把需要知道的东西写一下。

为了便于管理和查找,我们在assets里专门建立一个叫做script的文件夹来放我们的脚本文件吧。

在script文件夹里右键create-》c# script,新建一个脚本文件,我们就把它命名为root_script,起一个宽泛的名字,以后什么功能都写在里边就好啦。

unity的脚本需要绑在一个gameobject上,在这个gameobject激活的时候,脚本也会开始运行。

那么我们就在hierarchy里边右键新建一个空白的gameobject用来挂这个脚本吧。我给它起名root。

按住这个文件把它拖放到hierarchy里边的root上,你会在root的inspector里看到脚本作为一个component被放在了root这个原本是空着的袋子里。

现在双击project里边的root_script,unity会打开关联的IDE来让你编辑这个脚本文件,windows下默认关联的是visual studio,mac是mono developer,如果你使用的是mac,强烈建议将关联IDE更换为vscode…………

打开这个脚本文件可以看到,它里边已经默认生成了几行代码。


using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class root_script : MonoBehaviour {

// Use this for initialization

void Start () {

}

// Update is called once per frame

void Update () {

}

}


这几行代码中

这里是文件头,用来放会在代码里调用到的namespace的名字,unity和c#自身带有默认的一些库,比如上边的System和UnityEngine,这些库里已经写好了很多常用的功能,我们想要使用的时候只要直接调用就可以,但是System.Collections.Generic.xxxx这样调用也太长了呢……所以我们在文件头写上using System.Collections.Generic;这样在代码里边直接写xxxx就可以了不用那么长……

具体这些库里都有什么功能可以去查阅微软和unity的相关说明文档,不过大部分时候大家是不看的(喂)……通常做法是想要实现一个功能,然后再直接上网查这个功能在库里有没有写好的函数,这就是调库侠的正道(住口)

继续往下看

c#里边定义一个东西基本结构是

类型 名字

所以这里是定义了一个叫做root_script的class,第一行从左往右,public是修饰符表示这是个公共class,class是类型,root_script是名字

如果你没有任何编程基础的话,可以将class看做一个空的塑料袋(又来了),里边会由我们装入各种函数,在需要的时候就可以使用那些函数。

 : MonoBehaviour说明这个class继承了MonoBehaviour这个已有的class,MonoBehaviour是UnityEngine里边已经定义好的一个class,里边已经官方放入了很多功能,具体都有什么可以去看https://docs.unity3d.com/ScriptReference/MonoBehaviour.html

当然现在不看也没有关系…………

继承的意思就是,我们新建的这个叫做root_script的class,默认带有MonoBehaviour这个class的所有功能。即使我们一行代码都没写,root_script也可以直接装作自己有那些函数来调用MonoBehaviour的函数。

然后我们再看root_script里边现在有什么

在c#里(其实是在大部分编程语言里)//后边一直到行尾的内容说明是注释,就是会被编译器当做不存在的东西,不管你写什么都可以

所以有个蛮有名的笑话是这样

所以// Use this for initialization 和 // Update is called once per frame 是unity写给你看的注释,并不是程序的一部分,你将它们删掉也没有关系

因此其实root_script里边装的是

void Start () {

}

void Update () {

}

两个函数。

还记得我们之前说的定义结构是

类型 名字

void是函数的类型,void这个类型就是空,这个函数执行完啥也不返回的意思

Start是函数的名字

()是函数传入参数的地方,这里是空的说明不需要传入参数。

{}是函数里边具体的内容,这里是空的是在等着我们往里写东西。

start会在挂载这个脚本的gameobject激活的时候运行,update则会在挂载这个脚本的gameobject存在于场景中的每一帧运行一次。

为什么这两个function(函数)有这样的能力呢,因为这是在MonoBehaviour里边写好的功能。我们的root_script继承了MonoBehaviour所以这两个function就直接有了这样的功能。

不过目前这两个功能我们都不需要,所以不用管它就让它空着。

我们来写2个自己的function,一个用来把a_num设为1,一个用来把a_num设为2……


using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class root_script : MonoBehaviour {

// Use this for initialization

void Start () {

}

// Update is called once per frame

void Update () {

}

public void bg1fadein()

{

GameObject temp = GameObject.Find("bg1");

Animator an = temp.GetComponent<Animator>();

an.SetInteger("a_num", 1);

}

public void bg1fadeout()

{

GameObject temp = GameObject.Find("bg1");

Animator an = temp.GetComponent<Animator>();

an.SetInteger("a_num", 2);

}

}


红色的部分是我们要添加的部分。

可以看到我们定义了bg1fadein和bg1fadeout两个函数。这两个函数前边都加了public这个修饰词,表示是公共函数,添加这样修饰词的函数我们才能够在外边的unity的编辑界面上调用它,不然就只能在root_script这个class里边使用它。

GameObject temp = GameObject.Find("bg1");

这里出现了两次GameObject,这是UnityEngine里边定义好的一个class(类),等号前边就是定义了一个类型是GameObject的名字叫做temp的东西,等号后边则是直接调用了GameObject的一个静态函数Find。

粗暴一点来说的话,我们平时写的普通的函数一定要是这样才能用

myclass a1=new myclass();

a1.myfunction();

但是如果myfunction是个静态函数的话,就可以直接这样用

myclass.myfunction();

这种情况下a1.myfunction()就不能用了。总之就是这样的区别。

在之后我范例里边出现的代码中,如果出现了之前并没有定义过的class和function,说明就是调用了已有的库里的东西,大家可以直接在搜索引擎中用它作为关键词搜索来查看具体用法。

Find是GameObject的一个静态函数,功能是找到场景中的叫做bg1的gameobject。

然后我们用一个=把两边连起来,就表示当我们在下边的程序里写了temp这个名字的时候,表示的就是我们在场景里放的叫做bg1的那个GameObject,所以

GameObject temp = GameObject.Find("bg1");

Animator an = temp.GetComponent<Animator>();

这两行等价于

Animator an = GameObject.Find("bg1").GetComponent<Animator>();

那我为什么一定要用temp倒一下呢,因为有时候GameObject.Find("bg1")要用到不止一次,起个短点的名字好写……当然在现在的function里这个优势并没有体现出来……

temp这个名字就是随便起的,你可以任意使用自己想要的名字来替代它。

这边我们提前来介绍一下unity里用代码获取指定GameObject的方法

unity的场景里的所有东西都是gameobject,在hierarchy里边可以看到他们的列表并对他们进行选择。那么在代码里怎么选择一个gameobject呢?

在unity的场景里寻找一个确定名字的gameobject有两种方法,

一种是直接用Gameobject.Find(“名字”),这样会返回场景里叫做这个名字的gameobject,不管它在多少层的子物件底下都会找到。比如下图这样结构的时候,GameObject temp = GameObject.Find("ui_editname");虽然ui_editname是canvas的子gameobject,但是依然可以直接通过名字获取它。问题就是,当同一个场景有多个相同名字的gameobject的时候,你就不知道是获取了谁了。

比如这种时候我写了GameObject.Find("bt1"),就不知道获取的是哪个bt1了…………

另外当一个GameObject不在激活状态的时候GameObject.Find是找不到它的。

这种时候可以用第二种方法,先获取父gameobject,然后通过父gameobject的transform获取子gameobject的transform,然后再通过这个拿到需要的那个gameobject。

比如我想获得ui_editname下边的InputField下边的Text

获取方法就是GameObject.Find("ui_editname").transform.Find("InputField/Text").gameObject
得到这个Text的gameobject之后就可以用
GetComponent对它的component进行各种操作和修改。

当然在现在我们只用了GameObject.Find这种方法,但是第二种很快我们之后也会用到的。

Animator an = temp.GetComponent<Animator>();

这一行的前半截,我们定义了一个叫做an的animator类型的东西,等号的后半则是获取了temp上边的animator组件。也就是用an来指代temp上的animator组件。

我们回到unity编辑界面,选中bg1,可以看到它上边有sprite renderer和animator两个组件(component)

我们上一句是用temp来指代场景中的bg1,temp.GetComponent<Animator>()就是场景中的bg1这个GameObject获取了它上边的animator这个组件,这样我们就可以像在编辑界面修改组件里边各项的值一样,用代码来修改组件里边各项的值了。

an.SetInteger("a_num", 1);

我们知道an指的就是场景里的bg1上边挂的animator组件,这是我们刚在上一行给它起的名字。

SetInteger是animator的一个函数,具体功能就是设定动画控制里边的变量值。animator都有哪些函数可以直接在unity的文档里查到。

总之这样写就是将场景里bg1的animator里的变量a_num(在我们刚才做动画控制的时候设定的那个)的值设为了1。

就像我们刚才做动画控制设定好的那样,当a_num是1的时候,如果动画不是正在播放bg1a1,那么它就会开始播放bg1a1,这样就实现了在代码中控制bg1什么时候播放淡入动画。

同样的,这个值设为2的话就是播放淡出动画。

我们现在有了控制背景淡入淡出的两个function,这两个function是我们新放入root_script里边的东西,和Start与Update不一样,这种我们自己写好的东西,是没有什么自动调用的功能的,如果我们不调用他们,他们就只会静静的躺在那里,一定要我们自己做一个东西来在需要的时候调用它们,才会执行我们写的那些代码。

添加一个按钮​

一般来说,avg里并不会有一个按钮让你选择背景淡入淡出,但是我们现在还没有人物和剧情,所以就简单粗暴的先用一个按钮来演示背景淡入淡出的功能。​

在hierarchy空白的地方​右键ui-》button

hierarchy里就会多了一个叫做canvas的gameobject和一个叫做eventsystem的gameobject。

​canvas是管理ui显示的东西,button,image等都属于ui,需要放在canvas下边才能被按照canvas上设定好的规则渲染到画面上。

eventsystem则是用来接收玩家输入的东西,如果没有它,你的鼠标和键盘都无法和游戏进行任何互动。​

当你想要给画面里增加一个ui原件比如按钮的时候,unity会自动把这两个东西帮你生成好,然后把你的按钮放到这个canvas下边。​

选中我们刚生成的button,可以在inspector里边看到它有canvas renderer,image,button三个组件,而且transform变成了rect transform(这是ui特有的),而组件中的canvas renderer决定了它必须要放在一个canvas下才会被渲染

你可以试着把button从canvas下边移动到其他地方比如和canvas平级,就会看到画面上刚才还能看到的按钮不见了。

所以还是把它移动回去吧。

关于ui我们之后会有很多要说的东西,不过这里不是讲ui的时候,我们先什么都不说。

我们双击选中刚生成的这个按钮button,它出现在屏幕的正中间,但是你会发现它的位置并不在我们的main camera的正中间,不如说main camera根本不能照到它。

在canvas默认的这个模式里ui和其他的部分是这样分开的,为了便于理解,你可以将canvas想象成ui专用的camera,所有ui的出现大小和位置是相对于canvas那个框的,而和main camera的取像框没有关系。unity将canvas看到的ui画面​叠加在main camera看到的画面上。

我们将button用左上角的工具组里的移动工具移动到画面右上角这样感觉舒服一点。

这个将是我们的淡入按钮,将它在inspector上方显示的名字改为Button1,

然后选中它的子gameobject——Text

​将Text的inspector里边的Text下的Text框里的文字改为“淡入”

再创建一个button用来实现淡出。用unity左上角的功能按钮组里的移动按钮将这些按钮放到画面右上角比较舒服的地方。

​我们选中button1,看到它的inspector里边Button里的onclick事件列表是空的,点击加号按钮

生成了一个用来挂function的空位。​

将hierarchy里边的root拖到None那个位置上,然后在下拉的function列表里选择bg1fadein​

这样我们刚才写的bg1fadein这个function就被挂在了这个淡入按钮上,每点击一次这个按钮,就会执行一遍bg1fadein。​

使用同样的操作将bg1fadeout挂在淡出按钮上。

现在点击上方的三角play按钮在game页面开始游戏,你可以点击淡入按钮和淡出按钮查看效果了。

背景是如此显示,立绘当然也可以如此显示。​

那么来试一试吧,在画面上添加2个按钮,一个点击后将立绘fg1从画面左边移动淡入到画面中心,一个点击后将立绘从画面中心淡出

需要注意的地方是立绘要显示在背景前边,所以fg1的position的z值需要比bg1的position 的z值要小。比如bg1的position的z值是0时,fg1的position的z值可以是-1。

​扩展阅读:动态立绘的制作方法​​​​



TOP

访客数: 3128234
aa