C#開發WPF/Silverlight動畫及游戲系列教程(Game Course):(二十八) 經典式屬性設計及完美的物理攻擊系統
戰斗即將開始!要實現MMORPG中的攻擊系統,必須為精靈增加相關的參數及屬性,這些內容及它們之間的牽連關系設計決定著游戲的新穎度與耐玩性;就好比當年的傳奇,系統再普通不過了,但是卻因為有著恰如其分的系統參數設定與完美的世界觀定位,成就了一代不朽巨作。那麼本節開始,我將首先對精靈控件進行屬性完善,使之具傳統經典游戲中的角色屬性。
首先看下圖:
這些屬性是目前最經典的角色屬性設置(不同的游戲中,某些屬性的命名可能會不同。例如上圖中的“體質”屬性在本節示例游戲中我稱之為“體格”;而“運勢”屬性我則稱之為“運氣”等等,但意義是一樣的);我們先分析:力量、智慧、敏捷、體質、運氣這5個屬性為所有基本屬性中的根屬性,何謂根屬性?就是所有基值屬性的最底層。當角色升級時,通過手動或系統自動根據角色的職業特性對這5個根屬性進行加值,而其他所有與之相關聯的基值屬性都將同時受到影響。例如我是這樣定義這5個根屬性的:
double _VPower;
/// <summary>
/// 獲取或設置力量
/// 影響最大負重,物理攻擊力(最小 - 最大)
/// --[相關基值屬性計算公式]:
/// 最大負重 = ABase[0] + Equip[0] + Buff[0] + VPower * Coefficient[0]
/// 物理攻擊力最小值 = ABase[1] + Equip[1] + Buff[1] + VPower * Coefficient[1]
/// 物理攻擊力最大值 = ABase[2] + Equip[2] + Buff[2] + VPower * Coefficient[2]
/// </summary>
public double VPower {
get { return _VPower + Equip[16] + Buff[16]; }
set { _VPower = value; }
}
double _VAgile;
/// <summary>
/// 獲取或設置敏捷
/// 影響命中,閃避,跑速,攻速,施法速度
/// --[相關基值屬性計算公式]:
/// 命中 = ABase[3] + Equip[3] + Buff[3] + VAgile * Coefficient[3]
/// 閃避 = ABase[4] + Equip[4] + Buff[4] + VAgile * Coefficient[4]
/// 跑速 = ABase[5] - Equip[5] + Buff[5] - VAgile * Coefficient[5]
/// 物攻速度 = ABase[6] - Equip[6] + Buff[6] - VAgile * Coefficient[6]
/// 施法速度 = ABase[7] - Equip[7] + Buff[7] - VAgile * Coefficient[7]
/// </summary>
public double VAgile {
get { return _VAgile + Equip[17] + Buff[17]; }
set { _VAgile = value; }
}
double _VPhysique;
/// <summary>
/// 獲取或設置體格
/// 影響最大生命值,物理防御力,格檔率(與暴擊率相反,物理或魔法攻擊傷害減半)
/// --[相關基值屬性計算公式]:
/// 最大生命值 = ABase[8] + Equip[8] + Buff[8] + VPhysique * Coefficient[8]
/// 物理防御力 = ABase[9] + Equip[9] + Buff[9] + VPhysique * Coefficient[9]
/// 格檔率 = ABase[10] + Equip[10] + Buff[10] + VPhysique * Coefficient[10]
/// </summary>
public double VPhysique {
get { return _VPhysique + Equip[18] + Buff[18]; }
set { _VPhysique = value; }
}
double _VWisdom;
/// <summary>
/// 獲取或設置智慧
/// 影響最大魔法值,魔法防御力,魔法攻擊力(最小 - 最大)
/// --[相關基值屬性計算公式]:
/// 最大魔法值 = ABase[11] + Equip[11] + Buff[11] + VWisdom * Coefficient[11]
/// 魔法防御力 = ABase[12] + Equip[12] + Buff[12] + VWisdom * Coefficient[12]
/// 魔法攻擊力最小值 = ABase[13] + Equip[13] + Buff[13] + VWisdom * Coefficient[13]
/// 魔法攻擊力最大值 = ABase[14] + Equip[14] + Buff[14] + VWisdom * Coefficient[14]
/// </summary>
public double VWisdom {
get { return _VWisdom + Equip[19] + Buff[19]; }
set { _VWisdom = value; }
}
double _VLucky;
/// <summary>
/// 獲取或設置幸運
/// 影響暴擊率(物理或魔法攻擊加倍)及其他
/// --[相關基值屬性計算公式]:
/// 暴擊率 = ABase[15] + Equip[15] + Buff[15] + VLucky * ACoefficient[15]
/// </summary>
public double VLucky {
get { return _VLucky + Equip[20] + Buff[20]; }
set { _VLucky = value; }
}
/// <summary>
/// 獲取或設置屬性基數
/// 0-14對應基礎屬性值
/// </summary>
public double[] ABase { get; set; }
/// <summary>
/// 獲取或設置裝備加成總和
/// 0-14對應基礎屬性值
/// 15-19對應5大屬性
/// </summary>
public double[] Equip { get; set; }
/// <summary>
/// 獲取或設置加持/減持值總和
/// 0-14對應基礎屬性值
/// 15-19對應5大屬性
/// </summary>
public double[] Buff { get; set; }
/// <summary>
/// 獲取或設置屬性系數(成長率)
/// 0-14對應基礎屬性值
/// </summary>
public double[] Coefficient { get; set; }
以力量屬性VPower為例,它影響著最大負重、物理攻擊力最小值、物理攻擊力最大值這3個基值屬性,又以其中的物理攻擊力最大值為例:
/// <summary>
/// 獲取物理攻擊力最大值
/// </summary>
public double VAttackMax {
get { return ABase[2] + Equip[2] + Buff[2] + VPower * Coefficient[2]; }
}
從它的CLR構造可以看出VAttackMax = ABase[2] + Equip[2] + Buff[2] + VPower * Coefficient[2],並且最關鍵的它是只讀的(即通過多個影響其值的相關屬性值集合而成,並非通過直接賦值得到,這樣做可以輕松構建出通用且易於維護的精靈屬性系統)。其中Abase[2]為該精靈物理攻擊力最大值的原始基礎值,例如戰士的ABase[2]可以定為30,那麼魔法師的ABase[2]大約為10左右等等;Equip[2]為所有裝備的力量加成值總和,這個不用說大家也明白;Buff[2]為所有加持減持等效果的附加值;Coefficient[2]為該精靈的力量系數,例如狂戰士的Coefficient[2]可以定為10,而牧師的Coefficient[2]則約為2左右等等。那麼其他的諸如敏捷屬性VAgile,體格屬性VPhysique等等均是類似的關聯設計。
屬性定義完成後,接下來便可以著手設計精靈的物理攻擊系統。
同樣的我們首先對物理攻擊的需求進行分析,即物理攻擊的觸發、過程及結束處理:
當我們在其他精靈身上點擊鼠標左鍵後,首先進行敵對判斷,如果是敵對的則將物理攻擊對象鎖定為該精靈(敵人),然後主角向其位置跑去,當該敵人進入主角的攻擊距離(范圍)內時,主角向其發起物理攻擊,然後通過公式計算出傷害值並輸出顯示出來。這就是最典型的物理攻擊發生過程。
下面是我根據此原理過程寫的一些關鍵邏輯代碼:
步驟一,主角與對象精靈之間的敵對判斷,根據注釋並且憑借大家N年的網游PK經驗應該不難理解^_^:
/// <summary>
/// 判斷主角是否與監視對象敵對
/// </summary>
/// <param name="obj">自身精靈</param>
/// <param name="obj">對象精靈</param>
/// <returns>是/否</returns>
private bool IsOpposition(QXSpirit me, QXSpirit obj) {
//假如對象為自己則返回否
if (me == obj) {
return false;
//假如對象的PK值大於或等於7則返回是
} else if (obj.VPK >= 7) {
return true;
} else {
//根據自身的PK模式與對方的PK模式進行比較進行相應返回
switch (me.PKMode) {
case PKModes.Peace:
return false;
case PKModes.Whole:
return true;
case PKModes.GoodAndEvil:
//在善惡模式下對方為全部攻擊模式時返回是
return obj.PKMode == PKModes.Whole ? true : false;
case PKModes.Faction:
return me.VFaction != obj.VFaction ? true : false;
case PKModes.Clan:
return me.VClan != obj.VClan ? true : false;
default:
return false;
}
}
}
步驟二,跑向敵人時,只要敵人進入攻擊范圍時即發起攻擊:
/// <summary>
/// 判斷是否將要向鎖定對象發起攻擊
/// </summary>
private bool WillAttack(QXSpirit attacker, QXSpirit defenser) {
return defenser == null ? false : Super.InCircle(attacker.X, attacker.Y, defenser.X, defenser.Y, attacker.AttackRange);
}
/// <summary>
/// 判斷點是否在圓內
/// </summary>
/// <param name="targetX">目標點X坐標</param>
/// <param name="targetY">目標點Y坐標</param>
/// <param name="centerX">圓心X坐標</param>
/// <param name="centerY">圓心X坐標</param>
/// <param name="radius">圓半徑</param>
/// <returns></returns>
public static bool InCircle(double targetX, double targetY, double centerX, double centerY, double radius){
return Math.Pow(targetX - centerX, 2) + Math.Pow(targetY - centerY, 2) <= Math.Pow(radius, 2) ? true : false;
}
我設定的主角發起實質性攻擊的前提條件是:主角進入為以敵人腳底為圓心以主角攻擊距離(范圍)為半徑的圓內:
通過圓來構造攻擊范圍並實施判斷在范圍型攻擊魔法中同樣被廣泛應用到,這是後話了。
步驟三,當主角向敵人揮劍並擊中對方時,運行計算公式並顯示傷害輸出。這裡涉及到精靈動作的分幀處理(分幀處理架構的優勢在此體現得淋漓盡致),也就是根據精靈當前動作的進度來實現相應功能。舉個最簡單的例子,當精靈死亡時會運行死亡的一系列動作幀,當播放到最後一幀(精靈躺在地上)時才算真正的死亡,此後我們才會對其進行例如線程釋放、控件移除等相關處理:
本游戲中的主角攻擊動作由7幀組成,當播放到第6幀時才會實質性的對敵人產生傷害:
而這列幀在主角的動作合成圖中所處的位置為第20列,這樣,我們為每個精靈控件添加一個名為EffectiveFrame(起效幀)的屬性,當精靈動作播放到此幀時,即會啟動相應的處理:
private void Timer_Tick(object sender, EventArgs e) {
……
//如果觸動起效幀
if (FrameCounter == EffectiveFrame[(int)Action]) {
Super.DoInjure(this);
}
}
這裡的處理調用DoInjure方法進行傷害計算、輸出及值更新:
/// <summary>
/// 精靈攻擊並產生傷害及輸出
/// </summary>
/// <param name="spirit">發起攻擊精靈</param>
public static void DoInjure(QXSpirit spirit) {
//捕獲敵人精靈
QXSpirit Enemy = (spirit.Parent as Canvas).FindName(spirit.LockObject) as QXSpirit;
//產生隨機數
Random random = new Random();
//首先進行閃避判斷
if (random.Next(100) >= (spirit.VHit - Enemy.VDodge)) {
Super.ShowText(Enemy, true, true, "Avoid", "Miss", 34, Colors.SkyBlue, 2);
} else {
int Injure = 0;
if (spirit.Action == Actions.Attack) {
Injure = Convert.ToInt32(spirit.VAttackMin + random.Next((int)(spirit.VAttackMax - spirit.VAttackMin)) - Enemy.VDefense);
} else if (spirit.Action == Actions.Magic) {
Injure = Convert.ToInt32(spirit.VMagicMin + random.Next((int)(spirit.VMagicMax - spirit.VMagicMin)) - Enemy.VMagicDefense);
}
//判斷是否暴擊
if (random.Next(100) <= (spirit.VBurst - Enemy.VBlock)) {
Injure *= 2;
Super.ShowText(Enemy, true, true, "Avoid", string.Format("Burst!{0}", Injure >= 0 ? Injure.ToString() : "0"), 30, Colors.Red, 2);
} else {
Super.ShowText(Enemy, true, false, "Avoid", Injure >= 0 ? Injure.ToString() : "0", 30, Colors.Yellow, 0.5);
}
//實際產生去血效果
Enemy.VLife = Enemy.VLife < Injure ? 0 : Enemy.VLife - Injure;
}
}
本節示例代碼中我為主角賦予了一定的根屬性值從而得出65%的命中與18%的暴擊率。當向敵人發起攻擊時系統會首先進行命中判斷,沒有命中則無任何傷害;如果命中,則在此基礎上再進行暴擊(傷害加倍)判斷,否則則為普通攻擊。接著根據不同的傷害類型通過ShowText()方法(該方法顯示出來的是QXText控件的實例,即第十四節中的BorderText,這裡我重構並Open了)將傷害顯示到游戲畫面中,最後如果有命中還需要讓敵人減去相應的生命值。DoInjure()方法算是最基本的傷害處理方法,在後面的章節中還會有更多更復雜的處理,我們可以通過DoInjure()多態來實現,最終完善游戲的傷害系統。
通常的網絡游戲中,傷害值會顯示在對應的精靈頭頂,並不斷的漸隱上升,最後消失並被系統移除。本節示例游戲中我們可以輕松的在AllMove()中實現此功能:
……
if (obj is QXText) {
QXText text = obj as QXText;
if (text.Opacity <= 0) {
Remove(text);
} else {
text.Opacity -= 0.005;
text.Y -= 0.5;
}
……
每次游戲畫面刷新時,傷害字體透明度-0.005且勻速上升0.5,當透明度由1->0後,游戲便將之移除從而及時的釋放掉資源:
在攻擊的過程中,大家不妨觀察被攻擊的怪物頭上的血條及監視頭像面板中的血條是否都時時更新且保持一致,通過前面幾節的努力,這方面的擴展均是無縫的。
額外的,傷害顯示方式並非只此一種,您完全可以充分的激活自己的大腦細胞做出更多的創新。例如利用第二十六節的彩虹筆刷來美化文字;模仿JS的數字驗證碼來創造出誇張或抽象美的字體;更甚者,您完全可以將文字作成動畫封裝成控件來使用,當暴擊時給予玩家極好的視覺享受等等:
整個過程完美銜接,構建出經典又不失完美的物理攻擊系統。下一節我將對為所有精靈加入簡單AI,使精靈們更加栩栩如生,美妙的虛擬世界在向你招手,敬請關注。
出處:http://alamiye010.cnblogs.com/