用Unity创建街机游戏的一些经验分享

发表于2016-08-08
评论0 2.2k浏览
  技术相关信息
  使用引擎:unity3d
  开发语言:C#(你有别的选择吗?)
  Why Unity3D?
  为啥使用U3D?
  选择U3D有以下几条原因:
  先前有经验,学习没曲线
  多平台支持
  开发周期短
  Unity或许不是开发街机游戏的最好选择,但是我想尽快从游戏开发中获利,我有点积蓄,但是不多,因此我可没什么闲情逸致去弄个专门的游戏引擎。此外,俺的数学水平颇低,这也是我不敢搞自定义引擎的原因。(神呐!是我翻错了,还是你写错了?)
  好处
  快速搞定
  Unity非常有利于快速原型建立,无需编写自己的物理引擎,精灵动画甚至状态机,全内置了。
  编辑器
  在编辑器中进行设置超级方便,你可以快速的修改各项设置,对正在进行的工作有一个总体把握,我总会创建一堆的自定义编辑器,后面我会谈到。
  劣势
  好处很少吧,以下是原因
  不开源
  Unity的源代码不公开,也就意味着如果想在Unity中干点出格的事总是辣么难。我会把这个问题分成几部分进行讲解;(您老对Unity就这么苦大仇深吗…)
  物理引擎
  SpiritSphere有着良好的街机游戏风格,结合了老式动作RPG和空气曲棍球的玩法,Unity的2D物理引擎是基于box2D,非常适合这样一款游戏。
  物理预测:
  游戏里有一个单人模式,AI需要知道球最后的落地点以免失分,但是没有内置的方法进行物理模拟,或预测。
  最后我只好推算球的落点,尽管搞定了,但由于是建立在物理假设基础上,  最后我对预测的结果总是狐疑重重,看不到底层的处理机制就是这么悲催。
 
 

  总的来说,感觉就像是我在和Unity的物理机制干架,这赶脚不是那么良好。
  序列化
  一般来说Unity编辑器都棒,而有时对我的折磨也是永无止境。
  除了gameobject外,你还可以向编辑器中添加[System.Serializeable]属性











[System.Serializable]
 
public class Loot
 
{
    public UnityEngine.Object item;
    public Sprite thumbnail;
    public int probabillity = 1;
    public int quanity = 1;
    public int value = 1;
}











[System.Serializable]
 
public class LootTable
 
{
    private int maxProbabillity = -1;
    public bool debug = false;
 
    public List possibleDrops;
    public List<int> lootProbabbillities= new List<int>();
}int>int>
  这是一个森林关卡中草地地块的截图画面,我可以在编辑器中指定使用何种地块,不错吧?


  现在假设我想创建一个继承自Loot的新类,我先暂将其命名为SpecialLoot。
  草地物体中有一个List(Loot游戏物体的队列),在我向数组中添加SpecialLoot实例时,Unity竟然不认?SpecialLoot,是神马鬼?我只好把SpecialLoot又全部转换为普通的Loot实例,然后抛弃所有相关数据。Unity编辑器竟然不支持继承,这真是让人如坐针毡啊。
  铺地面的网格
  按照Unity的建议,铺地面的网格就是简单的游戏物体和精灵渲染器的集合。但众所周知,这是一个性能大忌,我相信Unity正在开发一个tilegrid系统,但是在他们完工之前,我只能自己写。


  Tilegrid简单来说,就是用带有许多附加顶点的方形网格做成的地块。通过设置顶点的UV坐标,我们可以指定是否显示相应的地块。
  我的tilesheet(这个词是作者拼出来的,可以参看下面的图)有16个地块宽,是我需要为每个角落设置特定的类型地块的总数,这样,我就可以根据当前地块来选择正确的UV坐标了。

float indexY = Mathf.Floor(tile.ID / 16 );
float indexX = Mathf.Floor(tile.ID - (indexY * 16 ) ); //using 16x16 tilesheet





//calculate the size of one pixel
Texture2D texture =Application.isPlaying ? (Texture2D)renderer.material.mainTexture :(Texture2D)renderer.sharedMaterial.mainTexture ;
 
float textureScale = 1 /(texture.width / (float)parent.tileSize);
float pixelSize = 1f /texture.width;









float tileLeft = (indexX *textureScale);
float tileRight = ((indexX *textureScale ) + textureScale) - seamMargin;
float tileTop = (1f - (indexY *textureScale) ) - seamMargin;
float tileBottom = (1f - ((indexY * textureScale) + textureScale ) );
 
uv[ (i * 4) + 0 ] = new Vector2(tileLeft,tileTop ); //0,0
uv[ (i * 4) + 1 ] = newVector2(tileRight, tileTop ); //1, 0
uv[ (i * 4) + 2 ] = newVector2(tileRight, tileBottom ); //1,1
uv[ (i * 4) + 3 ] = newVector2(tileLeft, tileBottom ); //0,1

  上面的代码工作的很好,为了更轻松的创建场景,我添加了一些自定义的编辑器来直接在editor中绘制tilegrid。
  我将tilegrid分块,这样当一个地块改变时,我无需调整整个的tilegrid,只要处理其中一小部分就OK了。


  但是却存在着一个小小的例外,在一个特定的摄像机位置,地块之间的接缝可以被看出来,我认为这是由于摄像机处于半像素位置时渲染不准确造成的。


  为了解决这个问题,我就得在每个地块周围挤出1个像素大小,这样,我的tilesheet就不再是2的幂了,这让我感到不甚恐惧。
后面我得看下会有多少玩家会碰到这个接缝问题,要是多的话,我就得返工一大堆的tilesheets。
  (为啥不考虑修改摄像机的移动,不要让它落在你半像素位置上呢?)
  输入
  需要讨论这个吗?unity的输入管理系统实在是太糟糕了,无法支持游戏中自定义输入控制。我有些用户报告:一个说想在键盘上玩,还有一个想用游戏杆,为此我得补写一些很讨巧的补丁来处理这些需求,我觉得还是等新的Unity输入系统出来后再说吧。目前,我就简单将用户1的键盘输入和用户2的特定键位进行映射来解决这个问题。
  优化像素显示
  我努力让像素艺术看起来更优美,通过设置摄像机的正交视图大小为(screenheight/ 2 ) / pixelsToUnit,但是在摄像机运动时游戏画面看起来就有点怪怪的(幸运的是,游戏中大部分时间摄像机都是静止的)。(又是摄像机移动的问题…)
  我可以用一个渲染材质按照其初始分辨率对整个屏幕进行渲染,然后再放大材质(这这,这是制造像素感吗?)但是这种方法也招致了一堆问题,最后我只好放弃。
  UI
  Unity的界面系统还是可以的,但就像Unity开发做的其他东西一样,感觉还是个半成品:他们希望建立界面元素根据焦点位置而自动滚动的输入系统。
  但使用时却让人赶脚相当的恶心,每次我不小心从一个UI元素上移走鼠标的时候(你不是不用鼠标吗…),游戏手柄也会失去它在UI元素上的焦点。
在启用和禁用界面元素时,被选择的项就会混在一起,结果就是两个看起来都像是被选中了一样。
  而且我无法将一些文本卷曲显示,导致游戏中至今还有大量的文本混做一团。
  游戏中唯一使用第三方资源是用于本地化的,名字叫做 LeanLocalisation,做的很棒,我写了一个扩展可以方便的从导出的excel工作表中加载数据,这插件着实让我省了不少事。
  状态机
  Unity的状态机还是很棒的,其中有大量的控制可以设置动画播放的条件。


  查看状态机,对游戏特性进行测试或裁剪还是挺有趣的,我删除了冲刺时进行攻击的技能,因为它看起来不正确,也就是状态间红色箭头的部分。
  除了动画外的状态机的其他问题
  Unity声称,状态机可以支持除了动画以外的别的工作实现。但我并不认同,动画控制器与游戏物体完全独立的,因此无法仅仅引用游戏物体上的任何属性或者组件,幸运的是,我使用了自建的消息系统,实现了这项功能。


在进入dashing状态时,俺的系统可以禁用movement组件,在退出时再次启用movement组件。相当方便。
  我还是认为如果能直接引用Dash组件是最好的,但是,好吧。
  General tips and tricks
  一些小窍门和技巧
  我想凭以上的内容足以给Unity打分了。
  我学到的就是:用它,你得包容它的怪异之处,并努力按照它的工作流程工作(有时很奇怪)。
  缓存常用组件
  因为是获取器,使用gameobject.transform相当占用CPU时间。最好为其建立缓存,这对大部分你经常要访问的组件来说是很有必要的。
  Animator.SetBool/SetInt 等函数会消耗大量CPU时间
  不是每帧都会调用这些函数的,而出于一些原因,Unity也不会检查其中的值是否已经修改,因此调用这些函数前得自己检查下。
  用单件模式,不要用静态类*
  原因嘛,Unity倾向于将事物与游戏物体关联起来,以方便其进行监听




















































GameObject projectile =ResourcePool.instance.Fetch( projectilePrefab );
 
public void Pool( GameObject obj,int maxSize = 20 )
    {
        if ( !pool.ContainsKey( obj.name ) )
        {
            pool.Add( obj.name, newList() );
        }
 
 
        if ( pool[ obj.name ].Count > 0&& maxSize != -1 && pool[ obj.name ].Count >= maxSize )
        {
            //Debug.Log( "can't fit "+ obj.name + " in pool, destroying " );
            Destroy( obj );
            return;
        }
 
 
        if ( pool[ obj.name ].Contains( obj ) )return;
        obj.SetActive( false );
 
 
#if UNITY_EDITOR
        obj.transform.SetParent(gameObject.transform );
#endif
 
 
    }
 
 
    public GameObject Fetch( GameObject ob )
   {
        if ( pool.ContainsKey( ob.name ) ==false || pool[ ob.name ].Count < 1 )
        {
            GameObject clone =GameObject.Instantiate( ob ) as GameObject;
            clone.name = ob.name;
            return clone;
        }
        else
        {
            GameObject obj = pool[ ob.name ][pool[ ob.name ].Count - 1 ];
            int instanceID =obj.GetInstanceID();
            pool[ ob.name ].Remove( obj );
 
            obj.SetActive( true );
 
#if UNITY_EDITOR
            obj.transform.SetParent( null );
#endif
 
            return obj;
        }
        return null;
    }

  创建自己的定制编辑器
  通过建立tilegrid编辑器为我节省了不少时间,看起来编写编辑器工作量不小,但之后它也会为你节省大量的时间。
  为啥下一个项目我还是坚持使用Unity?(这个问题问的好啊)
  尽管我洋洋洒洒写了10多页,痛陈Unity的不是,但是它仍然是最好的项目实现工具,并支持一大堆不同平台,缩短开发周期对我来说十分重要,毕竟我不能将一整天时间都花在电脑前。
  此外,如果可以的话,我也想在下一个项目尝试下别的平台(特别是Monogame).当然,只要乐意,我还可以重回Unity的怀抱。(你个朝三暮四的汉子 ;))
  像素(编程员)艺术
  我不是艺术设计师,但是我自个为Spiritsphere完成了所有的艺术创作,我想对那些雇不起或者不想雇艺设的程序员同僚们提点建议:
  我能给的最好的建议就是一步一步来:开始别尝试太高的分辨率,从8x8的像素精灵开始,慢慢提高。
  当然,有几百万的颜色需要你调配,这蛮吓人的,使用调色板吧,我自己用的是NES色板,此外每个精灵最多限用3种颜色(很少有例外)。
 
 
  简单也能上档次!
  营销
  很幸运,有人愿意帮我做市场推广,这使得我在发布前两周还可以放松心情关注些别的东东。不过游戏刚刚发布,根据至今我对发行商的了解,也提出一些小的建议:
  对于Key,别太慷慨
  即使是对发行商,也别太大方,我散了一些Key给评测人员,但是给出的测评和试用都太差了,抹黑了俺的游戏,他们对游戏报以肯定的态度,但是他们制作的测评内容却相当差劲,所以别太不贪婪(注意不字哦,很多程序员都不会谈价,所以我就这么翻了),花点时间去了解下那些申请测试key的人。
  别害怕使用掣肘
  SpiritSphere是一个本地的多人游戏,即使这在发布的第一句话就予以强调,但是发行商还是贴出了大量单用户的测评和视频,让玩家对游戏产生了不正确的理解,在完整版发布时,我也会对注意好对多玩家支持的宣传。
  总结
  长话短说,Unity对快速实现游戏是非常棒的,但也别指望能随心所欲的使用它。



如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引