天天看點

【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》這本書源碼裡臨時拉過來的一個做好了的人物運動模型

額,雖然看起來不怎麼英俊,但是将就一下好了,畢竟鄙人手頭上的素材比較少,也不可能拿我開發過的項目資源來用……先看人物元件avatar内部的結構,一個人物是由頭(head)、左手(lefthand)、右手(righthand)以及身體(body)四個主要部分組成的,至于左右腳,由于它們在本例中的外形永遠不會改變(而且又這麼小個,也沒人會注意的啦,哈哈~),暫且無視之。我們将這四個主要部分分别做成四個元件,然後按照先後順序依次添加到元件舞台上,保證它們之間的顯示層次(身體覆寫腳與右手,左手覆寫身體,頭覆寫左手),然後為它們分别取好對應的元件名就完成了咱們的部位布局工作。下圖顯示的是其中一個部位——頭部元件的屬性面闆,該元件被取名為"head":

别忘了給其他三個部位的元件取上相應的名字哦(我這裡取的名字分别是lefthand、righthand以及body,你可以直接下載下傳源碼進行檢視)。

接下來,我們看第一張圖的時間軸部分,使用傳統補間應該可以非常容易地做出人物閑置以及運動的模型動畫,再在時間軸上為每套動作起始幀取上相應的名字,在該例中,第一幀為靜止幀,該幀上寫着的動作代碼是

stop();
      

用這個動作代碼可以確定人物元件avatar在被添加到舞台上時時間軸停止在第一幀,不會自動跑到第二幀上面去,因為從第二幀開始一直到第50幀都是人物閑置的動畫,為第二幀取名為idle之後,若需要在任意時刻開始播放人物閑置的動畫,隻需要執行一句

gotoAndPlay("idle");
      

就可以了。在第50幀上添加的動作代碼就是這句,為的是在閑置動畫播放到最後一幀的時候回到閑置動畫第一幀繼續開始循環播放。在50幀之後是人物行走的動畫部分了,原理是一樣的,我就不多啰嗦了。

那麼完成人物模型的建構之後就該開始我們的正題:換裝。一般來說,實作換裝的思路有兩種,一種是整體替換人物素材,另一種則是分部位替換。第一種方式适用于超簡單的換裝系統,人物要麼不換裝,一換就TM把全身上下都脫了個幹淨然後換一整套新的行頭。第二種比較常見,适用于複雜一點的換裝系統,換一個部位的服飾不會影響到其他部位。我們今天就着重講第二種方式。

傳統做法是在Flash Professional CS工具裡面一次性把可換的衣服都放在元件裡面,以跳幀來實作部位外形的改變,就像下圖展示的一樣:

我們看到,頭部元件head内的時間軸上,每一幀都是一張不同的圖檔,是以隻要控制head跳幀就可以很友善地實作人物換裝,當然,你的素材必須保持位置一緻才行(美術同僚表示壓力山大)。做好各部位的元件之後,我們設定人物元件avatar的屬性,将其導出為ActionScript類,之後釋出成swf檔案,稍後在代碼中會加載該素材。

現在,讓我們打開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的,那麼你如何把它們定位到正确的位置上去呢?

由上圖我們看到,各部位的時間軸上現在隻留有一幀了,那麼接下來我們把所有可用的套裝圖檔都放到其部位名所對應的目錄中,比如所有可用的頭部套裝都放在名為head的目錄下面,并依次給這些套裝圖檔編好号:head1.jpg, head2.jpg.....如下圖所示:

做完這些之後别忘了儲存我們剛才修改過的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。

好啦,本次内容就是這些,希望列位能夠喜歡,那麼咱們下回再見啦~拜拜~哎喲……(撞牆了……)

【AS3 Coder】任務六:人物換裝(紙娃娃)系統的制作