战棋游戏通常指以回合制为基础,角色在地图上按格移动作战的游戏,好比下棋一样,该类型游戏更侧重于策略,节奏较缓慢,注重精美、绚丽的画面,考验的是玩家运筹全局的智慧。耳熟能详的比如《梦幻模拟战》、《火焰纹章》、《大战略》、《炎龙骑士团》、《幻世录》等经典。
战棋游戏占据了我太多记忆,每次看到类似的游戏都会激动不已,尤其是远距离攻击类型的弓箭手和魔法师,战局扭转往往就在一步之间。
今天,作为一名游戏开发者,当技术与思想逐步成熟,理想似乎已不再是那么的遥不可及。以SLG战棋游戏战斗范围为例,我们可以巧妙的通过集合运算来实现几乎所有你能想到的角色战斗范围设定。
动手前我们不妨先看一下《曹操传》中所有的战斗范围图例:
见到此类素材大家会否觉得实在亲切。没错,就是我们小时候读书最常玩的找规律数学题。
既然要找出规律,那么就得先分析出其中的共性与变化。最直接的共性便是所有的格子均是“对称”的;而发生变化的则是某些图例似乎都被“挖”去了一些;再往下想,这些被“挖”掉的格子同样也是“对称”的。由此,基于集合的差集运算第一时间浮现在脑海。
规律把握的是否正确离不开证明过程,接下来我们随便以上图中任意几个图例为例加以验证:
……
是不是开始有些激动了?
最后的总结:所有“对称”的战斗范围都可以是基于某种规律N格长度的范围与基于另一种规律M格长度范围的差集,其中这两种规律可以相同,N与M也可以相同。由此便可衍生出如文中开头所有的战斗范围,以及更多未列出来的。
有了以上强大的理论依据作为基础,接下来的编码便是手到擒来,我的思路大致如下。
第一步,定义最常用的基本规律(即连续的战斗范围类型):
/// <summary>
/// 战斗范围类型
/// </summary>
public enum AttackRangeTypes {
/// <summary>
/// 无
/// </summary>
None = 0,
/// <summary>
/// 全八面(方形)
/// </summary>
Square = 1,
/// <summary>
/// 斜四面(菱形)
/// </summary>
Diamond = 2,
/// <summary>
/// 正四向(十字)
/// </summary>
Cross = 3,
/// <summary>
/// 斜四向(交叉)
/// </summary>
Oblique = 4,
}
除None外,它们2长度范围分别对应以下图例:
第二步,以这些战斗范围(List<Point>)为返回值构造方法:
List<Point> GetRange(Point center, AttackRangeTypes attackRangeType, int range) {
List<Point> points = new List<Point>();
for (int x = -range; x <= range; x++) {
for (int y = -range; y <= range; y++) {
switch (attackRangeType) {
case AttackRangeTypes.None:
continue;
case AttackRangeTypes.Square:
break;
case AttackRangeTypes.Diamond:
if (Math.Abs(x) + Math.Abs(y) > range) { continue; }
break;
case AttackRangeTypes.Cross:
if (Math.Abs(x) != 0 && Math.Abs(y) != 0) { continue; }
break;
case AttackRangeTypes.Oblique:
if (Math.Abs(x) != Math.Abs(y)) { continue; }
break;
}
points.Add(new Point(center.X + x, center.Y + y));
}
}
return points;
}
/// <summary>
///
获取攻击范围坐标列表
/// </summary>
/// <param name="rangeType">范围类型</param>
/// <param name="range">范围</param>
/// <param name="exclusionRangeType">排除范围类型</param>
/// <param name="exclusionRange">排除范围</param>
/// <returns></returns>
public List<Point> AttackRange(AttackRangeTypes rangeType, int range, AttackRangeTypes exclusionRangeType, int exclusionRange) {
List<Point> points = GetRange(Coordinate, rangeType, range);
List<Point> excludePoints = GetRange(Coordinate, exclusionRangeType, exclusionRange);
return points.Except(excludePoints).ToList();
}
第三步,游戏中,将玩家控制的角色坐标Coordinate作为以上方法的Center参数,通过前文提到的组合方式最终完成SLG战棋角色战斗范围动态设定:
……
这是一款基于Silverlight的SLG游戏引擎的一部分,在线演示Demo 如下(点击下载该Demo源码):
通过动态组合差集的方式设定SLG战棋角色的攻击范围,无论是灵活性、适用性还是拓展性都显得极其强大。当然,每个人的思维方式不一样,写出的算法也会大相径庭,游戏开发的乐趣就在于此:优化永无止尽。
手记思考:当问题出现时若能正确的把握其本质规律,一切都将显得那么的简单。