炉石的设计,最核心的内容是法术效果。

法术卡牌,无疑是法术的集中体现,但是,法术效果除了在法术卡牌之外,也不除不在。

随从的战吼,亡语,奥秘的揭示等等都是法术效果的体现。

法术卡牌在炉石里面有很多种(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的管理工具我也一直在开发着。

最新界面如下:

暂时没有职业区别,英雄技能是法师的技能。

博客园管理者:能否在网站分类中增加一个游戏开发的选项,谢谢。

炉石传说 C# 开发笔记 (法术篇)相关推荐

  1. 炉石传说 C# 开发笔记

    最近在大连的同事强力推荐我玩 炉石传说,一个卡牌游戏.加上五一放一个很长很长的假期,为了磨练自己,决定尝试开发一个C#的炉石传说. 这件事情有人已经干过了,开发了一个网页版的炉石,但是貌似不能玩... ...

  2. 炉石传说 C# 开发笔记 (续)

    炉石传说山寨的工作一直在进行着,在开发过程中深深体会到,对于业务的理解和整个程序的架构的整理远比开发难得多. 在开发过程中,如果你的模型不合理,不准确,很有可能造成代码的混乱,冗余,难以维护和扩展性比 ...

  3. 炉石传说 C# 开发笔记 (初版)

    法术资料说明 1.资料的准备 从GitHub上面获得的工程里面,是没有XML卡牌资料配置的,这个是需要你自己生成的. 打开炉边处说的客户端 然后按下  卡牌资料生成 将炉石资料文件设定为 Github ...

  4. 微信小程序开发笔记 进阶篇④——getPhoneNumber 获取用户手机号码(小程序云)

    文章目录 一.前言 二.前端代码wxml 三.前端代码js 四.云函数 五.程序流程 一.前言 微信小程序开发笔记--导读 大部分微信小程序开发者都会有这样的需求:获取小程序用户的手机号码. 但是,因 ...

  5. 微信小程序开发笔记 进阶篇⑤——getPhoneNumber 获取用户手机号码(基础库 2.21.2 之前)

    文章目录 一.前言 二.前端代码wxml 三.前端代码js 四.后端java 五.程序流程 六.参考 一.前言 微信小程序开发笔记--导读 大部分微信小程序开发者都会有这样的需求:获取小程序用户的手机 ...

  6. Google Map 开发笔记——基础篇(Javascript )

    Google Map 开发笔记--基础篇 说明: 一.使用入门: 1.在您需要显示地图的 html 页面嵌入这段 script 2.地图 DOM 元素 3.初始化地图 二.地图画点.线.面 1.标记( ...

  7. 微信小程序开发笔记 进阶篇⑥——getPhoneNumber 获取用户手机号码(基础库 2.21.2 之后)

    文章目录 一.前言 二.前端代码wxml 三.前端代码js 四.后端java 五.程序流程 六.参考 一.前言 微信小程序开发笔记--导读 大部分微信小程序开发者都会有这样的需求:获取小程序用户的手机 ...

  8. 【QT开发笔记-基础篇】| 第二章 常用控件 | 2.12 表格控件 QTableWidget

    本节对应的视频讲解:B_站_链_接 QTableWidget 是 Qt 中的表格控件,可以行列的形式来展示数据 1. 属性和方法 QTableWidget 有很多属性和方法,完整的可查看帮助文档. 在 ...

  9. 【 持续更新 】Android开发笔记汇总篇,爬各种坑,敲高效代码,各种奇难杂症,有您要治的病 。

    [持续更新]Android开发笔记汇总篇,爬各种坑,敲高效代码,各种奇难杂症,有您要治的病 . 一.AndroidStudio 开发工具的那些事 . 问题 1: AndroidStudio2.2以上在 ...

最新文章

  1. silverlight与javascript交互操作
  2. JSP简单练习-使用JDOM创建xml文件
  3. React开发(208):react代码分割在嵌套组件中更新 Context
  4. 前端学习(3072):vue+element今日头条管理-删除文章失败(json-bigint)
  5. c语言程序设计 函数说课,《C语言程序设计》之函数说课课件.ppt
  6. android 中resources管理
  7. Apache Ignite(七):基于Ignite的企业级分布式并行计算
  8. 【遗传算法】求解TSP问题
  9. H3CV7交换机WEB登录设备方法
  10. 多少层楼听不见街边噪音_街边刮板
  11. 禅道登录显示用户名密码错误
  12. 可编程控制、微机接口及微机应用综合实验台
  13. 不用USBASP芯片也可用USB,纯AVR实现USB通讯:AVRUSB
  14. 2022年国家高新技术企业认定最新变化
  15. 【Mark Schmidt课件】线性代数
  16. 抖音短视频零基础能做到百万粉丝吗?国仁楠哥
  17. 使用 Chrome 开发者工具研究一个基于 Angular 开发的网站源代码
  18. 极域卸载、忘记密码及找回密码----骚操作
  19. 全球使命系列诚意之作《全球使命VR》即将上线
  20. 【图论】已知度数列情况下的简单无向图的判断方法

热门文章

  1. osg3.4.0完美嵌入到Qt(实现各种事件响应)(一)
  2. ucos系统中串口驱动
  3. Java实现Snmp
  4. 收入达11亿,自主品牌手表出货量翻倍!华米科技这3大招值得学习
  5. 多地复工复产复销,泰禾集团正在“上岸”
  6. 操作系统文件系统题库
  7. PyQT5线程:多线程(QThread),线程锁(QMutex)
  8. 像素坐标转换为世界坐标
  9. 10个免费视频通话网站与陌生人聊天
  10. 写出小说滑动翻页的效果