爐石的設計,最核心的內容是法術效果。 法術卡牌,無疑是法術的集中體現,但是,法術效果除了在法術卡牌之外,也不除不在。 隨從的戰吼,亡語,奧秘的揭示等等都是法術效果的體現。 法術卡牌在爐石裡面有很多種(200種),但是具體整理後,大約也只有10個種類,每個種類通過法術對象的指定方式,效果點數的不同排列組合,演化出了不同卡牌效果。 例如攻擊類的卡牌, 通過攻擊次數的不同(奧術飛彈是3次),攻擊對象不同(有的是只能攻擊隨從,有的只能攻擊英雄,有的兩者都可以), 攻擊方向不同(有的可以攻擊對方,有的是本方,有的是雙方),攻擊模式不同(有的是隨機對象,有的是全體,有的是指定),各種排列組合,獲得不同的法術效果。 綜上所述,一個法術效果的定義看上去是這樣的。 復制代碼 /// <summary> /// 描述 /// </summary> public String Description = String.Empty; /// <summary> /// 魔法效果 /// </summary> public enum AbilityEffectEnum { /// <summary> /// 未定義 /// </summary> 未定義, /// <summary> /// 攻擊類 /// </summary> 攻擊, /// <summary> /// 治療回復 /// </summary> 回復, /// <summary> /// 改變狀態 /// </summary> 狀態, /// <summary> /// 召喚 /// </summary> 召喚, /// <summary> /// 改變卡牌點數 /// </summary> 點數, /// <summary> /// 抽牌/棄牌 /// </summary> 卡牌, /// <summary> /// 變形 /// 變羊,變青蛙 /// </summary> 變形, /// <summary> /// 獲得水晶 /// </summary> 水晶, /// <summary> /// 奧秘 /// </summary> 奧秘, } /// <summary> /// 法術類型 /// </summary> public AbilityEffectEnum AbilityEffectType; /// <summary> /// 法術對象選擇模式 /// </summary> public CardUtility.TargetSelectModeEnum EffictTargetSelectMode; /// <summary> /// 法術對象選擇角色 /// </summary> public CardUtility.TargetSelectRoleEnum EffectTargetSelectRole; /// <summary> /// 法術對象選擇方向 /// </summary> public CardUtility.TargetSelectDirectEnum EffectTargetSelectDirect; /// <summary> /// /// </summary> /// <returns></returns> public Boolean IsNeedSelectTarget() { return EffictTargetSelectMode == CardUtility.TargetSelectModeEnum.指定; } /// 攻擊的時候:99表示消滅一個單位 /// 治療的時候:99表示完全回復一個單位 /// 抽牌的時候:表示抽牌的數量 /// <summary> /// 效果點數(標准) /// </summary> public int StandardEffectPoint; /// <summary> /// 效果點數(實際) /// </summary> public int ActualEffectPoint; /// <summary> /// 效果回數 /// </summary> public int StandardEffectCount; /// <summary> /// 效果回數(實際) /// </summary> public int ActualEffectCount; /// <summary> /// 附加信息 /// </summary> public String AddtionInfo; 復制代碼 同時,注意到每張法術卡牌中,可能包含兩個法術效果,所以,設計的時候,每張法術卡牌可以包含兩個效果,兩個效果之間,可以是 AND 或者 OR。 (在抉擇系卡牌的時候,兩個法術效果用OR連接。) 這裡還有一個概念,法術的原子效果: 例如奧術飛彈是進行3次打擊效果。所以,一個原子法術效果就是一次打擊。 每次打擊後,整個戰場進行清算,如果觸發奧秘事件等等,都要實時計算。 對於攻擊全體地方隨從的操作,系統也會對於每次打擊效果進行實時清算。 復制代碼 using System; using System.Collections.Generic; namespace Card.Effect { [Serializable] public class Ability { /// <summary> /// 第一定義 /// </summary> public EffectDefine FirstAbilityDefine = new EffectDefine(); /// <summary> /// 第二定義 /// </summary> public EffectDefine SecondAbilityDefine = new EffectDefine(); /// <summary> /// 第一定義 和 第二定義 的連接方式 /// </summary> public Card.CardUtility.EffectJoinType JoinType = Card.CardUtility.EffectJoinType.None; /// <summary> /// 是否需要抉擇 /// </summary> /// <returns></returns> public Boolean IsNeedSelect() { return JoinType == CardUtility.EffectJoinType.OR; } /// <summary> /// 分解獲得效果列表 /// </summary> /// <param name="IsFirstEffect">需要抉擇的時候,是否選擇第一項目</param> /// <returns>最小效果列表</returns> public List<Card.Effect.EffectDefine> GetSingleEffectList(Boolean IsFirstEffect) { //這裡都轉化為1次效果 //例如:奧術飛彈的3次工具這裡將轉為3次效果 //這樣做的原因是,每次奧術飛彈攻擊之後,必須要進行一次清算,是否有目標已經被摧毀 //如果被摧毀的話,無法攻擊這個目標了, //同時,如果出現亡語的話,亡語可能召喚出新的可攻擊目標 List<Card.Effect.EffectDefine> EffectLst = new List<Card.Effect.EffectDefine>(); if (IsNeedSelect()) { if (IsFirstEffect) { for (int i = 0; i < FirstAbilityDefine.ActualEffectCount; i++) { EffectLst.Add(FirstAbilityDefine); } } else { for (int i = 0; i < SecondAbilityDefine.ActualEffectCount; i++) { EffectLst.Add(SecondAbilityDefine); } } } else { for (int i = 0; i < FirstAbilityDefine.ActualEffectCount; i++) { EffectLst.Add(FirstAbilityDefine); } if (SecondAbilityDefine.AbilityEffectType != EffectDefine.AbilityEffectEnum.未定義) { for (int i = 0; i < SecondAbilityDefine.ActualEffectCount; i++) { EffectLst.Add(SecondAbilityDefine); } } } return EffectLst; } /// <summary> /// 初始化 /// </summary> public void Init() { if (FirstAbilityDefine != null) FirstAbilityDefine.Init(); if (SecondAbilityDefine != null) SecondAbilityDefine.Init(); } } } 復制代碼 法術的資料整理: 整個資料在整理的時候都保存為XLS文件,然後通過輔助程序,轉化為XML。 程序運行的時候,將XML反序列化成對象。 A000XXX開始的都是實際的法術卡牌。可以作為玩家的手牌 A100XXX都是輔助卡牌,用戶戰吼和亡語等等。 A200XXX都是英雄技能。奧秘計算的時候,不算本方施法,不能享受法術效果加成和施法成本的減少。 施法邏輯: 第一段代碼是施法的入口代碼。 通過 game.UseAbility施法,獲得施法的結果數組。這裡包括了法術的各個動作。這些動作將作為對方客戶端復原的法術的依據。 例如奧術飛彈的施法結果可能是這樣的 ATTACK#YOU#2#1 //對方的2號位隨從1點傷害 ATTACK#YOU#1#1 //對方的1號位隨從1點傷害 ATTACK#YOU#2#1 //對方的2號位隨從1點傷害 這些結果將發送到對方客戶端,進行戰場的同步操作。 然後觸發 本方施法事件, 例如 法術浮龍會相應這個事件,攻擊力 +1,有些奧秘會被揭示,產生效果 復制代碼 ActionCodeLst.Add(UseAbility(CardSn)); //初始化 Buff效果等等 Card.AbilityCard ablity = (Card.AbilityCard)CardUtility.GetCardInfoBySN(CardSn); ablity.CardAbility.Init(); var ResultArg = game.UseAbility(ablity, ConvertPosDirect); if (ResultArg.Count != 0) { ActionCodeLst.AddRange(ResultArg); //英雄技能的時候,不算[本方施法] A900001 幸運幣 if (CardSn.Substring(1, 1) != "2") ActionCodeLst.AddRange(game.MySelf.RoleInfo.BattleField.觸發事件(MinionCard.事件類型列表.本方施法, game)); } else { ActionCodeLst.Clear(); } 復制代碼 具體施法的代碼比較冗長和復雜: 這裡還是對於施法前的一些整理工作, 具體的施法動作,還是要交給各個 XXXXEffect處理。每個XXXXXEffect負責某種法術的施法工作。 這裡有個有趣的話題: 法術強度本意是增加法術卡的總傷。以奧術飛彈為例,法術強度+1會令奧術飛彈多1發傷害,而非單發傷害+1。法術強度不影響治療效果。 復制代碼 /// <summary> /// 使用法術 /// </summary> /// <param name="card"></param> /// <param name="ConvertPosDirect">對象方向轉換</param> public List<String> UseAbility(Card.AbilityCard card, Boolean ConvertPosDirect) { List<String> Result = new List<string>(); //法術傷害 if (MySelf.RoleInfo.BattleField.AbilityEffect != 0) { //法術強度本意是增加法術卡的總傷。以奧術飛彈為例,法術強度+1會令奧術飛彈多1發傷害,而非單發傷害+1。法術強度不影響治療效果。 switch (card.CardAbility.FirstAbilityDefine.AbilityEffectType) { case EffectDefine.AbilityEffectEnum.攻擊: if (card.CardAbility.FirstAbilityDefine.StandardEffectCount == 1) { card.CardAbility.FirstAbilityDefine.ActualEffectPoint = card.CardAbility.FirstAbilityDefine.StandardEffectPoint + MySelf.RoleInfo.BattleField.AbilityEffect; } else { card.CardAbility.FirstAbilityDefine.ActualEffectCount = card.CardAbility.FirstAbilityDefine.StandardEffectCount + MySelf.RoleInfo.BattleField.AbilityEffect; } break; case EffectDefine.AbilityEffectEnum.回復: card.CardAbility.FirstAbilityDefine.ActualEffectPoint = card.CardAbility.FirstAbilityDefine.StandardEffectPoint + MySelf.RoleInfo.BattleField.AbilityEffect; break; } if (card.CardAbility.SecondAbilityDefine.AbilityEffectType != EffectDefine.AbilityEffectEnum.未定義) { switch (card.CardAbility.SecondAbilityDefine.AbilityEffectType) { case EffectDefine.AbilityEffectEnum.攻擊: if (card.CardAbility.SecondAbilityDefine.StandardEffectCount == 1) { card.CardAbility.SecondAbilityDefine.ActualEffectPoint = card.CardAbility.SecondAbilityDefine.StandardEffectPoint + MySelf.RoleInfo.BattleField.AbilityEffect; } else { card.CardAbility.SecondAbilityDefine.ActualEffectCount = card.CardAbility.SecondAbilityDefine.StandardEffectCount + MySelf.RoleInfo.BattleField.AbilityEffect; } break; case EffectDefine.AbilityEffectEnum.回復: card.CardAbility.SecondAbilityDefine.ActualEffectPoint = card.CardAbility.SecondAbilityDefine.StandardEffectPoint + MySelf.RoleInfo.BattleField.AbilityEffect; break; } } } Card.CardUtility.PickEffect PickEffectResult = CardUtility.PickEffect.第一效果; if (card.CardAbility.IsNeedSelect()) { PickEffectResult = PickEffect(card.CardAbility.FirstAbilityDefine.Description, card.CardAbility.SecondAbilityDefine.Description); if (PickEffectResult == CardUtility.PickEffect.取消) return new List<string>(); } var SingleEffectList = card.CardAbility.GetSingleEffectList(PickEffectResult == CardUtility.PickEffect.第一效果); for (int i = 0; i < SingleEffectList.Count; i++) { Card.CardUtility.TargetPosition Pos = new CardUtility.TargetPosition(); var singleEff = SingleEffectList[i]; singleEff.StandardEffectCount = 1; if (singleEff.IsNeedSelectTarget()) { Pos = GetSelectTarget(singleEff.EffectTargetSelectDirect, singleEff.EffectTargetSelectRole, false); //取消處理 if (Pos.Postion == -1) return new List<string>(); } else { if (ConvertPosDirect) { switch (singleEff.EffectTargetSelectDirect) { case CardUtility.TargetSelectDirectEnum.本方: singleEff.EffectTargetSelectDirect = CardUtility.TargetSelectDirectEnum.對方; break; case CardUtility.TargetSelectDirectEnum.對方: singleEff.EffectTargetSelectDirect = CardUtility.TargetSelectDirectEnum.本方; break; case CardUtility.TargetSelectDirectEnum.雙方: break; default: break; } } } Result.AddRange(EffectDefine.RunSingleEffect(singleEff, this, Pos, Seed)); Seed++; //每次原子操作後進行一次清算 //將亡語效果也發送給對方 Result.AddRange(Settle()); } return Result; } /// <summary>