爐石傳說 C# 開發筆記 (法術篇)


爐石的設計,最核心的內容是法術效果。

法術卡牌,無疑是法術的集中體現,但是,法術效果除了在法術卡牌之外,也不除不在。

隨從的戰吼,亡語,奧秘的揭示等等都是法術效果的體現。

法術卡牌在爐石里面有很多種(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>

 

源代碼已經整理過了,去除了不需要的項目。

注意:以前文章中出現過的Git已經變更過了,請以前關注過,Fork過的朋友,重新Fork一下。

GitHub地址

從五月份到現在,都是一個人獨自開發。得到了很多網友的支持和建議。

接下來,我想是不是有擅長界面和爐石的朋友來幫我開發Window Form的界面。

我的想法是,先做一個Windows/Ubutun 的版本,在這個版本成熟的基礎上考慮 Android版本。

當然,如果你有想法,將魔獸主題的游戲改為 三國主題的游戲,可以發送游戲策划給我,這樣能避免版權的問題。

游戲的玩法沒有版權問題,但是使用的圖片和文字描述,確實有版權問題。

如果你有興趣,請留下電子郵件,以后我想通過電子郵件進行聯系。IM可能有些浪費時間。

或者上海的朋友,真的有興趣靠這個創業,可以留下聯系方式,我們可以一起喝咖啡。聊聊計划。

(不是上海的朋友也歡迎,不過,有些事情當面聊天效果最好,電話聯系也可以)

服務器數據庫,我打算使用MongoDB,本人的MongoDB水平,應該在園子里面算好的了。

MongoDB的管理工具我也一直在開發着。

最新界面如下:

暫時沒有職業區別,英雄技能是法師的技能。

 

博客園管理者:能否在網站分類中增加一個游戲開發的選項,謝謝。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM