【AS3 Coder】任务六:人物换装(纸娃娃)系统的制作

使用框架:AS3(Flash Professional CS5.0及更高版本 + Flash Buider)
任务描述:了解人物换装系统的制作原理
难度系数:2

本章源码下载:http://www.iamsevent.com/zb_users/UPLOAD/AS3Coder6/AS3Coder6.rar

列位道友,许久不见了,前段时间因为工作比较忙,已经很久没有更新教程了,实在是抱歉,此次教程是以前一直很想写的一个题材,而且比较多人在职业生涯中都会遇到且一直以来都跟我咨询比较多的。介于此题材难度并不大,所以列位道友不必担心篇幅会很大,算是一篇快餐式的教程吧,enjoy~~

对于人物换装系统(也叫做纸娃娃系统),我想我不必过多介绍了,网上到处都是,而且特别是对于我们这些游戏开发人员来说,使用了它可以让我们的游戏人物更加丰富多彩,也是一个很好的盈利点哦~其实,做这么一套系统并不复杂,要头大的应该是美术才对,而不是我们程序,对于我们来说,只需要做好人物运动模型动画,布置好人物各部位的位置就可以了。为了更方便地做这些事情,我们需要借助Flash Professional工具一下,我想在座各位对它应该很熟悉了才对,它可是开发纯AS应用必不可少的工具哦,下面是我从《Flash Multiplayer Virtual Worlds》这本书源码里临时拉过来的一个做好了的人物运动模型

【AS3 Coder】任务六:人物换装(纸娃娃)系统的制作

额,虽然看起来不怎么英俊,但是将就一下好了,毕竟鄙人手头上的素材比较少,也不可能拿我开发过的项目资源来用……先看人物元件avatar内部的结构,一个人物是由头(head)、左手(lefthand)、右手(righthand)以及身体(body)四个主要部分组成的,至于左右脚,由于它们在本例中的外形永远不会改变(而且又这么小个,也没人会注意的啦,哈哈~),暂且无视之。我们将这四个主要部分分别做成四个元件,然后按照先后顺序依次添加到元件舞台上,保证它们之间的显示层次(身体覆盖脚与右手,左手覆盖身体,头覆盖左手),然后为它们分别取好对应的元件名就完成了咱们的部位布局工作。下图显示的是其中一个部位——头部元件的属性面板,该元件被取名为"head":

【AS3 Coder】任务六:人物换装(纸娃娃)系统的制作

别忘了给其他三个部位的元件取上相应的名字哦(我这里取的名字分别是lefthand、righthand以及body,你可以直接下载源码进行查看)。

接下来,我们看第一张图的时间轴部分,使用传统补间应该可以非常容易地做出人物闲置以及运动的模型动画,再在时间轴上为每套动作起始帧取上相应的名字,在该例中,第一帧为静止帧,该帧上写着的动作代码是

stop();

用这个动作代码可以确保人物元件avatar在被添加到舞台上时时间轴停止在第一帧,不会自动跑到第二帧上面去,因为从第二帧开始一直到第50帧都是人物闲置的动画,为第二帧取名为idle之后,若需要在任意时刻开始播放人物闲置的动画,只需要执行一句

gotoAndPlay("idle");

就可以了。在第50帧上添加的动作代码就是这句,为的是在闲置动画播放到最后一帧的时候回到闲置动画第一帧继续开始循环播放。在50帧之后是人物行走的动画部分了,原理是一样的,我就不多啰嗦了。

那么完成人物模型的构建之后就该开始我们的正题:换装。一般来说,实现换装的思路有两种,一种是整体替换人物素材,另一种则是分部位替换。第一种方式适用于超简单的换装系统,人物要么不换装,一换就TM把全身上下都脱了个干净然后换一整套新的行头。第二种比较常见,适用于复杂一点的换装系统,换一个部位的服饰不会影响到其他部位。我们今天就着重讲第二种方式。

传统做法是在Flash Professional CS工具里面一次性把可换的衣服都放在元件里面,以跳帧来实现部位外形的改变,就像下图展示的一样:

【AS3 Coder】任务六:人物换装(纸娃娃)系统的制作【AS3 Coder】任务六:人物换装(纸娃娃)系统的制作【AS3 Coder】任务六:人物换装(纸娃娃)系统的制作

我们看到,头部元件head内的时间轴上,每一帧都是一张不同的图片,所以只要控制head跳帧就可以很方便地实现人物换装,当然,你的素材必须保持位置一致才行(美术同事表示压力山大)。做好各部位的元件之后,我们设置人物元件avatar的属性,将其导出为ActionScript类,之后发布成swf文件,稍后在代码中会加载该素材。

【AS3 Coder】任务六:人物换装(纸娃娃)系统的制作

现在,让我们打开Flash Builder,新建一个项目,将之前导出的素材swf文件(本项目中该swf文件名为Avatar1.swf)放在项目目录下后我们就开始写换装系统的代码。由于大部分工作都在Flash Professional里面做好了,我们的代码量因此就削减了N多,真爽啊~下面来一起看看全部代码:

package
{
	import flash.display.Bitmap;
	import flash.display.Loader;
	import flash.display.LoaderInfo;
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.net.URLRequest;
	
	public class AvatarTest extends Sprite
	{
		private var avatar:MovieClip;
		private var currentSuit:int=1;
		private var suitNum:int = 3;
		
		public function AvatarTest()
		{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			loadAsset();
		}
		
		private function loadAsset():void
		{
			var loader:Loader = new Loader();
			loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
			loader.load( new URLRequest("Avatar1.swf") );
		}
		
		private function onComplete( e:Event ):void
		{
			var loaderInfo:LoaderInfo = e.currentTarget as LoaderInfo;
			loaderInfo.removeEventListener(Event.COMPLETE, onComplete);
			//获取Flash Professional里面导出为ActionScript的类名为avatar的元件类定义并实例化之
			var avatarClass:Class = loaderInfo.applicationDomain.getDefinition("avatar") as Class;
			avatar = new avatarClass() as MovieClip;
			addChild( avatar );
			//让人物播放闲置动画
			avatar.gotoAndPlay("idle");
			avatar.x = avatar.y = 100;

			stage.addEventListener(MouseEvent.CLICK, onClick);
		}

		
		protected function onClick(event:MouseEvent):void
		{
			if( ++currentSuit > suitNum )
			{
				currentSuit = 1;
			}
			changeSuit( currentSuit );
		}
		
		private function changeSuit( suitIndex:int ):void
		{
			if( suitIndex > 0 && suitIndex <= suitNum )
			{
				changePart("head", suitIndex);
				changePart("lefthand", suitIndex);
				changePart("righthand", suitIndex);
				changePart("body", suitIndex);
			}
		}
		
		/** 用跳帧实现的换装方法 */
		private function changePart( partName:String, partFrame:Object ):void
		{
			var part:MovieClip = avatar.getChildByName(partName) as MovieClip;
			if( part )
			{
				part.gotoAndStop(partFrame);
			}
				
		}
		
	}
}

如果你连这么简单的代码也无法理解那我真是可以奉劝你去补补基础再来玩换装系统好了。我这里是一次性把四个部位的套装都换掉了,你大可以一次只换某几个部位这样子来玩。下面给出的是这段代码的运行效果预览页面(通过点击来换装):

http://www.iamsevent.com/zb_users/UPLOAD/AS3Coder6/AvatarTest.html

如果你觉得这样做就能满足的话你可以不必看接下来的内容了,如果你还不满足,那么接下来看看更加优化的方式。

上面给出的方式虽然很省代码,制作方便,但是导致的一个问题是全部素材都放在一个swf文件里面,在加载该swf素材时会消耗异常多的时间,除非你肯定你的换装应用中会用到所有的套装,否则就用我推荐的第二种方式:运行时加载套装。使用这招会让代码变得复杂,但是比较节省流量,不会加载那些穿不上用不到的套装。

首先自然是打开Flash Professional把我们的人物元件中各部位元件编辑一下,让这些部位元件中只留一帧,删掉其他套装所占有的帧。留一帧的目的是方便我们在把四肢组合成人型的时候定位它们到正确的位置,试想,若是四肢都是透明的或者大小为0的,那么你如何把它们定位到正确的位置上去呢?

【AS3 Coder】任务六:人物换装(纸娃娃)系统的制作

由上图我们看到,各部位的时间轴上现在只留有一帧了,那么接下来我们把所有可用的套装图片都放到其部位名所对应的目录中,比如所有可用的头部套装都放在名为head的目录下面,并依次给这些套装图片编好号:head1.jpg, head2.jpg.....如下图所示:

【AS3 Coder】任务六:人物换装(纸娃娃)系统的制作

做完这些之后别忘了保存我们刚才修改过的fla文件并导出为swf(本项目中该swf文件名为Avatar2.swf),放置到项目目录中后我们一起来写代码。

package
{
	import flash.display.Bitmap;
	import flash.display.Loader;
	import flash.display.LoaderInfo;
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.net.URLRequest;
	
	public class AvatarTest extends Sprite
	{
		private var avatar:MovieClip;
		private var currentSuit:int=1;
		private var suitNum:int = 3;
		private var headPart:AvatarPartControler;
		private var bodyPart:AvatarPartControler;
		private var righthandPart:AvatarPartControler;
		private var lefthandPart:AvatarPartControler;
		
		public function AvatarTest()
		{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			loadAsset();
		}
		
		private function loadAsset():void
		{
			var loader:Loader = new Loader();
			loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
			loader.load( new URLRequest("Avatar2.swf") );
		}
		
		private function onComplete( e:Event ):void
		{
			var loaderInfo:LoaderInfo = e.currentTarget as LoaderInfo;
			loaderInfo.removeEventListener(Event.COMPLETE, onComplete);
			//获取Flash Professional里面导出为ActionScript的类名为avatar的元件类定义并实例化之
			var avatarClass:Class = loaderInfo.applicationDomain.getDefinition("avatar") as Class;
			avatar = new avatarClass() as MovieClip;
			addChild( avatar );
			//让人物播放闲置动画
			avatar.gotoAndPlay("idle");
			avatar.x = avatar.y = 100;
			
			headPart = new AvatarPartControler(avatar.getChildByName("head") as MovieClip, "head");
			bodyPart = new AvatarPartControler(avatar.getChildByName("body") as MovieClip, "body");
			righthandPart = new AvatarPartControler(avatar.getChildByName("righthand") as MovieClip, "righthand");
			lefthandPart = new AvatarPartControler(avatar.getChildByName("lefthand") as MovieClip, "lefthand");
			changeSuit( 1 );//穿上默认套装
			
			stage.addEventListener(MouseEvent.CLICK, onClick);
		}

		
		protected function onClick(event:MouseEvent):void
		{
			if( ++currentSuit > suitNum )
			{
				currentSuit = 1;
			}
			changeSuit( currentSuit );
		}
		
		private function changeSuit( suitIndex:int ):void
		{
			if( suitIndex > 0 && suitIndex <= suitNum )
			{
				headPart.changeSuit(suitIndex);
				bodyPart.changeSuit(suitIndex);
				righthandPart.changeSuit(suitIndex);
				lefthandPart.changeSuit(suitIndex);
			}
		}
		
	}
}
import flash.display.Bitmap;
import flash.display.MovieClip;
import flash.display.Sprite;

class AvatarPartControler extends Sprite
{
	private var _partName:String;
	private var _suitBMP:Bitmap = new Bitmap();
	private var _randomID:String;
	
	public function AvatarPartControler( partMC:MovieClip, partName:String )
	{
		//用我们的_suitBMP实例来替换partMC原有的子对象
		partMC.removeChildAt(0);
		partMC.addChild(_suitBMP);
		this._partName = partName;
		//为当前AvatarPartControler实例生成一个随机ID用以在使用AsstsManager进行资源加载
		//时所需的加载请求者ID
		_randomID = String(Math.random() * 100);
	}
	
	/**
	 * 改变部位着装 
	 * @param suitIndex	欲着装的套装索引。若为1,则为不着任何装束的裸身情况
	 * 
	 */	
	public function changeSuit( suitIndex:int ):void
	{
		AsstsManager.instance.addEventListener(AssetEvent.ASSET_LOAD_COMPLETE, onSuitLoadComp);
		var path:String = "assets/" + _partName + "/" + _partName + suitIndex + ".png";
		AsstsManager.instance.loadImage( path, _randomID );
	}
	
	private function onSuitLoadComp( e:AssetEvent ):void
	{
		if( e.id == _randomID )
		{
			AsstsManager.instance.removeEventListener(AssetEvent.ASSET_LOAD_COMPLETE, onSuitLoadComp);
			_suitBMP.bitmapData = e.asset;
		}
	}
}

这里我们只需要看看名为AvatarPartControler的包外类即可,它负责控制人物的某个部位的换装。在它的构造函数中需接受两个参数,第一个参数代表该 AvatarPartControler实例将控制的人物部位,这个部位其实就是我们在fla文件中已布局好的人物四肢(head,body,righthand或lefthand);第二个参数是该部位的名称,我们将以此名称去匹配某个部位的可用套装所存储的目录以及套装图片名,如头部套装的图片都保存在head目录下,且这些图片的名字都有统一的"head"前缀,那么此时第二个参数就应该传入"head"这个字符串才对,不然会找不到对于的套装图片,出现加载资源错误。在够咱函数中我们将移除该AvatarPartControler实例所控制部位元件中原有的那个显示对象,因为这个显示对象只是在制作人物模型时用来定位的而已。之后我们用一个Bitmap实例来替代移除了的那个显示对象,到时候只要改变该Bitmap实例的bitmapData属性就可以实现外形的改变了。不用担心这种用Bitmap对象替换原有显示对象的做法会影响到人物整体的闲置或走动动画中各部位会发生错位什么的问题,因为在fla中我们设计人物模型动画的时候产生缓动的是head,body...这些MovieClip对象,只要这些MovieClip对象发生了缓动,自然会带动其中的子对象一起运动,因此它们中的子对象不论是原来那个Shape对象还是后来替换它的Bitmap对象都无所谓。最后, AvatarPartControler类还提供了一个changeSuit的公共方法用来让外部控制它实现换装功能。

在这里,换装功能的实现不再是通过控制一个MovieClip对象的跳帧来实现的了,而是通过改变一个Bitmap对象的bitmapData属性来实现。我们只需要在发起换装请求的时候去加载所需要的套装图片,然后把加载得到的bitmapData换给AvatarPartControler中的Bitmap对象就可以了。为了避免资源的重复加载,我依旧使用了对象池(对此不了解的道友可以参考我之前写的资源缓存机制帖子:http://bbs.9ria.com/thread-81620-1-1.html

好了,运行该段代码的效果与之前的例子是一样的,使用该方法能尽可能地降低带宽浪费,在实际项目中用途比较大。如果你觉得用位图作为素材的话在缩放时会产生锯齿,你大可在每次更换Bitmap对象的bitmapData属性后设置一次该Bitmap对象的smoothing属性为true。

好啦,本次内容就是这些,希望列位能够喜欢,那么咱们下回再见啦~拜拜~哎哟……(撞墙了……)