Silverlight 2.5D RPG游戲技巧與特效處理:(二)紙娃娃系統

作者: 深藍色右手  來源: 博客園  發布時間: 2011-02-28 14:47  閱讀: 1234 次  推薦: 0   原文鏈接   [收藏]  

  紙娃娃系統,或許大家聽起來并不陌生。早在十幾年前,當時不論是文字游戲“泥巴(Mud)”或是交友、社交網站,我們只能通過屏幕上的文字來傳達與交互信息;隨著技術不斷進步,2D/3D圖形技術高速崛起,通過在基礎模型上由客戶隨意挑選、任意更換各種造型(素材),即可打造出真正屬于“自我”獨特風格的網絡虛擬形象,QQ秀便是我們耳熟能詳的代表,更貼近真實的如(RPG)游戲及虛擬現實中的換裝/換膚系統同樣亦得益于紙娃娃機制。

  本節,我將向大家講解如何最好的實現Silverlight 2.5D網絡游戲中的紙娃娃系統,以最大程度控制性能損失為前提,將游戲資源占用最小化,綜合效果及用戶體驗最優化。

  以《Silverlight MMORPG網頁游戲開發課程(Game Lesson)一期》的源碼為基礎,我將其再一次的進行了大規模重構。

  素材來源于網絡,取《封神榜3》中的角色系統(紙娃娃系統)做示例,每個角色大致都包含3個部件:鎧甲(身體)、武器、騎乘(乘)等,而其中的騎乘道具又由2個部份組成,比如異人(弓手)的翅膀分為左右兩支;甲士(戰士)的坐騎分為前后兩半;而方士(法師)的飛劍則僅為單獨對象:

  2D/2.5D游戲中角色帶翅膀飛行要考慮左右翼與身體的層次關系,騎馬則需要考慮馬頭/馬尾與身體間的層次問題。而且武器長短,角色朝向,行為姿勢等也都可能影響到各部件的層次關系。因此,一些游戲為了簡化設計,同時又不失華麗,便誕生了比如“踏云”,“御劍”,“乘鶴”,“踩蝶”等諸多天馬行空的駕馭模式,這些乘具的共同點就是均被踩在腳上,自然而然處理起來更簡單明了。當然,如果角色是3D模型的話則無需考慮這么多層疊關系。

  鑒于以上的參考分析,在Silverlight中構造裝備紙娃娃系統框架便會輕松很多。暫時以帶翅膀的弓手為例子,依葫蘆畫瓢,我們首先新建如下幾個類:

  如圖,EquipBase乃裝備(紙娃娃)系統中的核心,所有的裝備部件類比如鎧甲(身體)Armor/武器Weapon/翅膀Wing/坐騎Ride均繼承自該類:

 
/// <summary>
/// 裝備部件基類
/// </summary>
public abstract class EquipBase : ObjectBase {

/// <summary>
/// 加載完畢
/// </summary>
public event EventHandler Ready;

/// <summary>
/// 獲取或設置部件名
/// </summary>
protected string partName { get; set; }

long index = 0; // 異步加載與換裝同步協調
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& lt;string, Point>(); //各幀偏移
/// <summary>
/// 加載配置
/// </summary>
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<XElement> 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;
/// <summary>
/// 獲取或設置是否水平翻轉
/// </summary>
public bool IsTurn {
get { return _IsTurn; }
set {
if (_IsTurn != value) {
Transform
= (_IsTurn = value) ? scaleTransform : null;
}
}
}


bool _Flash;
/// <summary>
/// 獲取或設置是否閃光
/// </summary>
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) }; // 換裝時的閃光特效計時器
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 };
/// <summary>
/// 呈現幀圖
/// </summary>
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);
}
}

  內容比較簡明:當角色需要換裝時,通過異步下載的方式獲取該裝備部件的XAP包,一旦下載完畢便將其緩存起來使用。當然,由于是異步,在整個Loading的過程中為了提高用戶體驗,我們可以在角色(Role)身上做些修飾以讓玩家一看就明白該角色正處于換裝Loading,比較有新意的做法是在角色中心添加一些描述性的文字,或使用一些旋轉類的動畫(Animation)

  另外,我還為其增加了一個名為Flash的方法,即當某個裝備部件正處于Loading過程中時,該部件將執行時隱時現的Opacity動畫,這種效果最完美了。不過,就目前的Silverlight 4 來說還無法對UIElementOpacity進行GPU硬件加速,暫時該方案的拓展與取舍/取代問題只能交由大家一同探討。

  然后是關于換裝系統中的素材資源的組織。對于像Silverlight這樣基于動態加載的游戲開發技術來說,最大程度減少質量損失前提下的資源容量高度濃縮有利于網頁游戲的動態加載,以及像Windows Phone這樣磁盤空間相對較小的移動設備平臺。以精致的2.5D網游中的角色為例,大都以8方向居多,當然我們也還是能夠僅僅使用5個方向素材即達到減少資源開支的效果(比如對其中的東北、東、東南進行水平翻轉)

  此方法以犧牲少量性能進行圖像水平翻轉為代價達到讓資源總量減少近一半,且畫質不打折扣的效果。唯一缺陷就是武器永遠處于同一只手中,無論面朝何方;不過就整體而言,這不失為大多數網頁游戲之首選。另外,對于Silverlight開發2.5D網頁游戲來說,將圖像資源PNG8化確實必要而關鍵。由于本節源碼中的素材均來源于網絡,所以效果很一般,如果是由3D美術原創的話,將逐幀圖像導出并處理成顏色過渡均勻,邊線條紋清晰流暢且無鏤空的PNG8精美素材并非難事,最終還能再一次大幅降低游戲整體資源占用及內存開銷:

  此時,大家應該有注意到本節中的資源命名規范與以往有了些變化,形如a-b-c-d.png的形式,對于鎧甲(身體)和武器來說,a代表狀態(打坐/步行/騎乘)b代表行為動作(停止/移動/攻擊/受傷)c代表朝向;d代表幀號。而對于騎乘道具,比如翅膀和坐騎,a代表行為動作;b代表對象代號(比如翅膀1/翅膀2,坐騎前半部分/坐騎后半部分)cd則與前面一致。當然,這或許僅符合我個人的思維習慣,自認為如此配置更便于理解和使用,還是那句老話,只要能給程序的編寫帶來便利,依舊是仁者見仁,智者見智,并無定論。

  當裝備類及相關資源設置完畢后,我們便可通過一個Role控件作為容器進行統一包裝管理,此時我們創建一個名為RoleBase的角色基類,游戲中一切主體生命對象均由此衍生而來,比如英雄(Hero)/怪物(Monster)/非控對象(NPC)等等:

  大伙應該會留意到,與以前編寫的結構有所不同,此時的Sprite的意義得到了更廣泛的延伸,是一次新的詮釋,它指代所有基于場景坐標系布局中的對象(映射到現實世界中即指一切活動著得對象),比方說角色(如英雄,怪物,寵物,動物,NPC,動畫,魔法等),道具(如火焰,植物,飛箭等),特效(如云霧繚繞,打雷閃電,刮風下雨,花葉紛飛)等等,我們均可將其納入“游戲精靈”的行列。外加上對角色的Coordinate(場景中的Point坐標屬性)Position(游戲畫布中的Point坐標屬性)進行了更完美的協調,于是整個游戲控件項目(Controls)重構后層次關系更趨合理,耦合度降低,重用性更高,更利于后期功能的拓展。

  最后還是得特別強調下,Silverlight游戲中盡量使用小尺寸圖片,因為圖像的尺寸越大越消耗UI線程。作者曾經嘗試過對英雄的4個部件均使用510*510尺寸的幀圖像,即精靈每動一下就會同時切換4510*510的圖片;此時同屏僅共存10個該英雄便已讓CPUFPS痛苦不堪;而如果將該4個部件的每張圖像多余的透明部分裁剪掉,即每張幀圖片均只有不到100的寬和高,然后通過TranslateTransform偏移到共同位置上,性能較之前幾乎提升了幾十個數量級,同屏1004件套精靈FPS照樣不下30,開發者們切記了:

  本節源碼下載地址:Demo1.rar

  在線演示地址:http://silverfuture.cn/

0
0
 
標簽:Silverlight
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()