炉石传说 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的管理工具我也一直在开发着。

最新界面如下:

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

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

时间: 2024-10-11 03:52:59

炉石传说 C# 开发笔记 (法术篇)的相关文章

炉石传说 C# 开发笔记

最近在大连的同事强力推荐我玩 炉石传说,一个卡牌游戏.加上五一放一个很长很长的假期,为了磨练自己,决定尝试开发一个C#的炉石传说. 这件事情有人已经干过了,开发了一个网页版的炉石,但是貌似不能玩.... http://cnodejs.org/topic/529c1366a6957a0809485f3d 如果这位同志看到这篇文章,请一定和我联系!! [email protected]或Q我377372779 第一天 开始学习炉石传说的玩法,最好的方法是不停的玩游戏. 一个应用是否逻辑清晰,取决于你

炉石传说 C# 开发笔记(BS模式Demo)

原来这个项目,一直想做成CS模式的,BS模式对于炉石这样的游戏来说比较困难. 暴雪到现在也只出了 Windows 和 iPad版本的炉石,最大的问题还是在于如何在小屏幕下,实现最佳的客户体验. Windows和iPad的屏幕不会太小,所以操作起来不会出现很大的问题,但是如果是手机的话,对于操作的客户体验是一个巨大的挑战. iOs系统还可以限制iPad上运行,不能在iPhone上运行.Android的话,估计很难区别到底是Pad还是Phone. (iPhone通过特殊手段还是可以运行炉石的) 如果

炉石传说 C# 开发笔记(6月底小结)

炉石传说的开发,已经有30个工作日了. 关于法术的定义方法,有过一次重大的变更:法术效果是整个炉石的核心,正是因为丰富的法术效果,才造就了炉石的可玩性. 原来构思的时候,对于法术效果没有充分的理解,所以只将效果数据做成了常数,例如 造成5点伤害. 随着更加深入的解除,发现还有 毁掉你的武器,对所有随从造成武器攻击力的伤害,这样的话,效果是一个 表达式. 然后考虑到,有些追加效果,例如,对某个随从造成2点伤害,如果这个随从没有死,则抽一张牌, 这里就牵涉到了根据条件追加效果的处理. 同时,德鲁伊的

炉石传说 C# 开发笔记(BS上线尝试)

昨天买了一个月的1G 1核的服务器,由于不是新用户,所以没有享受到阿里的6个月免费的优惠. (阿里脑残,为什么不对于续费或者升级免费呢?) 在服务器的通讯上面已经OK了,完全绕过了ASP.NET,就是单纯的服务器和浏览器在通讯,页面也只是静态的页面,纯HTML5 + JS. 通讯是WebSocket,绘图SVG.不知道为什么,对于固定的文字,UC的文字绘制是OK的,对于JSON反序列化的对象,所有的绘制都是有问题的. 初步怀疑是由于使用了汉字,然后没有设置编码,造成了JS的本地反序列化不对. 服

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

炉石传说山寨的工作一直在进行着,在开发过程中深深体会到,对于业务的理解和整个程序的架构的整理远比开发难得多. 在开发过程中,如果你的模型不合理,不准确,很有可能造成代码的混乱,冗余,难以维护和扩展性比较差等问题. 当然,除去领域专家之外,很少人对于一个新的事物可以在一开始就把握住整个核心业务. 接下来讲讲整个程序的构造: Card类库:将整个业务逻辑封装在里面,包括了服务器和客户端的通信逻辑.通信协议的编码和解码.现在是为了炉石定制的,以后想改写成更加通用的. CardHelper:一个辅助程序

炉石传说 C# 开发笔记 (源代码整理公开)

源代码已经整理过了,去除了不需要的项目. 注意:以前文章中出现过的Git已经变更过了,请以前关注过,Fork过的朋友,重新Fork一下. GitHub地址 卡牌XML文件的做成:(Git上面是没有XML文件的,原因是数量比较多,更新比较频繁,所以,运行游戏前,请自行生成) 代码里面有一个CardHelper的工程,运行这个工程: 资料导入导出 炉石资料文件可以从GitHub上找到,XML文件夹可以自己设定.  导出到XML 按钮按下去后就可以生成XML文件夹和文件了. 服务器的启动 启动  火炉

iOS开发笔记 - 网络篇

计算机网络基础 ??计算机网络是多台独立自主的计算机互联而成的系统的总称,最初建立计算机网络的目的是实现信息传递和资源共享. ??如果说计算机是第二次世界大战的产物,那么计算机网络则是美苏冷战的产物.20世纪60年代初期,美国国防部领导的ARPA提出研究一种崭新的.能够适应现代战争的.生存性很强的通信系统并藉此来应对苏联核攻击的威胁,这个决定促使了分组交换网的诞生,也奠定今天计算机网络的原型,这是计算机网络发展史上第一个里程碑式的事件. ??第二个里程碑式的事件是20世纪80年代初,国际标准化组

ONVIF客户端搜索设备获取rtsp地址开发笔记(精华篇)

原文  http://blog.csdn.net/gubenpeiyuan/article/details/25618177 概要: 前言及鸣谢: 感谢guog先生,快活林高先生,onvif全国交流群的的酷夏先生在开发过程中给予的巨大支持,没有你们的帮助开发过程将异常艰难啊.谢谢了! ONVIF介绍: ONVIF致力于通过全球性的开放接口标准来推进 网络视频 在安防市场的应用,这一接口标准将确保不同厂商生产的网络视频产品具有互通性.2008年11月,论坛正式发布了ONVIF第一版规范——ONVI

ONVIFclient搜索设备获取rtsp地址开发笔记(精华篇)

概要: 前言及鸣谢: 感谢guog先生.快活林高先生,onvif全国交流群的的酷夏先生在开发过程中给予的巨大支持,没有你们的帮助开发过程将异常艰难啊.谢谢了! ONVIF介绍: ONVIF致力于通过全球性的开放接口标准来推进网络视频在安防市场的应用,这一接口标准将确保不同厂商生产的网络视频产品具有互通性.2008年11月.论坛正式公布了ONVIF第一版规范--ONVIF核心规范1.0.随着视频监控的网络化应用,产业链的分工将越来越细. 有些厂商专门做摄像头.有些厂商专门做DVS.有些厂商则可能专