开发者分享使用Unity制作2D游戏的技巧 您所在的位置:网站首页 unity3d制作2d游戏 开发者分享使用Unity制作2D游戏的技巧

开发者分享使用Unity制作2D游戏的技巧

#开发者分享使用Unity制作2D游戏的技巧| 来源: 网络整理| 查看: 265

开发者分享使用Unity制作2D游戏的技巧

作者:Josh Sutphin

我们都知道,Unity是一种容易使用的跨平台3D引擎和工具,但这并不意味着我们不能使用它去创造FPS或第三人称行动冒险游戏。这两年来,我一 直在使用Unity创造一款基于精灵的2D游戏(游戏邦注:就像《征服者》和《Fail-Deadly》那样),而我将在本篇文章中描述自己在创造经典 2D外观时所使用的技巧。

本文的目标受户

我将简单地介绍自己使用Unity创造经典2D“像素图像”所使用的一些技巧。这篇文章并不是初学者的教程:我希望你已经知道如何在3D背景下使用Unity,并正在寻找如何基于该工具创造2D像素图像的指南。

精灵设置

首先需要理解的便是,尽管你正在创造一些看起来像是2D的内容,但从技巧上来看它仍是基于3D的场景。场景中的每个精灵都是具有触感的嵌块,如果是位于3D空间中就与一般模型无两样了。

perspective(from gamasutra)

perspective(from gamasutra)

你需要创造并导入一个嵌块作为你的网格。我是在Modo上进行创造,即我的建模方案选择。这只是一个简单的单面嵌块,一侧代表一个单位,并且它的面法矢量指向负Z。我同时也使用了平面UV投射而确保UV在面法中的标准化。

modo-quad(from gamasutra)

modo-quad(from gamasutra)

为什么让嵌块朝向负Z如此重要?因为你想要在Unity中设置游戏摄像机朝向负Z,所以世界XY相当于屏幕上的XY,而这就意味着嵌块需要面向相反的方向,如此它才能朝着摄像机并让人所看到。

coordinate-mapping(from gamasutra)

coordinate-mapping(from gamasutra)

你可能会好奇,是否能够只是用Unity的嵌入平面基元而取代创建自己的嵌块原型。我并不建议你们这么做,因为平面基元包含了10×10的嵌块网格,这便意味着每个精灵所需要的渲染是你真正需要的几何数量的100倍。

quad-vs-plane(from gamasutra)

quad-vs-plane(from gamasutra)

在Unity,你需要导入嵌块,然后设置一个包含MeshFilter和MeshRenderer的预制件,如此我们便能看到网格了。你可以面向不同游戏对象创造预制件,如敌人,补拍镜头,效果等等,并且就像你在创造3D图像那样,确保它们都使用了这一嵌块模型。

prefab-setup(from gamasutra)

prefab-setup(from gamasutra)

纹理集

为了创造出不同的精灵,你需要不同的纹理。最简单的方法便是面向每个精灵预制件分配不同的材料,即包含你想要看到的精灵图像,但这也含括着讨厌的性 能成本。场景中每个单独的问题都会在运行时触发一个GPU环境;如果你拥有越多独特的纹理,每一帧便需要切换更多环境,那么你的帧率将会变得越低。

你可以通过创造精灵图集而解决这一问题。这是一个在网格中包含了你的所有精灵的纹理:

sprite-atlas(from gamasutra)

sprite-atlas(from gamasutra)

每个精灵预制件都拥有相同的材料分配。你可以编写一个简单的脚本在图集中进行查找:只显示四个数字–min X,min Y,宽度,高度,然后以编程的方式设置精灵的UV去匹配矩形。以下是我所使用的UV分配代码(注意你需要在从纹理空间转向UV空间时翻转V坐标,否则你的 精灵便会上下颠倒):

Vector2[] uvs       = new Vector2[m_mesh.uv.Length]; Texture texture     = m_meshRenderer.sharedMaterial.mainTexture;

Vector2 pixelMin    = new Vector2( (float)m_currentStrand.frames[m_animFrame].x / (float)texture.width, 1.0f – ((float)m_currentStrand.frames[m_animFrame].y / (float)texture.height));

Vector2 pixelDims   = new Vector2( (float)m_currentStrand.frames[m_animFrame].width / (float)texture.width, -((float)m_currentStrand.frames[m_animFrame].height / (float)texture.height));

// main mesh { Vector2 min = pixelMin + m_textureOffset; uvs[0] = min + new Vector2(pixelDims.x * 0.0f, pixelDims.y * 1.0f); uvs[1] = min + new Vector2(pixelDims.x * 1.0f, pixelDims.y * 1.0f); uvs[2] = min + new Vector2(pixelDims.x * 0.0f, pixelDims.y * 0.0f); uvs[3] = min + new Vector2(pixelDims.x * 1.0f, pixelDims.y * 0.0f); m_mesh.uv = uvs; }

这背后的原理非常简单。UV空间代表着纹理每个维度的百分比:

uv-diagram(from gamasutra)

uv-diagram(from gamasutra)

面向特定精灵矩形计算实际UV价值总是很无聊。而因为Photoshop的信息面板呈现出了光标当前的像素坐标以及所选择对象的像素大小,所以我们能够更轻松地在像素中呈现出精灵矩形:

info-panel(from gamasutra)

info-panel(from gamasutra)

所以代码只是通过整体的纹理维度去分割像素坐标,从而获得每个轴的百分比:有效的UV坐标轴!

我的脚本不只是分配了一个静态UV设置:它同时还是一个简单的动画管理者。因为你可以使用编程方式去设置UV,所以你能够很轻松地按顺序定义不同的 UV,即定义每一帧动画,然后基于编程方式以适当的速度置换UV,从而让精灵能够动起来。我的脚本很简单,要求在面对每一帧动画链时都能手动操作像素,虽 然不可否认这么做很乏味,但是因为我未拥有大量的动画数据,所以到目前为止这种方法还是可行的。我们可以直接通过扩展编辑器去完善这一过程(虽然这超过了 本文的讨论范围),如通过在编辑器UI上的纹理中直接选择矩形。

精灵着色器

你的精灵仍然需要一个材料去引用纹理集,为此你需要一个着色器。最明显的选择便是默认的Transparent Diffuse,但即使是这一简单的着色器也能提供给你比需求更多的内容(如支持象素光照,这在传统的基于精灵的2D图像中可能不会用到)。Unlit Transparent Cutout更加简单,我编写了一个非常简单的定制精灵着色器:

// Custom sprite shader – no lighting, on/off alpha Shader “Sprite” { Properties { _MainTex (“Base (RGB) Trans (A)”, 2D) = “white” {} } SubShader { Tags {“Queue”=”Transparent” “IgnoreProjector”=”True” “RenderType”=”Transparent”} //    LOD 100 ZWrite Off Blend SrcAlpha OneMinusSrcAlpha Lighting Off Pass { SetTexture [_MainTex] { combine texture } } } }

纹理过滤

当你着眼于“像素图像”时,你会发现这便是设置精灵纹理去使用点过滤模式的标准,而不是默认的Bilinear。点过滤在源纹理中保留了硬边,从而让你的精灵看起来更好看且更整洁:

filter-modes(from gamasutra)

filter-modes(from gamasutra)

你也需要避免使用Mip Map Generation(游戏邦注:虽然mip地图会让远处的纹理看起来更清楚,但是这却只能用于3D视图中),并检查你的纹理压缩设置。如果你是面向 iOS平台,那么默认的压缩设置便是一些PVRTC,这将可能破坏像素图像。最精确,同时也属于内存密集型的设置是RGBA32。因为大多数像素图像都使 用有限的调色板,所以你可以避免RGBA16,并减少一半的纹理内存占用空间。如果你的精灵不需要alpha渠道,那就舍弃alpha组件而设置 RGB16去增加额外的内存。

摄像机设置

对于典型的2D风格,你需要使用直角摄像机。基于直角摄像机设置,任何对象不会在后退时变小。在此你可以使用Z轴作为分层机制,即在确保所有对象都排列整齐的同时去控制哪个精灵该位于最上方。

ortho-properties(from gamasutra)

ortho-properties(from gamasutra)

将摄像机放置在世界的起点(0,0,0),并朝向负Z。注意世界轴在视窗中的呈现:当你朝向负Z时,世界X相当于屏幕X(向右增加),世界Y相当于 屏幕Y(从底部向上增加)。这让我们能够很轻松地1)认为游戏是在传统的XY坐标轴上,2)在世界空间,屏幕空间以及GUI空间之间转变。

直角的规格

如果你所追求的是“像素图像”,那么摄像机的直角规格便很重要;这是在Unity上创造2D内容中较为复杂的一部分。

直角规格传达的是在摄像机投射上半部分包含了多少世界单位。举个例子来说吧,如果你设置的直角规格为5,那么视口的深度将包含10个世界空间单位。(水平范围则是基于呈现比例。)

回想你的精灵嵌块是一侧代表一个单位。这便意味着直角规格将能告诉你在一个视口中将能垂直堆叠多少个精灵(除以2)。

为了整洁地渲染像素图像,你需要确保每个精灵的像素的源纹理将在视口上的映射为1:1。你不会想略过源像素或翻倍,否则你的精灵将会变得很扭曲或“很丑”。保证1:1比例的诀窍便是确保直角规格等同于垂直屏幕分辨率除以精灵的像素高度的数值。

让我们假设你运行于960×640像素的图像中,并且你正在使用64×64的精灵。垂直屏幕像素(640)除以精灵像素高度(64)后为10,这便是能够在640像素中垂直叠加的64×64精灵数量。要记得直角规格总是只有一半高度,所以在此的直角规格为5。如下所示:

ortho-size-clean(from gamasutra)

ortho-size-clean(from gamasutra)

如果你设置了直角规格而减半或翻倍了目标,你也仍会获得可用的结果,因为精灵的直角规格将被匀称地整合到视口的垂直规格中。但是如果你未能有效地设置直角规格,你便会发现一些像素被略过或翻倍了,而最终将呈现出一些糟糕的效果:

ortho-size-dirty(from gamasutra)

ortho-size-dirty(from gamasutra)

各种分辨率

你不需要为了渲染整洁的像素图像而局限于一个固定的分辨率上。处理各种像素的最简单的方法便是在摄像机上附加一个定制脚本,在此根据当前的垂直分辨率和已知的精灵规格去设置直角规格:

// set the camera to the correct orthographic size // (so scene pixels are 1:1) s_baseOrthographicSize = Screen.height / 64.0f / 2.0f; Camera.main.orthographicSize = s_baseOrthographicSize;

虽然进行了简单的修改,但仍具有一个缺陷,即随着屏幕像素的下降,你所看到的世界将变得越来越小,精灵将占据越来越多的屏幕。这便是保持来源和屏幕 像素1:1比例的结果:比起在1920×1200世界,在640×480世界中一个64×64精灵反而占据着更多空间。而这是否是问题所在还需要依靠于你 的特定游戏的需求。

如果你希望不管屏幕分辨率如何,精灵都能保持着同样的规格,那就将直角规格设定为固定值,并且不管屏幕分辨率发生怎样的改变它都保持不变。这里所存在的缺点在于,你的精灵将不可能拥有1:1的源像素和屏幕像素比例。你可以通过减半或翻倍目标分辨率而缓解这种情况。

GUI注意事项

如果你正在使用Unity的即时模式GUI,你便能够自动调节GUI去适应当前屏幕的分辨率,即使你是使用硬编码去设置所有GUI坐标。将如下代码放置在OnGUI调用上方:

void OnGUI() { // scale the GUI to the current resolution float horizRatio = Screen.width / 1024.0f; float vertRatio = Screen.height / 768.0f; GUI.matrix = Matrix4x4.TRS( Vector3.zero, Quaternion.identity, new Vector3(horizRatio, vertRatio, 1.0f) );

你可能需要不时地在世界空间坐标和屏幕空间坐标间转换着。built-in Camera.WorldToScreenPoint和Camera.ScreenToWorldPoint功能非常适用于直角摄像机,但还有一个问题 是:它们的屏幕空间理念和GUI系统的屏幕空间理念都使用的是倒转的Y轴。

当你使用Camera.WorldToScreenPoint时,你需要回到X向右边提升,Y从底向上提升的位置上,而(0,0)处在屏幕的左上方。如果你要在世界空间和GUI空间之间转换着,你就需要倒转Y坐标:

y = Screen.height – y;

2D中的物理性

你可以推动Unity的物理模拟在2D中运行。创造一个物理对象并在上面附加一个ConfigurableJoint组件,然后设置 “ZMotion”,“Anugular XMotion”和“Angular YMotion”属性为“锁定的”。这将避免物理对象沿着Z轴向前移动,并限制其旋转只会发生在同样的轴上(如此它便不会倾斜或弯曲地呈现在屏幕上)。这 并不是Box2D,但却能发挥功效。

你必须在场景的每个物理对象上设置这种ConfigurableJoint。不幸的是,并不存在任何方法能够将整个物理模拟对象整合到二维空间里,这必须以每个对象为基础。

粒子系统

在2D中,你通常不需要采取任何特殊行动去使用粒子系统。基于预期效果,你可能希望确保Z速度总是为零。因为你正在使用直角摄像机,粒子中的任何Z移动都不是很明显。(如果你看到粒子奇怪地移动着,这便是你最先需要检查的内容。)

如果你希望粒子也能像精灵那样拥有整洁的“像素图像”,那就在ParticleRenderer组件中使用精灵着色器去分配材料。

(本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦)



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有