-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
喜欢我的博客请记住我的名字:秦元培,我的博客地址是blog.csdn.net/qinyuanpei。
转载请注明出处,本文作者:秦元培, 本文出处:http://blog.csdn.net/qinyuanpei/article/details/40452019
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei。在Unity4.3版本以后,Unity3D推出了基于Box2D的2D组件,使得Unity3D成为一个可以支持2D游戏开发的游戏引擎。在Unity3D推出这一功能之前,大家对使用Unity3D开发2D游戏已经进行了大量的研究。所以,Unity3D推出2D组件从本质上来讲并不算是一个巨大的突破,因为二维与三维的区别就在于三维比二维多了一个Z轴,如果我们将Z轴固定,那么这就是一个二维的世界。在此之前,我们普遍采用的是正交投影法,即让摄像机垂直于XY平面进行投影,这样可以利用3D引擎实现2D游戏的效果。既然现在Unity可以支持原生2D游戏开发,为什么我们不来尝试一下呢?博主之前就打算抽时间研究下Unity3D的Native2D的特性,不过因为种种原因一直没有时间来研究,那么现在正好利用这个周末来研究下吧!
一、精灵(Sprite)与精灵图集
虽然我们将Unity3D的2D特性成为Native2D,不过事实上Natvite只是相对于第三方插件而言的,从本质上Unity3D的2D仍然是属于3D的范畴。精灵(Sprite)是我们走进Native2D的第一个组件,所以的2D特性都是以这个组件作为基础。学习过2D游戏开发的朋友应该知道精灵其实就是我们在2D世界里一张贴图。好了,下面我们创建一个新的项目来演示如何使用Sprite组件吧!第一个让我们激动人心的特性是我们可以在创建项目的时候就决定一个项目是3D游戏还是2D游戏,如图,这里我们直接选择2D,因为我们今天要探索的是Unity3D的Native2D特性
进入Unity后我们将注意到Unity的工具栏上会出现一个2D/3D的选项按钮且在当前场景中2D按钮被激活,这意味着这是一个2D项目,通过切换该按钮我们可以发现,Unity3D的Native2D就是将Unity3D的Z轴固定以后的效果,所以从本质上来讲Unity3D的Native2D还是3D引擎在2D效果下的一种实现。
博主从爱给网上找了点素材,做了下面的这样一个场景:
我们注意到Sprite组件中有一个最重要的SpriteRenderer组件,该组件负责的是对Sprite的渲染,我们可以通过指定Sprite这个属性来指定的渲染的资源。我们选择其中的一个图片资源,可以看到其属性窗口:
其中TextureType用来指明贴图的类型,这里我们选择Sprite类型,因为只有这种类型的贴图才能提供给Sprite组件来使用。SpriteMod用来指定精灵是一张单个的图还是一系列图集,我们注意到这张图片是一个帧动画序列,所以我们应该选择Multiple类型。接下来,我们单击SpriteEditor按钮打开精灵编辑器,目的是将这些精灵图集分割成单个的图片。如果大家阅读过博主刚开始学习Unity3D时写过的文章,一定记得博主曾经用PhotoShop将一张帧动画序列图用切图的方式分割出来,再通过绘制贴图的方式来实现帧动画吧。这样是不是很麻烦啊?没关系,Unity3D的Nativie2D提供的精灵编辑器可以帮你快速地完成这一工作。我们打开精灵编辑器:
大家可以看到这里博主将这张图片分割成了16个图片。这里有一个技巧是可以通过Trim按钮获得大小一致的图块,因为精灵编辑器可以帮助你判断图形的边界。这样做的好处是Unity可以帮你生成16张个帧动画序列,从而你可以任意地调用某一帧动画,然而资源管理器中并不会生成相应的文件,这样可以节省项目资源的大小。如图:
好了,接下来,我们来编写脚本来展示如何使用这组精灵动画:
using UnityEngine; using System.Collections; public class SpriteScript : MonoBehaviour { //向上的精灵集合 public Sprite[] UpSprites; //向下的精灵集合 public Sprite[] DownSprites; //向左的精灵集合 public Sprite[] LeftSprites; //向右的精灵集合 public Sprite[] RightSprites; //上一次使用的精灵集合 private Sprite[] lastSprites; //当前使用的精灵集合 private Sprite[] currentSprites; //当前帧序列索引 private int index=0; //每秒帧数 private float fps=10; //当前经历时间 private float currentTime=0; //角色当前状态 private PlayerState state; //精灵渲染器 private SpriteRenderer renderer=null; void Start () { //初始化角色状态 state=PlayerState.Idle; //初始化角色精灵集合 currentSprites=UpSprites; lastSprites=currentSprites; //获取精灵渲染器 renderer=GetComponent<SpriteRenderer>(); } void FixedUpdate () { if(Input.GetAxis("Horizontal")==1){ state=PlayerState.Walk; SetSprites(RightSprites); MoveTo(new Vector2(Time.deltaTime * 2.5F,0)); }else if(Input.GetAxis("Horizontal")==-1){ state=PlayerState.Walk; SetSprites(LeftSprites); MoveTo(new Vector2(-Time.deltaTime * 2.5F,0)); }else if(Input.GetAxis("Vertical")==1){ state=PlayerState.Walk; SetSprites(UpSprites); MoveTo(new Vector2(0,Time.deltaTime * 2.5F)); }else if(Input.GetAxis("Vertical")==-1){ state=PlayerState.Walk; SetSprites(DownSprites); MoveTo(new Vector2(0,-Time.deltaTime * 2.5F)); }else if(!Input.anyKey){ state=PlayerState.Idle; } DrawSprites(currentSprites); } //角色移动 private void MoveTo(Vector2 offest) { //根据偏移量计算角色位置 float x=transform.position.x + offest.x; float y=transform.position.y + offest.y; //使用2D刚体组件来移动精灵 this.rigidbody2D.MovePosition(new Vector2(x,y)); } //设置当前精灵集合 private void SetSprites(Sprite[] sprites) { currentSprites=sprites; //如果当前精灵集合和上一次使用的精灵集合不等则表明要切换精灵集合 if(currentSprites!=lastSprites) { lastSprites=currentSprites; index=0; } } //绘制当前精灵集合 private void DrawSprites(Sprite[] sprites) { //如果角色处于站立状态则显示第一帧 if(state==PlayerState.Idle){ renderer.sprite=sprites[0]; }else{ currentTime+=Time.deltaTime; //如果当前时间大于帧动画渲染时间则需要渲染新的帧动画 if(currentTime>1/fps){ //使索引增加并将当前时间清零以便于重新计数 index+=1; currentTime=0; //使索引连续画面循环 if(index>=sprites.Length){ index=0; } } } //渲染 renderer.sprite=sprites[index]; } #region 角色状态枚举定义# enum PlayerState { Walk, Idle } #endregion }
那么,我们来看看实际的运行效果吧:
这是一个控制人物沿着上、下、左、右四个方向进行移动的动画效果,我们很容易就实现了。不过,我们代码似乎写了不少啊,那么有没有一种更好的方法呢?在Unity3D没有推出2D组件的时候,我们可以以贴图的形式来绘制一个Sprite,对于这种帧动画序列图片,我们可以通过offset来决定贴图上要显示的位置。不过这种方法似乎只对普通的贴图管用,对于Sprite无能为力。博主个人还是喜欢使用这种方式,毕竟有了精灵编辑器后,切图就是一件很简单的事情了。下面,我们再来给出一个通用的脚本,该脚本可以实现任何连续帧动画的循环播放,适合在游戏中表现相对玩家来说静止的效果,比如在游戏中飘扬的旗子、飞来飞去的小鸟等等:
using UnityEngine; using System.Collections; public class FightScript: MonoBehaviour { //序列帧动画集合 public Sprite[] Animations; //当前帧序列索引 private int index=0; //每秒帧数 public float fps=10; //当前经历时间 private float currentTime=0; //精灵渲染器 private SpriteRenderer renderer; void Start() { //获取精灵渲染器 renderer=GetComponent<SpriteRenderer>(); } void FixedUpdate () { DrawSprite(); } //提供一个外部接口以便于随时改变动画 public void SetAnimations(Sprite[] sprites,int index) { this.Animations=sprites; this.index=index; } //根据精灵集合播放动画 void DrawSprite() { currentTime+=Time.deltaTime; //如果当前时间大于帧动画渲染时间则需要渲染新的帧动画 if(currentTime>1/fps){ //使索引增加并将当前时间清零以便于重新计数 index+=1; currentTime=0; //使索引连续画面循环 if(index>=Animations.Length){ index=0; } } //渲染 renderer.sprite=Animations[index]; } }
这个脚本的特点是只要指定了一系列帧动画序列,就可以让动画从某一帧开始循环播放动画。下面,我们添加两个带有攻击效果的Sprite:
怎么样?效果还不错吧,哈哈。好了,下面我们来说说精灵图集吧!大家可以注意到随着项目的持续推进,项目中使用的图片资源会越来越多,如果不注意控制的话,整个游戏的体积会越来越大。为了解决这个问题,Unity提供了精灵图集的打包制作功能Sprite Packer。所谓精灵图集呢,其实就是把分散的图片资源集中到一张图片啊,这样可以减少图片资源的容量。这其实有点类似于NGUI里面的图集啦,博主前段时间还解包了《仙剑奇侠传四》的部分资源,对于游戏中的图片资源它同样是采用这种方式进行处理的。好了,下面我们就来看看如何在Unity3D中实现精灵打包吧!首先需要在Unity中启用Sprite Packer功能:
接下来通过Window->Sprite Packer打开Sprite Packer窗口后,然后到项目资源中选择要打包的图片资源,并将其Packing Tag设为同一个名称如enemys,这样它们将会被打包在同一张图片上。
接下来我们点击Pack,就会发现这些图片被合成在一张图片上,如果我们修改任何一张图片的Packing Tag,则这张图片会从当前图集中消失。
好了,第一部分内容到此结束,大家稍作休整,我们开始本文的第二部分:2D物理世界一样美好
二、2D物理与Box2D
提及2D游戏引擎就不能不说Box2D。Box2D是一个用于模拟2D刚体物体的C++引擎,通常作为物理引擎被用到2D游戏引擎中,因此在很多游戏引擎中都能找到它的身影,而Unity3D的Native2D就是使用了Box2D这个引擎。关于这个引擎的细节大家可以自己去了解,总之如果游戏世界缺少了碰撞,那么游戏世界未免太无趣了吧!人生就像愤怒的小鸟,当你失败的时候总有猪在笑。该面对的我们还是要去面对,就算在现实中碰壁被撞得头破血流,可是这样的人生总是值得我们去追逐的,因为寒冷寂寞的生,终究是比不上轰轰烈烈的死。好了,闲话少叙,我们下面来一起学习下Unity3D中2D物理吧。在Unity3D中,所有与2D物理相关的组件都被放到Physics 2d这个父菜单中,因此我们可以在这里找到相关的2D物理组件。Unity中提供的2D物理组件主要有三类:刚体、碰撞体、关节。目前我们只需要关注刚体和碰撞体就可以了。估计2D物理这块很多朋友会觉得无所谓吧,大不了自己写呗。博主之前和一个朋友交流,他做的一款打击感不错ARPG手游从头到尾从来没有用过碰撞,所有的逻辑几乎都是自己写的,因为2D物理实在是简单啊,只要通过计算距离就可以了。可是作为一名有节操的程序员,在深刻理解了不重复制造轮子这一内涵后,还会执着地自己写碰撞检测吗?所以我们这里就直接使用Unity提供的2D组件了。我们首先给主角添加一个刚体Rigidbody 2D组件,并勾选Fixed Anglg这是因为我们在碰撞的过程中并不需要角度的变化。同样,我们不需要重力,所以需要将Gravity Angle设为0.如图:
接下来我们分别给场景里的主角、两个战斗角色和一个NPC添加Box Collider 2D碰撞器,并编写以下脚本来分别测试碰撞器和触发器,这里需要把握一个细节,就是如果需要物体碰撞后有力的效果,则需要给该物体增加一个刚体组件,因为力的作用是相互的。下面给出脚本:
using UnityEngine; using System.Collections; public class CollisionCheck : MonoBehaviour { void OnCollisionEnter2D(Collision2D Coll2D) { if(Coll2D.gameObject.tag=="Fight Player") { Debug.Log("请离我远一点,我正在练习绝世武功!"); } } void OnTriggerEnter2D(Collider2D Coll2D) { if(Coll2D.gameObject.tag=="NPC Player") { Debug.Log(" 尽管我是一名NPC,但是我的戏份还是要有的!"); } } }
2D物理世界里的碰撞检测和3D物理世界里的碰撞检测基本一致,大家可以参考官方最新的API文档,因为国内翻译的API文档基本上都没有这一部分。好了,我们来看看运行效果吧!
好啦,今天的内容就是这样了,希望大家喜欢啊,为什么每次写完博客都这么累啊?难道是因为要查阅大量的资料吗?有不懂的地方大家可以给我留言,我尽量给大家解决,还是希望大家能关注和支持博主的博客,这样博主才有勇气一直写下去。好了,就这样啦!
每日箴言:生命中所有的挫折与伤痛,所有的经历,都是为了造就你锻炼你。不要总说岁月残忍,它其实温柔了你。
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
喜欢我的博客请记住我的名字:秦元培,我的博客地址是blog.csdn.net/qinyuanpei。
转载请注明出处,本文作者:秦元培, 本文出处:http://blog.csdn.net/qinyuanpei/article/details/40452019
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------