2013年10月15日星期二

移动开发工具盘点:傻瓜式游戏工具引擎

       毫无疑问,游戏是所有App里最赚钱的一大类,但曾经在多数人看来,游戏开发是一件高深莫测的事,要懂各种编程语言,但随着技术的发展,越来越多傻瓜式游戏开发工具涌现,不懂编程的人,也可以实现自己的游戏开发梦想。
在《史上最牛独立开发者:花20美元狂赚100万美元》一文中,牛人Joe Kaufman说到,作为开发者,选择工具至关重要。但是面对市面上鱼龙混杂的开发工具与引擎,你也许会一脸茫然,而且如果你还不懂编程,又想开发移动游戏,事情就更难办了,哪些移动游戏开发工具好用呢?国外科技媒体Mobyaffiliates评选出了以下八种供大家参考。
1)Construct 22D引擎,原本是开发Windows的游戏引擎,但现在无需编程知识就可以利用一个基于事件的界面,开发iOS、Android及Facebook游戏,有70多种可视化游戏效果供你选择。更值得一提的是,它还是个很不错的HTML5游戏引擎。
图:Construct 2 logo
2)GameMakerGameMaker 是一款拥有图形界面,可灵活编程,以2D游戏设计为主的游戏开发软件。利用一个拖拽界面创建iOS及Android游戏,曾被用于开发游戏“Froad”(贪吃小怪兽)以及 “Grave Maker”(表演者)。是世界上最广泛使用的游戏开发产品,已经被下载超过1000万次。
图:用GameMaker开发游戏
3)Stencyl可视化的iOS及Flash游戏开发平台,简单易用,据说不久会支持Android及HTML5。在“场景设计”功能这个一块,还能把你设计的物体放在编辑框以外,已经升级到了Stencyl 2.0,增添了更多新功能。
图:Stencyl的游戏开发窗口
4)Multimedia Fusion 2一款十分强大的2D多媒体开发引擎,无需写任何代码就可以进行iOS、Anroid、Java及XNA游戏的开发,夸张一点说,可以在一小时之内学会基本的游戏开发技巧。所开发的著名的游戏有《尼特的故事》 、《夜空》、 《萨莉娜》、 《女忍者2:妮爱》等等。
5)GameSalad一个无需编码的游戏开发平台,提供拖放式游戏开发引擎,用户只需要想好图片和整个流程,其它的交给GameSalad处理即可。可以开发并发布Windows Phone游戏、iOS游戏、Android游戏及HTML5游戏,“Angry Anna”及Zombie Drop中就用到了该工具。还有中文版。
6)Gideros Mobile利用类似Flash的开发界面开发游戏及App,虽然不如上面的一些工具简单,但你也无须懂特别高深的编程语言,还能对已有的代码循环利用,开发好之后,意义在电脑或者其它设备上立即进行测试,无需反复导代码。
图:Gideros Mobile
7)LiveCode可利用一个叫做“直觉”的拖拽式图形界面进行快速编程,据说能在Android及iOS之间相互导数据,用的代码相比起同类软件少了90%。如果使用的是Mac,Windows或者Linux系统,可以把开发的程序任意调到任何设备上。
8)Game Editor开源游戏开发平台,支持iOS及Windows移动平台,基于事件和动作进行移动游戏开发,免费。

C# 事件和Unity3D

你知道C#有一个内置的事件机制吗?这个东东在Unity3D里也非常好用。下面举一个例子。 

为了响应一个GameObject的事件分发,你通常要建立一个脚本继承MonoBehaviour并且实现你需要的方法。比如你想对鼠标悬停作出反应,就要创建OnMouseOver方法。通常代码会像这个样子: 
  1. void OnMouseOver () {  
  2.   renderer.material.color = Color.red;  
  3. }  
这样工作没问题。但如果你想通知另外一个对象响应这个事件(OnMouseOver事件)怎么办? 

第一种方式是保持另外对象的脚本引用,然后在你的OnMouseOver方法中调用它: 
  1. public MyScript myScript;  
  2. void OnMouseOver () {  
  3.   myScript.NotifyMouseOver();  
  4. }  
这样做没问题,但是不够好。因为你需要一直保持另外一个对象的引用,如果想通知多个对象要保持多个引用。代码会变得很乱。 

Messages 消息 

另一个办法是用SendMessage或SendMessageUpwards方法。看上去这是解决问题的最好办法,但是这些方法存在严重的缺陷,以我的观点,你应该尽量不去使用它们。 

这些方法的语法并不灵活,你需要传递一个方法名字的字符串,这样做很容易出错。另外这些方法只能用在同一个对象的附属关系中。换句话说你只能在下面几种情况中调用SendMessage或SendMessageUpwards方法,这些方法的脚本被关联到同一个GameObject中,或者被关联到这个GameObject的祖先关系对象中。 

Events 事件 

幸运的是有一个更好的解决办法,这就是C#内置的事件机制。我不在这里过多的描述机制是如何工作的,你如果有兴趣可以学习相关的知识,访问MSDN手册。(译者推荐另外一篇文章,C# 中的委托和事件) 

现在让我们看看如何在Unity3D中使用事件机制。 
  1. using UnityEngine;  
  2. public class EventDispatcher : MonoBehaviour {  
  3.   public delegate void EventHandler(GameObject e);  
  4.   public event EventHandler MouseOver;  
  5.   void OnMouseOver () {  
  6.     if (MouseOver != null)  
  7.         MouseOver (this.gameObject);  
  8.   }  
  9. }  
如果你不知道这段代码到底干什么,先不要着急。重要的是一旦你把这段代码关联到一个GameObject,只要在整个项目的任何一个脚本中保持这个对象,你就可以像下面这样处理事件: 
  1. private GameObject s;  
  2. [...]  
  3. s.GetComponent<EventDispatcher>().MouseOver += Listener;  
  4. [...]  
  5. void Listener(GameObject g) {  
  6.    // g is being hovered, do something...  
  7. }  
这种方式比用消息更灵活,因为它可以被用在任何一个脚本中,而不仅仅在同一个对象附属关系中。如果在整个应用中保持一个单例模式的对象,你就可以监听任何从这个对象分发出来的事件。 

另外一个重要特点,同一个监听方法可以响应不同对象的事件。通过传递事件源对象的引用作为参数,你总会知道哪个对象分发了事件,就像我的代码展示的那样。(对于这句话可以这样理解,假如游戏中扔一颗导弹炸死了一个小兵并导致坦克减血,小兵死亡和坦克减血这两个事件都触发了同一个监听方法-玩家得分,通过传递进来的事件源对象,就能知道小兵还是坦克触发了玩家得分这个监听方法。) 

References, controllers and MVC 

现在让我们比较一下第一和第三种方式。在最开始的例子中(第一种方式保持另外对象的脚本引用),你需要在事件分发代码中保持监听者的对象引用,我说了这不是一个好主意。在用内置事件机制,改进的版本中(第三种方式),你需要在监听者代码中保持事件分发者的引用。你也许会问,为什么后者更好? 

首先,分发者不需要知道自己事件的监听者是谁,不需要知道有多少监听者。它只负责事件的发送。在最开始的例子中(第一种方式),如果要告诉分发者停止通知监听者,你能想象这种程序判断有多么笨重吗? 

事件机制中,是由监听者自己决定监听什么事件,什么时候开始监听,什么时候停止监听。像这样的对象通常用于管理程序的状态或者执行某些游戏逻辑。这个就叫做控制器,借用MVC设计模式的概念。这样我们的代码会更清晰,不易出错。(译者认为观察者设计模式更符合) 

最后一点,我喜欢重载“+=”操作符去添加监听方法。现在你也许能够猜到,如果想结束监听某个事件,可以这么写: 
  1. s.GetComponent<EventDispatcher>().MouseOver -= Listener;  

当然你可以创建一个通用的EventDispatcher类,实现所有GameObject能够分发的事件。可以参看下面的代码。另外在实现OnGUI事件时要特别小心,如果想知道为什么,读读这篇文章。 
  1. using UnityEngine;  
  2. using System.Collections;  
  3.   
  4. /**  
  5.  *  A simple event dispatcher - allows to listen to events in one GameObject from another GameObject 
  6.  * 
  7.  *  Author: Bartek Drozdz (bartek [at] everyday3d [dot] com) 
  8.  * 
  9.  *  Usage: 
  10.  *  Add this script to the object that is supposed to dispatch events.  
  11.  *  In another objects follow this pattern to register as listener at intercept events: 
  12.   
  13.     void Start () { 
  14.         EventDispatcher ev = GameObject.Find("someObject").GetComponent<EventDispatcher>(); 
  15.         ev.MouseDown += ListeningFunction; // Register the listener (and experience the beauty of overloaded operators!) 
  16.     } 
  17.  
  18.     void ListeningFunction (GameObject e) { 
  19.         e.transform.Rotate(20, 0, 0); // 'e' is the game object that dispatched the event 
  20.         e.GetComponent<EventDispatcher>().MouseDown -= ListeningFunction; // Remove the listener 
  21.     } 
  22.      
  23.  *  This class does not implement all standards events, nor does it allow dispatching custom events,  
  24.  *  but you shold have no problem adding all the other methods. 
  25.  */  
  26. public class EventDispatcher : MonoBehaviour  
  27. {  
  28.   
  29.     public delegate void EventHandler (GameObject e);  
  30.     public delegate void CollisionHandler (GameObject e, Collision c);  
  31.   
  32.     public event EventHandler MouseOver;  
  33.     void OnMouseOver ()  
  34.     {  
  35.         if (MouseOver != null)  
  36.             MouseOver (this.gameObject);  
  37.     }  
  38.   
  39.     public event EventHandler MouseDown;  
  40.     void OnMouseDown ()  
  41.     {  
  42.         if (MouseDown != null)  
  43.             MouseDown (this.gameObject);  
  44.     }  
  45.   
  46.     public event EventHandler MouseEnter;  
  47.     void OnMouseEnter ()  
  48.     {  
  49.         if (MouseEnter != null)  
  50.             MouseEnter (this.gameObject);  
  51.     }  
  52.   
  53.   
  54.     public event EventHandler MouseExit;  
  55.     void OnMouseExit ()  
  56.     {  
  57.         if (MouseExit != null)  
  58.             MouseExit (this.gameObject);  
  59.     }  
  60.   
  61.     public event EventHandler BecameVisible;  
  62.     void OnBecameVisible ()  
  63.     {  
  64.         if (BecameVisible != null)  
  65.             BecameVisible (this.gameObject);  
  66.     }  
  67.   
  68.     public event EventHandler BecameInvisible;  
  69.     void OnBecameInvisible ()  
  70.     {  
  71.         if (BecameInvisible != null)  
  72.             BecameInvisible (this.gameObject);  
  73.     }  
  74.   
  75.     public event CollisionHandler CollisionEnter;  
  76.     void OnCollisionEnter (Collision c)  
  77.     {  
  78.         if (CollisionEnter != null)  
  79.             CollisionEnter (this.gameObject, c);  
  80.     }  
  81.   
  82.     public event CollisionHandler CollisionExit;  
  83.     void OnCollisionExit (Collision c)  
  84.     {  
  85.         if (CollisionExit != null)  
  86.             CollisionExit (this.gameObject, c);  
  87.     }  
  88.       
  89. }  

Unity3d 4.0改进之处

1、Prefab系统的改进。之前Unity的Prefab虽然强大但是在数据接口和嵌套使用方面始终有混乱和不便之处,而在新的Prefab系统下,开发者可以严格控制Prefab的用户可以修改的属性。并且脚本中获取子物体里属性的方式也更加干净优雅。嵌套的Prefab也终于可以正常同步和修改实例了,对于任何游戏类型的开发都能带来巨大的效率提升。
2、Timeline,算是原来Animation系统的扩展。开发者可以使用Timeline控制任何物体或参数的线性变化,更方便的是加入了“录制”按钮,制作涉及模型、特效、声音等多种资源的动画也只需要打开录制,然后在合适的时间点添加资源到被编辑物体上就算完成了。
3、Mecanim方面,Unity4以前的版本里,系统是没有内置的动画状态控制的,完全需要开发者自己去编程来控制。Mecanim本身的核心是一套将动画重定向于不同角色骨骼的处理算法。Unity在这个基础上加入了Animation Controller这个状态控制系统,让整个动画开发流程变得非常流畅。Mecanim另外一大特性就是支持IK动画,解决了以前在Unity里开发动作和射击游戏时如何精确控制角色手部位置的老大难问题。此外IK的加入也能够让开发者跟踪角色脚踝关节的位置,并且根据踝关节位置曲线自动处理两段动画的融合衔接问题,为动画工程师省去了很大工作量。Mecanim还改进了动画资源导入工具,现在在导入资源时可以很方便地预览和分割动画,甚至根据动画层的需要直接生成可以无缝循环的动画。这个特性主要面向动作捕捉生成的动画数据。
4、Unity4的DirectX 11特性展示完全集中在了主发布会上播出的那段名叫《蝴蝶效应》的实时渲染动画短片中。包括使用Compute Shader制作和电影业使用相同工艺的爆炸效果,使用Dynamic Tessellation来渲染毛发,使用支持多层贴图的Skin Shader来创造更真实的皮肤质感等。
5、角色动画渲染方面最大的进步是支持Skinned Mesh Instancing,这个特性允许用户通过脚本访问蒙皮计算后的SkinningMesh,避免重复计算。用户还可以预先计算好一套骨骼动画里的不同Pose,并在运行时直接调用数据节约骨骼计算时间。这个功能主要的用途是允许用户同时在画面上渲染大量使用相同骨骼和动画的角色,就像主发布会上演示的一样,屏幕上有几百个人物播放相同的动画套件,而渲染时间只有几毫秒。
6、移动平台的渲染增强包括三个方面:第一是支持动态阴影,就是之前在PC平台可以使用的动态阴影效果如今在移动平台也可以享受了。第二是动态字体渲染,以前在移动平台Unity只能使用位图字体,使得中文之类字体的显示非常困难,Unity4里不光可以直接在手机上渲染高精度的动态字体,还支持HTML5标记来快速实现颜色和字体风格效果。第三个是在烘焙Lightmap时终于可以计算凹凸贴图了,让整个画面的质感提高很多。

Unity3D 常用的函数

transform.Translate --把对象从一个地方往给定的方向前进。
例如:
transform.Translate(Vector3.forward * Time.deltaTime);  // Vector3.forward相当于(0, 0, 1). Time.deltaTime见下面。
transform.position --获取对象的坐标(x, y, z)。
例如:
transform.position = Vector3(0, 0, 0); // 把对象移动到(0, 0, 0)。
gameObject.GetComponent --获得,怎么说呢,类似于返回一个指向gameObject(对象)的一个组件的指针吧。
例如:
curTransform = gameObject.GetComponent(Transform); // curTransform一个变量,后面的语句返回Transform这个组件。
GameObject.FindWithTag  --根据gameObject(对象)的标签来寻找对象。如果找到的话,就返回一个指向该对象的指针。
例如:
var respawn = GameObject.FindWithTag ("Respawn");  // var respawn是在定义一个新的变量respawn,后面的语句将返回一个指向标签为Raspawn的对象的指针。
GameObject.FindGameObjectsWithTag --根据gameObject(对象)的标签来寻找一组具有相同标签的对象。如果找到的话,就返回一个指向该对象数组的指针。
例如:
var respawns = GameObject.FindGameObjectsWithTag ("Respawn");  // var respawn是在定义一个新的变量respawn,后面的语句将返回一个指向标签为Raspawn的一组对象的指针。
Input.GetKeyDownInput.GetButtonDown  --这两者类似,当用户按下某个特定的键后,触发事件。
例如:
function Update () {
    if (Input.GetKeyDown ("space"))
        print ("space key was pressed");
} // 如果用户按下space键(空格键),则打印space key was pressed。Input.GetButtonDown类似。
Input.GetAxisInput.GetAxisRaw  --以名字来辨别虚拟键盘上的标识符。两者功能类似。后者限制更少。
例如:
var speed : float = Input.GetAxisRaw("Horizontal") * Time.deltaTime; // 接收左右或者A、D两个键的输入,控制水平方向的移动。
Time.time  --从游戏开始算起的总时间。
Time.deltaTime  --每一秒的最后一帧所用的时间。
Object.Instantiate  --克隆对象。
例如:
Instantiate (prefab, Vector3(i * 2.0, 0, 0), Quaternion.identity); // prefab是被克隆的对象, 第二个变量是克隆体生成的位置,第三个变量表示该生成的克隆不旋转。
function OnCollisionEnter(collision : Collision)  --检测碰撞,且碰撞的双方有一方为刚体。例子见官方文档。
function OnTriggerEnter (other : Collider)   --检测碰撞,且碰撞的双方有一方为刚体(另外,被装的一方还要在开启is trigger属性)。例子见官方文档。
Destroy  --摧毁对象。
例如:
Destroy (gameObject, 5); // 5秒后销毁gameObject。
Vector3  --代表一个三维坐标。
Random.Range  --在一定范围内获得随机数。
例如:
 Random.Range(-10.0, 10.0) // 获得-10.0到10.0的随机数。