天天看點

一起談.NET技術,Silverlight 2.5D RPG遊戲技巧與特效處理:(二)紙娃娃系統

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

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

一起談.NET技術,Silverlight 2.5D RPG遊戲技巧與特效處理:(二)紙娃娃系統

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

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

一起談.NET技術,Silverlight 2.5D RPG遊戲技巧與特效處理:(二)紙娃娃系統

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

/// <summary>

/// 裝備部件基類

/// </summary>

public abstract class EquipBase : ObjectBase {

/// 加載完畢

public event EventHandler Ready;

/// 擷取或設定部件名

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;

Dictionary<string, Point> frameOffset = new Dictionary& lt;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<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;

/// 擷取或設定是否水準翻轉

public bool IsTurn {

get { return _IsTurn; }

if (_IsTurn != value) {

Transform = (_IsTurn = value) ? scaleTransform : null;

bool _Flash;

/// 擷取或設定是否閃光

public bool Flash {

get { return _Flash; }

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; }

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 };

/// 呈現幀圖

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):

一起談.NET技術,Silverlight 2.5D RPG遊戲技巧與特效處理:(二)紙娃娃系統

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

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

一起談.NET技術,Silverlight 2.5D RPG遊戲技巧與特效處理:(二)紙娃娃系統

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

一起談.NET技術,Silverlight 2.5D RPG遊戲技巧與特效處理:(二)紙娃娃系統

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

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

一起談.NET技術,Silverlight 2.5D RPG遊戲技巧與特效處理:(二)紙娃娃系統

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

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

一起談.NET技術,Silverlight 2.5D RPG遊戲技巧與特效處理:(二)紙娃娃系統
一起談.NET技術,Silverlight 2.5D RPG遊戲技巧與特效處理:(二)紙娃娃系統

繼續閱讀