Silverlight 2.5D RPG游戲技巧與特效處里:紙娃娃系統(tǒng)
紙娃娃系統(tǒng),或許大家聽起來并不陌生。隨著2D/3D圖形技術(shù)高速崛起,通過在基礎(chǔ)模型上由客戶隨意挑選、任意更換各種造型(素材),即可打造出真正屬于“自我”獨(dú)特風(fēng)格的網(wǎng)絡(luò)虛擬形象,QQ秀便是我們耳熟能詳?shù)拇恚N近真實(shí)的如(RPG)游戲及虛擬現(xiàn)實(shí)中的換裝/換膚系統(tǒng)同樣亦得益于紙娃娃機(jī)制。本文將向大家講解如何最好的實(shí)現(xiàn)Silverlight 2.5D網(wǎng)絡(luò)游戲中的紙娃娃系統(tǒng),以最大程度控制性能損失為前提,將游戲資源占用最小化,綜合效果及用戶體驗(yàn)最優(yōu)化。
素材來源于網(wǎng)絡(luò),取《封神榜3》中的角色系統(tǒng)(紙娃娃系統(tǒng))做示例,每個(gè)角色大致都包含3個(gè)部件:鎧甲(身體)、武器、騎乘(乘具)等,而其中的騎乘道具又由2個(gè)部份組成,比如異人(弓手)的翅膀分為左右兩支;甲士(戰(zhàn)士)的坐騎分為前后兩半;而方士(法師)的飛劍則僅為單獨(dú)對(duì)象:
2D/2.5D游戲中角色帶翅膀飛行要考慮左右翼與身體的層次關(guān)系,騎馬則需要考慮馬頭/馬尾與身體間的層次問題。而且武器長(zhǎng)短,角色朝向,行為姿勢(shì)等也都可能影響到各部件的層次關(guān)系。因此,一些游戲?yàn)榱撕?jiǎn)化設(shè)計(jì),同時(shí)又不失華麗,于是乎誕生了比如“踏云”,“御劍”,“乘鶴”,“踩蝶”等諸多天馬行空的駕馭模式,這些乘具的共同點(diǎn)就是均被踩在腳上,自然而然處理起來更簡(jiǎn)單明了。當(dāng)然,如果角色是3D模型的話則無需考慮這么多層疊關(guān)系。
#p#
用Silverlight編寫代碼
鑒于以上的參考分析,在Silverlight中構(gòu)造裝備紙娃娃系統(tǒng)框架便會(huì)輕松很多。暫時(shí)以帶翅膀的弓手為例子,依葫蘆畫瓢,我們首先新建如下幾個(gè)類:
如圖,EquipBase乃裝備(紙娃娃)系統(tǒng)中的核心,所有的裝備部件類比如鎧甲(身體)Armor/武器Weapon/翅膀Wing/坐騎Ride均繼承自該類:
- ///
- /// 裝備部件基類
- ///
- public abstract class EquipBase : ObjectBase {
- ///
- /// 加載完畢
- ///
- public event EventHandler Ready;
- ///
- /// 獲取或設(shè)置部件名
- ///
- protected string partName { get; set; }
- long index = 0; //異步加載與換裝同步協(xié)調(diào)
- public override int Code {
- get { return base.Code; }
- set {
- index++;
- if (value == -1) { base.Code = value; return; }
- string key = string.Format("{0}{1}", partName, value);
- if (Res.ContainsKey(key)) {
- base.Code = value;
- loadConfig(key);
- } else {
- Downloader downloader = new Downloader();
- downloader.OpenReadCompleted += new OpenReadCompletedEventHandler(webClient_OpenReadCompleted);
- downloader.OpenReadAsync(string.Format("{0}{1}.xap", partName, value), string.Format("{0},{1}", index, value), 2000);
- }
- }
- }
- void webClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e) {
- Downloader downloader = sender as Downloader;
- downloader.OpenReadCompleted -= webClient_OpenReadCompleted;
- string[] str = e.UserState.ToString().Split(',');
- if (Convert.ToInt64(str[0]) == index) {
- int code = Convert.ToInt32(str[1]);
- string key = string.Format("{0}{1}", partName, str[1]);
- if (!Res.ContainsKey(key)) { Res.Add(key, new StreamResourceInfo(e.Result as Stream, "application/binary")); }
- base.Code = code;
- loadConfig(key);
- }
- }
- Dictionary<string, Point> frameOffset = new Dictionary<string, Point>(); //各幀偏移
- ///
- /// 加載配置
- ///
- void loadConfig(string key) {
- XElement info = XElement.Load(Application.GetResourceStream(Res[key], new Uri("Info.xml", UriKind.Relative)).Stream).DescendantsAndSelf(partName).Single();
- FullName = info.Attribute("FullName").Value;
- //解析各幀偏移
- IEnumerable
iFrame = info.Element( "Frames").Elements();- frameOffset.Clear();
- foreach (XElement element in iFrame) {
- frameOffset.Add(element.Attribute("ID").Value, new Point() {
- X = (double)element.Attribute("OffsetX"),
- Y = (double)element.Attribute("OffsetY"),
- });
- }
- if (Ready != null) { Ready(this, null); }
- }
- bool _IsTurn;
- ///
- /// 獲取或設(shè)置是否水平翻轉(zhuǎn)
- ///
- public bool IsTurn {
- get { return _IsTurn; }
- set {
- if (_IsTurn != value) {
- Transform = (_IsTurn = value) ? scaleTransform : null;
- }
- }
- }
- bool _Flash;
- ///
- /// 獲取或設(shè)置是否閃光
- ///
- public bool Flash {
- get { return _Flash; }
- set {
- if (_Flash != value) {
- //if (_Flash = value) {
- // dispatcherTimer.Start();
- //} else {
- // this.Opacity = 1;
- // dispatcherTimer.Stop();
- //}
- this.Opacity = (_Flash = value) ? 0.4 : 1;
- }
- }
- }
- bool order = false;
- DispatcherTimer dispatcherTimer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(100) }; //換裝時(shí)的閃光特效計(jì)時(shí)器
- public EquipBase() {
- dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
- }
- void dispatcherTimer_Tick(object sender, EventArgs e) {
- if (order) {
- this.Opacity = this.Opacity + 0.1;
- if (this.Opacity >= 1) { order = false; }
- } else {
- this.Opacity = this.Opacity - 0.1;
- if (this.Opacity <= 0.3) { order = true; }
- }
- }
- static Dictionary<string, Stream> equipRes = new Dictionary<string, Stream>();
- ScaleTransform scaleTransform = new ScaleTransform() { ScaleX = -1 };
- ///
- /// 呈現(xiàn)幀圖
- ///
- public void Display(string key) {
- string resKey = string.Format("{0}{1}{2}", partName, Code, key);
- if (!equipRes.ContainsKey(resKey)) {
- equipRes.Add(resKey, Application.GetResourceStream(Res[string.Format("{0}{1}", partName, Code)], new Uri(string.Format("{0}.png", key), UriKind.Relative)).Stream);
- }
- this.StreamSource = equipRes[resKey];
- this.InternalOffset = frameOffset[key];
- if (IsTurn) { scaleTransform.CenterX = Center.X - frameOffset[key].X; }
- }
- public override void Dispose(object sender, EventArgs e) {
- dispatcherTimer.Stop();
- dispatcherTimer.Tick -= dispatcherTimer_Tick;
- base.Dispose(sender, e);
- }
- }
#p#
使用的方法和效果
內(nèi)容比較簡(jiǎn)明:當(dāng)角色需要換裝時(shí),通過異步下載的方式獲取該裝備部件的XAP包,一旦下載完畢便將其緩存起來使用。當(dāng)然,由于是異步,在整個(gè)Loading的過程中為了提高用戶體驗(yàn),我們可以在角色(Role)身上做些修飾以讓玩家一看就明白該角色正處于換裝Loading,比較有新意的做法是在角色中心添加一些描述性的文字,或使用一些旋轉(zhuǎn)類的動(dòng)畫(Animation):
另外,我在其中還增加了一個(gè)名為Flash的方法,即當(dāng)某個(gè)裝備部件正處于Loading過程中時(shí),該部件將執(zhí)行時(shí)隱時(shí)現(xiàn)的Opacity動(dòng)畫,這種效果最完美了。不過,就目前的Silverlight 4 來說還無法對(duì)UIElement的Opacity進(jìn)行GPU硬件加速,暫時(shí)該方案的拓展與取舍/取代問題只能交由大家一同探討。
然后是關(guān)于換裝系統(tǒng)中的素材資源的組織。對(duì)于像Silverlight這樣基于動(dòng)態(tài)加載的游戲開發(fā)技術(shù)來說,最大程度減少質(zhì)量損失前提下的資源容量高度濃縮有利于網(wǎng)頁(yè)游戲的動(dòng)態(tài)加載,以及像Windows Phone這樣磁盤空間相對(duì)較小的移動(dòng)設(shè)備平臺(tái)。以精致的2.5D網(wǎng)游中的角色為例,大都以8方向居多,當(dāng)然我們也還是能夠僅僅使用5個(gè)方向素材即達(dá)到減少資源開支的效果(比如對(duì)其中的東北、東、東南進(jìn)行水平翻轉(zhuǎn)):
此方法通過犧牲少量的性能進(jìn)行水平翻轉(zhuǎn)達(dá)到讓資源總量減少近一半,且畫質(zhì)不打折扣之效果。唯一缺陷就是武器永遠(yuǎn)處于同一只手中,無論面朝左或是右;不過就整體而言,這不失為大多數(shù)網(wǎng)頁(yè)游戲之首選。另外,對(duì)于Silverlight開發(fā)2.5D網(wǎng)頁(yè)游戲來說,將圖像資源PNG8化確實(shí)必要而關(guān)鍵。由于本節(jié)源碼中的素材均來源于網(wǎng)絡(luò),所以效果很一般,如果是由3D美術(shù)原創(chuàng)的話,將逐幀圖像導(dǎo)出并處理成顏色過渡均勻,邊線條紋清晰流暢且無鏤空的PNG8精美素材并非難事,最終將再次大幅度降低游戲整體資源及內(nèi)存占用:
此時(shí),大家應(yīng)該有注意到本節(jié)中資源的命名規(guī)范與以往有了些變化,形如a-b-c-d.png的形式,對(duì)于鎧甲(身體)和武器來說,a代表狀態(tài)(打坐/步行/騎乘);b代表行為動(dòng)作(停止/移動(dòng)/攻擊/受傷);c代表朝向;d代表幀號(hào)。而對(duì)于騎乘道具,比如翅膀和坐騎來說,a代表行為動(dòng)作;b代表對(duì)象代號(hào)(比如翅膀1/翅膀2,坐騎前半部分/坐騎后半部分);c和d則與前面一致。當(dāng)然,這或許僅能符合我個(gè)人的思維習(xí)慣,自認(rèn)為如此配置更便于理解和使用,還是那句老話,只要能給程序的編寫帶來便利,結(jié)果依舊是仁者見仁,智者見智,并無定論。
當(dāng)裝備類及相關(guān)資源設(shè)置完畢后,我們便可通過一個(gè)Role控件作為容器進(jìn)行統(tǒng)一包裝管理,此時(shí)我們創(chuàng)建一個(gè)名為RoleBase的角色基類,游戲中一切主體生命對(duì)象均由此衍生而來,比如英雄(Hero)/怪物(Monster)/非控對(duì)象(NPC)等等:
大伙是否注意到,與以前編寫的結(jié)構(gòu)有所不同,此時(shí)的Sprite的意義得到了更廣泛的延伸,是一次新的詮釋,它指代所有基于場(chǎng)景坐標(biāo)系布局中的對(duì)象(映射到現(xiàn)實(shí)世界中即指一切活動(dòng)著得對(duì)象),比方說角色(如英雄,怪物,寵物,動(dòng)物,NPC,動(dòng)畫,魔法等),道具(如火焰,植物,飛箭等),特效(如云霧繚繞,打雷閃電,刮風(fēng)下雨,花葉紛飛)等等,我們均可將其納入“游戲精靈”的行列。外加上對(duì)角色的Coordinate(場(chǎng)景中的Point坐標(biāo)屬性)和Position(游戲畫布中的Point坐標(biāo)屬性)進(jìn)行了更完美的協(xié)調(diào),于是整個(gè)游戲控件項(xiàng)目(Controls)被大規(guī)模重構(gòu),層次關(guān)系更趨合理,耦合度降低,重用性更高,更利于后期功能的拓展。
最后還得特別強(qiáng)調(diào)下,Silverlight游戲中盡量使用小尺寸圖片,因?yàn)閳D像的尺寸越大越消耗UI線程。就會(huì)可能出現(xiàn)讓CPU和FPS不堪重負(fù)的情況。
【編輯推薦】
- 2.1 紙娃娃系統(tǒng)
- 你可能不知道的15個(gè)Silverlight訣竅
- Windows Phone 7中用好Silverlight開發(fā)利器
- 微軟招聘廣告曝Silverlight應(yīng)用前景