找回密码
 立即注册
查看: 510|回复: 1

针对2D美术的Unity入门级教程

[复制链接]
发表于 2023-1-28 13:38 | 显示全部楼层 |阅读模式
关于这篇教程

这篇文章仅针对一些非编程向的美术人员快速的了解Unity场景该如何搭建的一个简单教程,这个教程中不包含任何代码的部分,风格偏向于傻瓜式教程。对于一些初次希望接触Unity2D美术人员,该教程可以帮助你们快速的了解Unity,并大概的知道该如何构建一个简单的场景。该文的目录结构如下:

  • 关于游戏引擎和Unity引擎
  • UnityHub和Unity主编辑器
  • 如何新建一份2D工程文件
  • Unity的所有窗口以及如何保存你的布局
  • Camera和Game窗口的分辨率关系
  • 如何导入2D游戏素材
  • 关于GameObject和功能组件
  • 了解游戏材质和着色器以及创建一个默认的材质
  • 利用ShaderGraph来编写一个简单的着色器
  • 如何搭建一个2D场景
  • 如何对Texture2D进行切图
  • 如何搭建一个Tilemap场景
  • 为游戏增加后期处理效果
  • 如何导出你的工作内容
希望通过该文,你能够快速的入门Unity并能够搭建简单的场景。
一.关于游戏引擎和Unity引擎

1.1 什么是游戏引擎

最早的游戏开发其实是没有引擎这个概念的,开发一个游戏几乎是纯粹的代码,甚至早在8位机时代连游戏美术素材都是通过代码来存储的。
不过长久的开发游戏的过程中,自然而然就会发现这么一件事,那就是似乎写一个新的游戏并不需要完全从零开始,之前写过的游戏里,很多代码是可以复用的。所以开发者们就把这些代码积累下来了,久而久之这些代码变得越来越坚固,形成了一套完善的框架,它就是游戏引擎。所以所谓游戏引擎,它只是一个“半成品”。
如果拿画画来说的话,对于早期的程序员来说,做游戏就相当于连纸、笔啥的都要自己造。长久的绘画过程中,发现纸笔这些东西其实完全不需要重新做,之前做的还能用。所以久而久之,有一些人就专门开始制作纸和笔,让画师们能够不需要再操心一些基础的东西。
所以游戏引擎就是你的纸和笔,至于上面要画些什么弄些什么,完全是自由的。
1.2 什么是Unity引擎

游戏引擎有很多,比较流行的比如UE、Unity、Godot、GameMakerStudio、RPGMaker、Cocos2dx等等。每个引擎都有自己的特色和适用的范围,它们之间更多是差异化竞争的。比如UE主打的是高端的3A级游戏开发,而Unity则针对手游、独立游戏等比较需要跨平台的游戏类型,Godot则主打开源、免费独游开发等特色。
FAQ


  • 是否UE做的游戏画面比Unity好看
    有一定的关联,但不是绝对的。因为决定画面的更多是着色器和材质,而这些则是由开发者来开发的。只不过不少开发者并不会自己开发着色器,用的是UE和Unity自带的着色器,而UE针对PC游戏,所以UE的默认材质会偏向于3A,而Unity则偏向于手游和独游,所以Unity的默认材质会偏向于低性能。因而大多数人如果只用它们的默认材质,可能造成了一个Unity的画面比UE弱的假象,但其实这是一个错误的观念。
  • Unity不知道你要做什么游戏,它是如何为你提供工具的
    正因为Unity并不知道你要做什么游戏,所以它能提供给你的工具大多数都是非常底层的工具,比如画面如何渲染、音频如何播放、素材如何加载和卸载等,并且需要你自己写很多的功能代码。而类似于RPGMaker,它们则更加偏向于某一种游戏类型,所以它们内部的工具就可以更加细化,不过反过来说,这也是在约束开发者。
二.UnityHub和Unity主编辑器

2.1 什么是UnityHub

Unity从最初发布到现在已经有很多年的历史了,所以积累了大量的不同的Unity版本,现在主流的版本包括Unity2019,Unity2020,Unity2021等。当然,你也可以找到更老的版本。
为了方便大家使用这些版本,所以Unity专门做了一个比较简单的工具,叫UnityHub,你可以通过UnityHub来安装不同版本的Unity,并且可以在UnityHub创建和维护你本地的工程文件。要使用UnityHub,你还需要先注册一个Unity的账户。
2.2 安装UnityHub并快速概览UnityHub

可以在这个页面,下载UnityHub,如下图所示位置:Notion – The all-in-one workspace for your notes, tasks, wikis, and databases.可以在这个页面,下载UnityHub,如下图所示位置:


下载下来之后是一个叫做UnityHubSetup的exe文件,安装这个文件到你期望的位置。安装过程没有任何要注意的地方,直接选择位置并安装即可。
打开之后,Unity的界面如下图所示(我已经登录),如果你的页面是英文的,则可以点击下图中的齿轮,并在Perferences中的Appearance页面可以看到语言选项,调整你需要你的语言即可。




左侧栏位里有项目、安装、学习等栏位,在项目页面,我们可以看到自己本地的工程文件,我们创建一个新的工程则可以点击项目页面右上角的蓝色按钮新项目。


再点击安装,我们可以看到自己在本地已经安装的Unity版本,如果你没有安装任何版本,希望安装一个新的版本或者卸载旧的不用的版本,都可以在这里进行管理和维护。


下载并安装一个Unity版本是完全由UnityHub来管理的,但是我们需要做一些配置。首先是Unity版本安装包和安装位置。打开刚才的偏好设置界面(也就是改语言那个界面),在安装一栏中我们可以配置自己期望的安装位置和安装包下载位置。


在项目一栏中,我们则可以配置当创建一个新的项目的时候,它的默认存放位置在哪。


Ok,对于UnityHub的内容,我们暂时了解这些内容即可。
2.3 注册Unity用户并获取开发许可

在安装正式的Unity版本之前,还需要通过邮箱或者手机来注册一个Unity用户,否则应该是无法启动Unity的,(当然,这其实是一个很不合理的事情),还是在UnityHub界面。


然后它会带你来到Unity主站的注册页面,如下图所示:


填写所有的信息并点击创建UnityID之后,就可以创建一个新的用户了。之后你会收到一份验证邮件,验证通过后这个账户就可以开始使用了。


之后我们在UnityHub登录这个账号,UnityHub还是会弹出一个web页面让你进行登录。在页面登录完成后即可。


另外,如果你是第一次登录的话,Unity可能会让你填写你的个人工作信息以及绑定手机,主要是为了收集一些Unity的用户信息,(这个行为其实挺流氓的),职业、工作室名称和行业都可以随便写,这个无关紧要,然后要绑定你的手机才能登录成功(微笑就可以了)




之后就登录成功了,UnityHub也会变成下面这样。


如果没有登录成功就再登录一遍就可以了,然后我们点击上图位置中的管理许可证。然后点击下图位置中的添加许可证。


然后选择个人免费许可证即可




然后就可以了,Unity的许可证机制有点麻烦,它会两三天就失效了,然后需要你重新按照上述步骤再走一遍,但是我也不知道意义在哪。Ok,有了以上的内容,就可以开始安装Unity版本了。
2.4 选择并安装Unity主编辑器

在安装一栏右上角选择安装编辑器,你可以看到现在主流的版本。




如果以上版本里没有你想要安装的版本,那么可以点击右侧的存档栏。在存档栏有一个下载存档,它会弹出一个web页面,然后你可以在这个web页面里找到所有的历史的版本。




最上面一栏是大版本号(以年代来记录的,比如2019,2020等),下面的则是小版本号。大版本号的话,一般倾向于选择比较稳定的版本,2019,2020,2021等,小版本号,则一般选择最新的小版本号。
如果你不懂的话,也不是团队开发者的话,可以直接下载它推荐的版本即可。如果你是在做一个游戏项目可以询问你们的制作人现在用的是什么版本的Unity。
在这篇教程中,我们选择的是2021的最新版本,但是不用害怕,小版本号主要是修复和改善bug和使用体验,在大的技术调整上内容很少。


点击安装后,Unity会提示我们需要哪些必要的组件。


最上面一栏是开发工具,但是要注意的是,这个开发工具是用来写代码的,对于美术来说,这个玩意是不需要的,所以不需要勾选。下面的平台则是针对打包的时候要用到的编译文件,也不需要用到,所以都不需要勾选。下面的文档也主要是针对开发者的,所以对于美术来说是不需要的,最下面的语言包则是针对Unity的界面语言的,如果你的英文不够好的话,可以选择中文语言包,不过不用期望太多,因为它好像是机翻的。


然后点击安装即可。


然后它就开始下载了,看到这个界面就算是没问题了,如果你遇到了下面的安装异常


那么你可以先退出UnityHub然后通过管理员模式重新启动并再次执行上述的安装步骤,当然,也可以通过将你的安装位置和安装包下载位置设置在一个不需要管理员权限就可以创建文件的地方。下载完成后,我们的编辑器版本就多了一个。
FAQ


  • 这些版本的Unity有什么区别
    区别有很多,但是都是一些相对比较细节的区别,往往新版本的Unity内容的排布更加合理,代码功能更加齐全,比如Unity2019的URP并不支持点光源阴影,但是2020版本就支持(不需要了解什么是URP,这只是一个案例)。内容菜单项划分的粒度更加细节,但同时Bug和各种未知的问题也更多,更容易崩溃。
三.如何新建一份2D工程

下面我们来学习创建一个新的Unity2D工程文件,点开UnityHub的项目一栏,点击新项目就可以配置项目的属性了。




在这里,我们选择2D(URP)模板,至于为什么是这个模板解释起来比较复杂,一两句话是说不清的,往简单了说,你知道URP做出来画面效果更nice就可以了。项目名称随意,中英文都可以,项目的设置的是默认位置,你可以在偏好设置/项目/默认位置中改动这个路径。版本管理暂时用不到,不过你需要知道,版本管理是用于多人合作的就行了,它会将你的项目寄存到云端。这个部分比较复杂,展开说可能堪比这篇文章的体量的,所以我们留到另外的文章里说。然后点击创建项目即可。创建的同时,UnityHub会自动打开这个项目。
以及,如果你有多个Unity版本,创建时可以在页面的最上侧选择你要用的Unity版本。




这个就是Unity的主界面了,下面我们来了解Unity的所有窗口的含义和如何调整布局。


四.Unity的所有窗口以及如何保存你的布局

这个部分我们除了认识所有的窗口之外,也需要了解一些简单的快捷键。Unity的界面和各类子界面非常多,我们按照下面的顺序来逐个认识。

  • Project(项目)
  • Console(控制台)
  • Hierarchy(层级)
  • Inspector(检查器)
  • Scene(场景)
  • Game(游戏)
开始了解它们之前,我们先改动一下Unity编辑器的语言,点击窗口主菜单的Edit→Preference


在偏好设置这一栏里,我们可以找到Language选项,这里可以改动我们所需要的语言,(特别要注意的是,如果你在之前安装Unity版本的时候,没有安装任何额外的语言包的话,这里是没有Language选项的)


4.1 Project-项目窗口

项目窗口存储了所有你游戏中所需要的文件,它全部都存储在Assets文件夹下,这个Assets文件夹对应着你的项目工程文件里的一个文件夹。


右击项目文件夹的空白处,在弹出的菜单上可以看到在资源管理器中显示,这样就可以打开目标文件夹了。


它本质上就是工程文件夹下的一个文件夹,固定名为Assets,这个名字是无法变化的,但是它内部的文件夹几乎都可以重命名(有几个个别案例不能重命名)


当我们导入图片、脚本、字体、音频、视频等任何文件时,它都会显示在这里,一些需要在Unity中创建的文件也会存储在这里,比如动画文件(是Unity定义的一种文件格式)等。
每个开发者都需要维护管理Project文件夹,删减、增加各类资源文件。对于任何一个有效的文件,你都可以直接从系统文件夹中将其拖入Project窗口来实现导入。通过Project窗口的右键菜单/创建/文件夹,我们可以创建一个新的文件夹,这个和系统文件夹没有任何区别。只不过它是显示在Unity里的。
除了直接从系统文件夹拖入,你还可以通过右键菜单/导入新资源来导入任何有效的资源,两者的效果是完全一样的。


拖动资源到文件夹上就可以将其放入子文件夹,同样,你也可以拖动文件夹到文件夹。类似于系统文件夹。比如我们可以随便导入一张图到这个地方:


4.2 Console-控制台窗口

Console也就是控制台窗口,不过这里的Console无法输入命令,当系统有什么错误、警告、普通信息时都会在这个地方显示,错误为红色,警告为黄色、普通则为白色。在游戏开发阶段时,程序可以通过这个窗口来进行简单的调试工作。对于美术来说,这个窗口的意义相对较弱。


4.3 Hierarchy-层级窗口

Hierarchy窗口是显示所有场景和游戏对象的窗口,这里有一个新的概念就是游戏对象(GameObject),游戏对象可以看作一个拼图,多个拼图拼接起来一个完整的游戏。而场景就是GameObject的容器,场景的存在往往是处于性能考虑,比如如果你的游戏世界非常庞大的话,那么你就得考虑将这个庞大的世界拆分为若干个场景,每个场景里则各自通过GameObject来搭建不同的内容,但是GameObject这个拼图是完全空白的,这个拼图有什么用是由开发者给它挂载了什么组件来决定的。比如Camera就是一个GameObject挂载了Camera组件。
这里的GameObject和组件我们在第五节去详细了解,现在只需要知道Hierarchy窗口是用于查看所有已经添加的GameObject即可。
另外,当游戏启动时,Game Object也有可能被动态的创建和删除,但是这些往往是一些游戏元素,Npc、怪物、道具之类的。对于美术来说,要创建的内容往往是静态的场景,你可以通过右击Hierarchy窗口空白的位置来打开右键菜单,里面有各类你可以创建的元素,包括空的游戏对象、2D对象、3D对象、摄像机、粒子系统、音频、视频等等。


前面说到,摄像机就是一个空的GameObject挂载了摄像机组件,所以你创建一个摄像机等同于创建一个空对象并为其挂载摄像机组件。
右击一个GameObject可以更改它的名字,拖动一个GameObject到另外一个GameObject上可以使得它成为一个子对象。当你拖动父对象时,子对象也会一并被拖动。这个除了用于管理GameObject,也方便校准两个对象之间的距离,比如有一个3D对象,它是由两个模型构成的,那么我们就可以把这两个模型都塞入一个对象里,让它变成一个完整的对象。


同样,对于一个2D图片来说,如果你将其拆成了两部分,那么可以通过这种方式将两个2D对象组合在一起,调整子对象之间的距离,之后将其视为一个完整的对象即可。
4.4 Inspector-检查器窗口

如果你希望查看一个GameObject并改动它的组件参数,增加或者移除组件,你可以点击Hierarchy窗口中的目标对象。然后Inspector就可以显示改对象的所有信息了。对于一个空对象来说,它的Inspector窗口是这样的。


即使它是一个空对象,它也会具有一个Transform组件,这个Transform决定了它的位置、旋转、缩放信息。一个游戏对象至少具有一个Transform组件,这个组件无法添加也无法移除,是任何对象默认具有的,如果这个对象是UI对象的话,那么Transform组件会变成针对UI对象的RectTransform组件。调整Transform组件的值就可以移动它的位置,不过我们通常会使用Scene窗口来移动它的位置。
你可以点击添加组件来为其添加组件,Unity的组件数量非常多,当然,更多的组件是程序自定义的组件。对于以搭建场景为核心目的的美术人员来说,要学习和认识的组件并不多。
另外它还有一个标签和一个图层的概念,这可能会给部分美术一个误导,以为图层就是类似于PS或者什么绘图软件里的图层,怎么说呢,这里的图层其实是Layer,也就是层级,它跟PS里的图层不一样,它无法决定遮挡关系,也无法按照层级删除。关于这个层级,后面的章节如果遇到的话,我会解释它的作用的。
我们可以点击摄像机,查看摄像机的参数信息,它就是一个相对比较复杂的组件了,有很多的内容和参数可以调整的,之后我们以2D美术的视角来拆解其中我们需要的部分。对于我们不需要的部分则简单略过。现在就看看即可。


4.5 Scene-场景窗口

场景窗口就是一个能够看到我们搭建的场景的具体内容的地方了,场景窗口就相当于是一个特殊的摄像机,它以旁观者的视角观察这个世界。我们可以通过Scene窗口下图位置中的2D按钮来改变场景窗口的摄像机模式


如果是2D的话,那画面就是正交的,如果是3D的话,那么画面就是透视的。(正交的意思就是没有近大远小)


在3D视角下这个世界会显示的更加明朗一些,我们可以尝试在Hierarchy窗口中新建一个3D方块。


之后在Scene窗口中就可以看到这个方块。


为了方便开发者漫游这个3D世界,Unity提供了很多快捷键来对Scene窗口的视角进行操作。如下列表所示:

  • 快速的将视角锁定并跟随一个对象
    双击Hierarchy窗口的目标对象,等价于选中Hierarchy中的某个对象后按Shift+F键(意思是Focus,即聚焦)处于聚焦状态的时候,下面的相机围绕观察时会围绕该对象,并且该对象移动时,镜头也会跟随他一起移动。
  • 改变Scene窗口的视角方向
    按住右键不放并上下左右拖动鼠标
  • 像3D游戏一样在Scene窗口漫游
    按住右键不放并按WASD来进行移动
  • 使得相机围绕一个点或者围绕某个对象进行观察
    按住Alt键的同时并按住鼠标左键并上下左右拖动
  • 使得相机拉近或者拉远
    按住Alt键的同时并按住鼠标右键并上下左右拖动,等价于鼠标的轮滑键
在Scene窗口中我们还能看见各种不同的图标,这类图标都是因为这些组件是没有可渲染的实体的


如果你不希望看见图标,或者你想隐藏特定的图标。可以通过Scene窗口下列位置的按钮来实现。将这个按钮点掉的话,所有的图标都会消失。


当我们在Scene窗口选中一个对象时(等同于在Hierarchy中选择它),它的身上会显示箭头(移动操作柄),比如拖动这些箭头可以让它们在固定的轴进行移动,如果你想调整它的旋转或者缩放,当选中这个对象时,可以按E(旋转操作柄),按R(缩放操作柄),另外,移动操作柄的快捷键是W。






以上就是Scene窗口的大概内容了,虽然还有很多内容没有说,但是大概可以用了。
4.6 Game-游戏窗口

Game窗口比较简单,也就是这个游戏的最终画面,它呈现的就是主相机拍到的画面


上面的一些选项中,Display是玩家的显示器问题,如果玩家有多个显示器,一般游戏只会显示在一个显示器,对于开发阶段不需要考虑这个。
第二个就是分辨率问题了,分辨率同时决定了摄像机的宽高比,初始的分辨率一般是FreeAspect,FreeAspect的意思就是,摄像机的宽高比完全等同于当前Game窗口的宽高比,我们一般是使用HD,也就是1920x1080的分辨率,这个分辨率只是一个比率,它只能决定摄像机的宽高比,如果显示器硬件的分辨率宽高比与这个宽高比不对应,那么它会强行缩放至目标显示器分辨率宽高比。关于这个部分会在第五节详细解释。
后面有一个缩放,对于像素游戏来说,如果缩放不是1.0的话,会出现模糊的情况。但是这种模糊仅限于开发阶段。
PlayFocus则是指当运行游戏时,是否需要将Game窗口置于其他窗口的上侧。后面的小喇叭代表是否禁用音频、键盘则代表是否禁用键盘输入、状态是必须启动时才会显示,显示当前游戏的fps数值以及对系统占用的简化信息,最后的Gizmos有点类似于Scene窗口的图标是否显示,只不过这个Gizmos主要是由开发者来绘制的。比如一些网格线,一些线段,线条啥的。
4.7 调整布局并保存

首先要知道两件事,第一,每个窗口都可以复制多个出来,比如你希望从两个角度观察场景窗口,你就可以复制两个场景窗口,方法就是右击任何一个窗口的标题栏,会展开一个子菜单。你可以添加一个新的场景窗口。


第二,窗口可以任意停靠,左右停靠,上下停靠,或者以tab的形式来停靠。直接拖动标题栏然后移动它们即可。
在调整这些窗口的布局之前,你可以先尝试一些系统布局,点击编辑器菜单栏的窗口/布局如下图所示。


比如我们选择4分割,那么它就是下面这样的,我们可以从四个角度观察Scene窗口。


每个窗口的大小都是可以调整的


当你构建了一个合适你开发的布局时,可以通过窗口/布局/保存布局将其保存,此后你可以从任何布局状态下恢复到你保存的布局结构。
五.Camera和Game窗口的分辨率关系

这是一个非常绕的部分,我们大概作为一个节点去讲一下。Game窗口的分辨率比如下图所示的这些。


它指的其实是一种屏幕宽高比信息,比如你开发时选择了1920x1080的分辨率,但是目标显示器不支持这个分辨率,那么最终的结果就会被缩放适配到目标显示器,因为它并不是真的1920x1080,只是一种比率而已。
同样,它也只会影响摄像机的宽高比,比如你选择了16:9,那么宽高比就是16:9。至于摄像机具体有多大,是由Camera组件的size参数来决定的。


这里的5是什么意思呢?它指的是相机高度的一半,单位是Unity单位,比如说我们设定size为5,那么相机的真实高度就是10个Unity单位,而且我们也在Game窗口设定了宽高比,所以就可以计算出相机的完整大小了。
所以明白了吗?Game窗口的分辨率决定了宽高比,Camera则根据宽高比和参数size来决定自己能拍摄到多少个Unity单位。


这就是Camera组件和Game窗口分辨率的关系。
FAQ


  • 如果我做的是像素类游戏,如何保证画面对应到像素大小呢
    因为联系Camera组件和Game窗口分辨率最终决定只是最后能拍到多少个Unity单位,所以Unity必然给你提供了一个参数,即决定一个Unity单位能容纳多少个图片像素。关于这个部分,我们可以参考第六节。
六.如何导入2D图片素材

第六节算是第五节的延申,在这里我们会了解到如何导入一个2D图片素材,尤其是针对像素素材这类不太倾向于缩放的素材。让我们从文件夹里拖拽一张像素图到Unity的Project窗口,通过导入一张像素素材来说明2D图片素材的配置。


刚导入时,我们主要注意三个参数,分别是纹理类型、过滤模式、压缩三个部分。
6.1 纹理类型

对于任何要作为游戏实体元素的图片来说,纹理类型始终是精灵图,也就是Sprite


当我们选择Sprite时,它会弹出Sprite才有的信息,在里面,我们就可以看到一些Unity单位换像素的信息了。Unity默认的单位像素数是100,也就是一个Unity单位等于100个图片像素。


这个参数是每张图都有的,并不是统一设置,但是往往值都是统一的,所有的图的每单位像素数都是对等的。所以联系前面的相机和Game窗口的关系,我们就可以知道下列关系了。


假设我们选择Game窗口选择16:10,Camera大小设为5,那么一共可以拍到16x10个Unity单位,又假设我们每个图片的每单位像素值都是100,即一个Unity单位=100个图片像素,那么相机换算到图片分辨率大小就是,1600*1000,这个大小对于像素游戏来说是太大了,不过对于大部分的2D游戏是正好。而且平涂类2D游戏往往是支持缩放的。
假设Game窗口分辨率选择1920x1080,Camera高度设为16.875,那么一共可以拍摄到60*33.75个Unity单位,又假设我们每个Unity单位=8个图片像素,那么最终能拍到480x270个图片像素,这个大小也正是我自己的游戏《多洛可小镇》所使用的参数。
因为一个Unity单位=8个像素大小,所以一个像素换算为Unity单位就是0.125个。
6.2 过滤模式

这个说起来比较复杂,我估计美术可能有点不太能听懂,过滤模式是为了让图片在进行缩放的时候相对来说平滑一点,有点类似于加抗锯齿的感觉。对于骨骼动画来说都是选择双线性或者三线性,对于像素游戏来说始终是point模式,或者说无过滤模式。


6.3 压缩

等于一些比较大的图来说,性能的消耗也会很大,所以Unity往往会压缩部分太大的图片,使其占用较低,部分纹理也不需要太高的质量,比如法线贴图、遮罩贴图,全部改成低质量可能画面都没啥区别,但是性能消耗区别就很大了。
那么对于像素游戏来说,压缩始终是无压缩,因为像素素材非常小,不需要压缩,而且压缩后像素素材的色彩会崩掉。所以2D像素游戏始终选择无压缩,那么Unity将不再压缩这张图片,直接以原图质量来显示,而平涂类型则根据具体情况来做选择。


注意,当你调整完任何一个参数之后都需要点击应用按钮,如果没有点击它也会提醒你要点击的,否则这些参数不会生效。
6.4 Texture2D类型和Sprite类型

当纹理类型被改为Sprite类型时,我们发现这个图片会有一个折叠的部分,打开这个折叠的部分,如下图所示。


这里会出现这种情况是因为它俩虽然外观是一样的,但是它们类型不一样,我们导入的这个图片素材,叫做2D纹理(Texture2D),而它后面出现的这个,叫做Sprite(精灵图),两者是完全不同的,我们通常在UI和搭建场景时用的都是Sprite。一个Texture2D文件其实可以包含多个Sprite对象,关于这个部分更加细节的内容可以从下面的章节如何自己切图来学习。
七.关于GameObject和功能组件

看到这里,你基本上已经对Unity有了一定的熟悉了,那么我们就来简单学习两个常用的组件。这里的简单学习的意思就是,对于一些我们用不上或者过于复杂无法一两句话说清楚的,我们都会略过或者做简单陈述。
对于任何一个GameObject来说,挂载某个组件就会使得它具有对应的功能,比如给GameObject挂载一个Rigidbody2D组件,它就会受到重力的控制。如果给它挂载一个Collider2D类型的组件,它会受到其他Collider2D组件的碰撞。对于美术来说,我们所需要了解的基本上都是和渲染有关的。
7.1 Camera组件

首先我们要学习的是Camera组件,当然,只是了解其中的部分参数。

  • 渲染类型
    渲染类型有基础和overlay两种类型,如果是基础,则它就会拍到具体的内容并渲染到Game窗口,而如果是Overlay,就相当于一个图层,这个相机拍到的所有内容都会叠加到另外一个相机的画面上去。在不需要了解太多高度操作的情况下,只需要设为基础即可。
  • 投影类型、大小、裁剪平面
    投影类型就简单了,正交和透视,透视针对3D,正交是2D,不过很多人都做那种HD像素,也就是在3D场景里构建一个2D游戏。就是搞2D素材+透视相机。



大小我们说过了,就是摄像机高度的一半,并且是Unity单位,这里的裁剪平面比较特殊,它指的是这个相机能够拍多远,默认是1000个Unity单位,也就是能拍非常远,在Scene窗口我们其实可以看到它的范围空间,远平面就是它的最远处到起点的距离。



  • 剔除遮罩
    在渲染这一栏里我们只需要了解一个参数就是剔除遮罩。还记得我们在说GameObject的时候提到了Layer,也就是层级嘛?这里的遮罩剔除的意思就是这个相机要拍到哪些层级,如果是everything,就是这个相机会拍到所有层级,如果有的层级没有勾上,那么处于那个层级的GameObject就会被这个相机无视。





  • 背景类型
    背景类型就是相机啥都没有拍到的时候,它要显示什么?2D游戏里一般是纯色,你可以设定一个色彩作为背景色彩。这样的话,如果什么都没有拍到,就全都是这个色彩了。




7.2 SpriteRenderer组件

SpriteRenderer是2D游戏开发中最常用的组件之一,Renderer的意思是渲染器的意思,也就是说它是用来渲染一个东西用的,Sprite是精灵图,也就是渲染精灵图用的。
我们可以先创建一个空白的GameObject,然后为其添加一个SpriteRenderer组件。如下图所示


它的参数信息如下图所示


精灵
首当其冲的就是精灵图,我们可以点击右侧的圆圈,就可以查询project中所有的sprite了。


我们可以选择我们导入的素材,它就渲染出来了。


颜色
颜色就是叠加色彩,也就是正片叠底的色彩,如果是白色,那就是它原来的色彩。


翻转
字面意思,就是指要不要在横轴或者纵轴反转,注意这里的翻转是绘制翻转。


绘制模式、遮罩交互
暂时不需要了解,绘制模式设为简单,遮罩交互设为无即可。
Sprite排序点
排序点指的是当系统以x轴或者y轴参数来排序时(默认是以z轴为排序,也是在后面的被前面的遮挡),它是以自身中心为准还是以锚点为准,Sprite中无法设置锚点,锚点可以在Texture2D文件中设置。


关于这个知识点,后续我们还会继续说的。现在不了解也没关系。
材质
材质几乎是最重要的参数了,它决定了这张图是如何被渲染的,如果你是新创建了一个SpriteRenderer,那么这个材质往往会被设为默认的能够接受2D光照的材质。


这个材质是默认的,甚至无法改动材质栏的信息。关于材质到底是什么,我们会在第八节中来解释。
排序图层、图层顺序、渲染层遮罩
我们先了解一下遮挡计算,Unity会用一个轴来作为遮挡计算的轴,比如说默认是z轴,z表示前后。但是在俯视角的2D游戏中,遮挡往往是在高处的被低处的遮挡,我们可以在某个地方改变这个遮挡计算的轴,这就是遮挡计算。
但是有些元素是不需要参与遮挡计算的,比如在俯视角的2D游戏中,地面是始终会被其他元素遮挡的,那么它就没有参与遮挡计算的必要了。于是我们可以为其创建一个新的排序图层




新增了一个Ground图层,如果我将其拖到Default前面,那么它会永远被Default挡住,如果拖到Default后面,那么它会永远挡住Default层级。
如果两个元素在同一个图层,那么则先对比它们的图层顺序。


图层顺序可以是负值,图层顺序高的在前面,低的在后面。比如1始终会挡住0。而如果两个Sprite,它们的排序图层和图层顺序都相同的话,那么才会进行遮挡计算,也就是根据z或者y来进行排序。
渲染层遮罩
这个我也不太清楚是什么哒喵。
八.了解材质和着色器以及创建默认材质

通俗的理解材质和着色器

这应该是全文最具有难度的一个地方了,如果你觉得听不懂或者太吃力的话,那么可以略过。我们需要解释一下什么是传说中的材质。
那么假设以人、衣服来比较的话,那么一个人的外观就是一个SpriteRenderer,而材质就是这个人穿的衣服。人的脸和身材是自己决定 的(也就是贴图素材),但是穿上了不同的衣服,看起来可能就完全不一样了。材质就是衣服。
而着色器呢?着色器就是衣服的设计图,本质上,着色器是一段在显卡里运行的程序,告诉显卡该怎么渲染这个图,类比到这里,着色器很像是衣服的设计图,告诉裁缝这个衣服该怎么做。
所以着色器决定了材质,而材质会被一个SpriteRenderer“穿起来”用于显示一个对象。
创建一个默认的2D材质

让我们来创建一个默认的2D材质,在Project窗口中打开右键菜单,并点击创建/材质


然后你可以调整这个材质的着色器是什么。


Unity提供了一堆着色器,有不少是历史版本遗留,在URP工程里你只能使用Universal Render Pipeline下的着色器。我们选择Universal Render Pipeline/2D/Sprite-Lit-Default


这个着色器的功能就是渲染原图、并且让原图可以受到光照的影响,如果没有任何光照,它渲染的图就会是黑色的。之后,我们点击SpriteRenderer材质栏右边的圆圈就可以找到这个材质了。当然,你可以直接把这个材质拖到它的材质栏,最后的效果与默认的材质没有区别,因为他俩就是同一个着色器。


九.利用ShaderGraph来编写一个简单的着色器

这个章节原本没有打算写的,因为可能过于复杂了,但是我转念一想,如果只是用ShaderGraph的话,或许没有想象中那么复杂。所以我们来尝试做一个不受到光照影响的能够一闪一闪的着色器,来作为一个着色器的入门级的案例。
什么是ShaderGraph?

ShaderGraph是Unity内置的一种用于开发Shader的工具,它通过节点编辑器的形式来开发Shader,使得这项原本应该十分复杂的工作变得相对轻松了很多。
我们可以右击Project窗口空白处,点击创建/ShaderGraph/URP/精灵无光照来创建一个不接受光照的ShaderGraph,我将其命名为MyShader


之后双击我们创建出来的ShaderGraph就会打开ShaderGraph的编辑器界面。之后是这样的一个界面。


左侧的窗口是增加参数的地方,比如你做了一个水流,你可以通过增加参数来让水流变快变慢,改变色彩之类的。中间的这些节点叫做输出节点,也就是这个着色器最终输出的色彩是怎么算出来的,右上角的是配置界面,维持默认配置即可。右下角则是预览界面,你可以预览你的着色器最后的输出效果。
功能节点

编辑器中默认的Vertex和Fragment两个部分这些叫做Master节点,它们决定了最终的输出外观,改变BaseColor或者Alpha值就会使得最终外观产生变化。除了Master节点,ShaderGraph还提供了一堆功能节点,这些节点都或多或少有一些输入值,和一些输出值。
你可以将这些节点视为一个黑盒,或者一个什么机器,什么一个函数。它们需要接受一些输入,也就是参数,并同时输出一些数据。


这其实也有点像一些模拟经营类游戏,比如说我有一个熔炉,它可以将铁矿石熔炼成铁锭。


只不过在ShaderGraph中,参与运算的往往是一个数字(图片也是一组数字),不过一个什么具体的东西。比如我们有Add函数节点,顾名思义就是加法节点,它可以将两个数字相加后输出,如下图所示:


A和B就是参与加法运算的两个数字,然后Out就是输出结果。当然有一个基本规则就是,对于一个节点来说,只有输出节点才能连接输入节点,无法将输入节点连接到输出节点。
编写一个透明度闪烁的着色器

首先我们要说一些简单的规则,那就是在着色器的世界里,白色是1,而黑色是0,它都是一种数字的形式。透明度也是0-1,如果我们能让一个数字在0-1之间浮动的话,将其输出到Alpha值,它的透明度就会浮动,也就做出了透明度闪烁的效果。
右击空白处,我们可以创建新的Node




控制节点有很多不同的类型,数学计算,UV啥的。我们不放直接创建一个Color节点把它输出到BaseColor




我们直接用黑色输出到BaseColor,那么无论如何,设为该着色器的材质只会显示黑色。


点击保存Assets,然后这个着色器就可以用了。


将这个着色器直接拖拽到我们前面创建的材质上,那么材质就会替换上这个着色器。


然后我们把这个着色器交给SpriteRenderer,然后就能看到一坨黑色了,这是正确的。


有人可能会疑问,我传入的sprite怎么不显示了?这是因为你的着色器中并没有处理sprite。注意着色器和材质的等级其实是要高于sprite这些基础参数的。它的关系如下:


现在我们编写一些代码让它的透明度开始闪烁。闪烁的意思就是数字在一个范围内来回浮动,在各种函数中,三角函数就非常符合这个特性,它们都是周期性的波函数。


我们只要给它一个不断前进的值就可以了,正好ShaderGraph提供了一个叫做Time的节点,Time表示从游戏开始运行时到当前的一共是多久的时间,它是一个不断增大的值,对于sine函数,无论输入的x值有多大,它最终都会被映射到0-1之间。
所以我们可以创建一个时间节点。


然后把Time值输入到sine函数里,我们再创建一个sine节点。然后你就会看见它从黑色到白色之间过度了。


不妨把这个输出到Alpha值,保存着色器之后看看如何?


https://www.zhihu.com/video/1602419790857007104
我们看到它居然还有白色的部分,这主要是因为sine的范围是-1到1,而透明度的值只能是0-1,如果要将一个函数的值约束在0-1之间该怎么办?很简单,加1再除2就可以了
y = \frac{sin(x) + 1}{2}\\ 当然,还有更简单的办法,就是直接把它丢入绝对值符号里。
y =|sin(x)|\\ 正好ShaderGraph也有绝对值函数,我们可以搜索Abs来创建它




保存后,就可以了。


https://www.zhihu.com/video/1602420289803153408
当然,囿于篇幅,我们只能写这么多了, 至于怎么显示原来的图片之类的,这些如果有兴趣的话,我们可以再后面补充更多的知识点。这个该节就是希望让美术能够大概了解一下一个着色器是如何制作的,它又是如何影响画面的。
十.如何搭建一个2D场景

这里就是正事了,来学习一下怎么搭建一个正常的2D场景,我们可以先在Unity的AssetStore下载一个免费素材包。首先打开下面的网站,然后用你之前创建的账户登录。


然后选择2D素材,在里面找一个免费的比较适合搭场景的素材包。那么我选择的是这个
其实啥素材都可以,就是有点东西摆一摆,然后点击添加到我的素材


添加成功后,我们就可以直接在Unity中下载了。点击Unity顶部菜单的窗口/包管理器。


然后选择我的资产


你需要点一下更新按钮,然后你就可以找到刚才自己选的那个素材包了。


注意,如果你在运行游戏的话,这个按钮是点不了的。然后更新之后就可以找到了,直接点击下载。


下载完成后点击导入


它这里有一堆东西,材质、纹理、着色器、预制体啥的。我们并不需要全部导入,只要纹理就完事了。




然后就用这些开始搭建场景吧。我们可以右击project窗口先创建一个场景


然后把这个场景直接拖拽到Hierarchy窗口中,然后删除场景中自带的摄像机,如下图所示:


然后就可以在新的场景中创建GameObject了,当然,你可以直接创建2D里的Sprite,它会直接在GameObject里自动加一个SpriteRenderer。还有,你也可以直接将sprite拖拽到Scene窗口里,它会自动创建一个带SpriteRenderer组件的游戏对象。它们的效果大同小异,只不过部分游戏对象不止有一个SpriteRenderer才会产生这些不同的构造方式。


如果你发现这些元素太小的话,那么说明这个图的像素单位太大了。你可以缩小自己的Camera大小,也可以缩小目标图片Unity单位里所显示的像素数量。(注意,如果这个图片是一个图集的话,那么这会影响它的所有sprite,也就是说一个Texture2D可以切图为多个sprite,具体怎么切图细看下一章节。)


然后就没问题了,之后你就可以创建一堆GameObject,然后通过调整位置,来构建一个场景了。在构建场景的过程中可能需要建立新的排序层级,调整它们的排序顺序,以及调整前后位置等等。


不过这样每个单位都是一个动态的对象,也就是Unity在运行的其实支持移动它们的位置(具体移不移动则主要看程序是否编写了对应的功能),如果我们可以确定一个物体就是一个静态的对象,永远不会移动的话,那么我们可以将其勾选为一个静态对象,这样的话,会节省Unity的计算压力。


十一.怎么自己切图

前面我们在打开别人的图集的时候其实会发现别人的图里有一堆sprite,因为它的图片是一个图集,然而我们可以在Unity中手动把一个大图切图为一组小图,这样我们就不需要一个一个导出图片了,尤其是瓦片这样的玩意。来简单学习一下这个是怎么做的。
设置图片为图集模式

我们先准备一张简单的图片,以便于切图使用,我涂了一个简单的四种颜色的方块。这样也便于理解。


将其保存(我用的是Aseprite,一种像素画编辑器)并导入到Unity中,不过更快的方式是你可以直接把它保存到目标Unity工程的Assets文件夹中,如果未来需要修改的话,可以直接修改Unity中的源文件,在你的编辑器中保存后Unity就会直接更新结果,远不需要频繁的保存并重新导入。
导入之后,如果我们将其理解为四个方块拼成了一个大方块的话,也就是它是一个图集,那么我们可以在图片的Inspector窗口将Sprite模式改为多个。


点击应用后,它就变成一个图集了。此后我们可以点击下面的SpriteEditor来进行切图。


下面就是SpriteEditor的主界面


在这个界面里,我们可以随意的拖拽出一个矩形,矩形框选出来的部分就是一个sprite,你可以在右下角的Sprite窗口里重命名这个Sprite,细节的调整它的范围参数,以及修改它的锚点,当然,你可以直接使用这个矩形本身所具有的框选拖拽柄,这个矩形中心的圆圈则表示它的锚点位置。


一张图里可以有无数个Sprite,因为这些矩形是可以覆盖彼此的,每个都将会是一个Sprite。


切图完成后,你可以点击右上角的应用,图片就会被拆分为你框选的部分,我们重新将四个矩形框选到对应的色块上,并点击应用,这样就切图完成了,每个Sprite都将会有自己的锚点位置。


十二.如何搭建一个瓦片地图

既然我们知道了如何切图,不妨来搭建一个简单的Tilemap场景,这次我们要搭建的是TopDown视角的Tilemap,也就是俯视角的。我们采用的还是之前的那个素材包,它的素材包正好有一个瓦片图集,但是可以是任何图集,哪怕是我涂的色块,只要重新切图即可。
在这个章节里我们将认识一些新的东西

  • Tile对象和Tile调色盘
  • Grid组件
  • Tilemap组件
  • TilemapRenderer组件
Tile对象和Tile调色盘

瓦片地图不是一个普通的GameObject,所以它并不是由一个一个的GameObject构成的。组成他的最基本单位叫做Tile对象,Tile调色盘负责创建和管理所有的Tile对象以及将这些Tile对象绘制到一个具有Tilemap组件的GameObject身上去。
反过来理一下,任何一个GameObject挂载了Tilemap组件就是一个Tilemap了,然后Tile调色盘是专门在这样具有Tilemap对象身上画画的一个笔刷工具。而Tile对象就是所有能用的笔刷。
Tile调色盘可以通过窗口/2D/平铺调色盘打开。


然后我们可以点击界面中的创建新调色盘来创建一个Tile调色盘




名称可以随意,网格选择矩形,矩形的网格适用于横板平台类游戏和俯视角的游戏,这里还有Hexagon网格,也就是六边形网格,适用于一些回合制战棋策略游戏,以及Isometric视角,也就是菱形网格,此类网格适用于斜向的网格游戏,比如一些SRPG类型的游戏。


单元格大小选择自动即可,这个目前不用解释。
排序模式则选择默认,稍微注意一下就是这里的排序模式针对是Tilemap瓦片自身的遮挡关系,比如说我们在Iso视角中,遮挡就是一个大问题,我们往往是选择自定义轴,以高度来进行排序。不过对于矩形Tilemap来说,由于大家都是平铺的,本身不存在遮挡关系,之后只需要处理Tilemap和其他Sprite对象的遮挡关系即可。
创建时选择Assets文件夹内部的一个位置创建即可,注意这个调色盘自身也是一个Unity的对象,不能存于外部的位置。




之后你可以将Sprite或者整个Sprite图集拖拽到这里,如果是单个Sprite或者Sprite图集,那么它会为其创建一个或者一组新的Tile对象(也需要你选择保存位置),如果拖拽的是已经创建好的Tile对象则无事发生。下面我将自己的瓦片插入其中,它就会自动在我们选定的位置创建四个Tile对象


注意这些个是Tile对象,就不要把它们和Sprite对象或者Texture对象搞混了,建议是存在不同的文件夹中以便于管理。


创建Grid对象和Tilemap对象

调色盘有了,现在还需要画布。那么画布就是我们之前所说的Tilemap对象了,不过Tilemap对象还需要依赖Grid对象,一个Grid记录了一个无边的网格空间,里面可以有多个Tilemap对象,每个Tilemap都必须是Grid对象的子对象,否则它将不会被显示以及不产生任何作用。所以我们可以先创建一个空的GameObject,为其增加一个Grid组件。


它的参数有以下几个


单元格大小,就是每个格子的大小,这里的单位均为Unity单位。还记得Unity单位如何对应到图片像素单位吗?这与你的图集的每个Unity单位包含的像素量有关,如果这个参数设为8,那么每个Unity单位就等于8个像素,反过来说,如果你的瓦片大小是16,那么这里的Grid网格大小应该设为2,就恰好每个格子可以放下一个这样大小的瓦片。
单元格间隙,就是每个网格之间的距离,一般平铺的矩形Tilemap不需要调整间隙。
单元格布局,就是之前在Tile调色盘中讲到的布局。对应布局的调色盘用于绘制对应布局的Grid组件。
单元格重排,一般的排列就是上下左右,也就是上是上,左是左这样。但是通过这个参数可以将不同的轴对应到上下左右去。比如将上视为左,左视为上。该参数在矩形排列中不重要,正常为xyz即可。
有了这样一个Grid对象,我们就可以在它内部创建一个子对象,我们创建一个空的子对象,为其增加两个组件。即Tilemap组件和TilemapRenderer组件。右击Grid对象,然后创建对象,就可以把新创建的对象作为该Grid的子对象。


我们增加Tilemap和TilemapRenderer,然后先来看一下TilemapRenderer吧



  • 排序次序
    也就是排序方向,对于任何彼此之间不会遮挡的Tilemap体系来说,这个参数没有任何意义。如果你做的是iso视角的tilemap,则可以尝试改变这个值来观察结果。
  • 模式
    有两个,第一个是Chunk,第二个是Individual,选择Chunk的话,TilemapRenderer会将它们分组渲染,速度会更快。但是它们的排序也会整体来进行,Individual会慢很多,一个瓦片一个瓦片的渲染,有点等效于多个SpriteRenderer了,不过每个瓦片都会单独进行排序。如果不考虑瓦片之间的排序,则可以直接使用Chunk模式。
  • 检测块剔除边界
    暂时没有搞懂它的用处。
  • 遮罩交互
    指的是Tilemap与遮罩的交互关系,如果是不与遮罩纹理交互,那么它将不会和遮罩产生任何反应,如果是遮罩内部可见,那么Tilemap仅在遮罩纹理出会显示。比如可以用一个圆形遮罩做出整体感观为圆形的Tilemap可见范围。


剩下的参数包括材质、排序图层、图层顺序、遮罩层交互则完全等同于SpriteRenderer的对应参数。这里的排序图层是带着整个Tilemap所有的瓦片一起进行排序。
接着我们来看Tilemap对象



  • 动画帧速率
    如果你的瓦片带有动画效果(比如是水体瓦片),该函数可以控制其帧率。
  • 颜色
    可以整体的影响Tilemap的色彩(前提是你的材质支持)
  • 平铺锚点
    瓦片的锚点位置,(0.5,0.5)就是中心的意思,如果是(0,0)就是左下角,而(1,1)是右上角。说白了就是瓦片是一个0-1范围的小方块,你通过设定一个坐标来规定锚点在哪。如果感受不到锚点的作用,可以绘制一些瓦片并尝试改动锚点位置来观察它的变化。(ps:这个位置与图集中的每个sprite的锚点对应的话,会使得瓦片正好放置于一个网格之内,否则会产生一定的偏移)
  • 方向
    对瓦片进行偏移、旋转和缩放。默认为xy即可。
用Tile调色盘来编辑Tilemap

接着我们来了解一下如何进行编辑,首先打开调色盘,了解一些基本概念。


这里你可以点击一个瓦片,然后它会被选中,当然,你还可以框选一组瓦片,这组瓦片就会成为你的笔刷。


Unity采用了覆盖机制的笔刷,不过如果你框选了空白的部分,它并不会成为一个橡皮。只有非空白的笔刷才会覆盖。


要删除瓦片直接用橡皮即可,橡皮的大小也可以自定义。


矩形工具有点类似于油漆桶,它会将你选中的部分都涂成目标瓦片。


滴管工具就不介绍了,直接刻板印象即可。
油漆桶工具有点特殊,解释起来有点麻烦,它和普通的油漆桶最大的区别就是,Unity的Tilemap是一个动态可扩展的画布,当你就画了这些地方时,它的大小就只有这么大,当你使用油漆桶在范围内部时,它就是正常的油漆桶,如果在外部的话,它则类似于矩形工具,会连带填充+扩展画布。如果听不懂的话,试一试就知道了。


另外,当你的画布被扩展之后,你又擦掉了一部分,那么画布是不会缩小的,还是会维持那么大。选中目标Tilemap按T键唤出自由缩放控制柄之后可以看见它的范围。


倘若扩大了画布之后又希望缩小画布(程序有的时候会读取画布大小来确定瓦片地图的范围),那么可以通过点击Tilemap组件右上角的竖着的三个点,在弹出菜单的最下面选择压缩地图边界来将画布归位到当前地图所使用的画布范围。


比如我擦掉一些瓦片,会发现它的画布范围并没有变,那么这个时候我们可以通过压缩瓦片地图边界来讲画布缩小到最小范围。




最后要了解的是选择和移动工具,使用选择工具,我们可以在Tilemap上框选出一部分瓦片。然后切换成移动工具,就可以整体的拖动它了。类似于Marquee选框工具。




有了这些基础我们就可以像在Aseprite里画画一样来绘制地图了。你学废了吗?!
十三.如何导出你的工作内容

我们可以将所有的工作内容打包成一个unity包,还记得我们在Unity的素材商店里下载的那个免费的素材包吗?这个素材包的本质上就是一个打包好的素材包,里面可以包含任何内容,代码、纹理、场景、Tile对象、音频、视频等等,而且它们会保留Unity的所有配置参数。可以轻松的被导出或者导入另外的场景。
点击顶部菜单的资源/导出包即可唤出导出窗口,它会把该工程中所有的资源全部都排列出来。你只需要选择你需要导出的那些东西然后点击导出即可。


没了。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2023-1-28 13:42 | 显示全部楼层
太谢谢您这篇文章了,非常感谢
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-6-21 00:59 , Processed in 0.096544 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表