普通视图

发现新文章,点击刷新页面。
昨天以前首页

背包大乱斗与俄罗斯方块(代码篇)

2024年11月21日 16:31

前一段时间写了一篇 背包大乱斗与俄罗斯方块(设计篇) ,具体的实现思路在这一文中已经讲清楚了,后来我抽空去实现了一版。目前看效果还不错。

已经实现,形状的变换,定位,移动,消除,障碍判定等。
本篇稍微讲一下具体的实现过程,以及如何去优化这个算法。

基于池去实现节点的创建与回收

一开始就基于这个池模板去管理所有数量上较多的对象,后期优化的压力会小一些。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public interface IReset
{
void Reset();
}

public interface IPool<T> where T : IReset
{
Stack<T> nodesPool { get; }

T CreateOne<W>() where W : T, new();
void ReturnOne(T item);
}


public class BasePool<T> : IPool<T> where T : IReset
{
Stack<T> _nodesPool = new Stack<T>();
public Stack<T> nodesPool { get { return _nodesPool; } }

public virtual T CreateOne<W>() where W : T, new()
{
if (nodesPool.Count > 0)
{
return nodesPool.Pop();
}
return new W();
}

public virtual void ReturnOne(T node)
{
node.Reset();
nodesPool.Push(node);
}
}

分离算法与表现

我们的算法是需要适应不同的场景的,如果基于一套UI或者3D/2D渲染,混写代码,就会导致这个代码复用性低,迁移起来费时费力。

TileInfo.cs 作为管理单个形状( 物品) 渲染信息的最小单位。我们并不需要在这里书写任何如何去渲染的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TileInfo :  IReset
{
public List<GameObject> Cubes = new List<GameObject>();
public GameObject Tile;
public Color BaseColor;

public void Reset()
{
BaseColor = Color.white;

if (Cubes.Count > 0)
{
for (int i = 0; i < Cubes.Count; i++)
PrefabPoolManager.GetInstance().PushGameObjectByType(PrefabPoolManager.PrefabType.Cube, Cubes[i]);
Cubes.Clear();
}
PrefabPoolManager.GetInstance().PushGameObjectByType(PrefabPoolManager.PrefabType.Tile, Tile);
Tile = null;
}
}

将这个逻辑放到一个单独的Render脚本中,这样处理现在我们已经将渲染画面的功能完全隔离到了 BlockRender。
如果我们有3D的画面,就可以写一个3DBlockRender 或者是 UIBlockRender ,只需要抽象出接口做新的实现即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BlockRender 
{
public TileInfo UpdatePreSelectNode(PreSelect node, LogicMap map)
{
return UpdateTile(node, node.Shape, node.x, node.y, map);
}

public TileInfo UpdatePreSelectNode(PreSelect node, IShape shap, LogicMap map)
{
return UpdateTile(node, shap, node.x,node.y,map);
}

...

至此我们已经完成了基础逻辑,他包含一个通用的池实现,与一个通用的渲染层。

核心算法

一般来说游戏的业务逻辑复杂度都不高,真要说复杂的,那肯定是渲染逻辑。
这段放置图形的代码,就是背包大乱斗最复杂的业务逻辑了。

通过当前节点的相对点加图形的数据结构中存储的x与y值,就可以推算出逻辑节点的坐标。
注意这边的逻辑节点,需要配置map的信息,比如map的位置信息与缩放进行一个定位,才能换算出真实坐标。

当然下面的代码并没有添加,是否这个位置有阻挡或者已经被占用的判定,由于我们的玩法尚未定型,则将这个判定放到了渲染层,在最后的演示中,你可以看到如果两个图形有重叠部分,重叠部分的区域会变成红色。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public INode PlaceShape(INode node,IShape shape) 
{
node.Shape = shape;

//解析data
var data = shape.Data;
var rows = data.GetLength(1);
var columns = data.GetLength(0);

for (int y = 0; y < rows; y++)
{
for (int x = 0; x < columns; x++)
{
if (data[x, y] == 0)
continue;

INode usedNode = GetNodeWithXY(node.x + x, node.y + y);
usedNode.State = NodeState.CUBE;
}
}

return node;
}

演示

通过空格键可以在左下角创建出方块,wasd去移动,qe可以转换方向,再次空格键可以放下方块。

页面有9M大小,加载较慢。演示地址 : 点我转跳

我尝试在下方加载了一个 iframe ,如果能正常显示的话就不用再转跳到上面的链接了。-

现在上面的演示地址失效,我用来放置游戏demo,当然这个demo也是用如上算法,欢迎参考,关注。

背包大乱斗与俄罗斯方块(设计篇)

2024年11月19日 13:53

前段时间背包大乱斗身边的同事玩的还挺多的,刚好这几天有空去研究研究,我还专门去看了背包大乱斗的攻略。发现,“啊,这不是俄罗斯方块吗 ?” 又一番研究之后确实,背包大乱斗和俄罗斯方块有诸多相似之处。

玩法分析

在空间规划与摆放方式上,俄罗斯方块是要将各种形状方块合理放置在游戏区域,通过移动和旋转来填满行以得分,背包大乱斗则要在有限背包格子内合理放置装备等物品,考虑其形状、属性和搭配来最大化利用空间;

在决策过程中,俄罗斯方块玩家要在方块下落时快速决策其旋转、移动和放置时机,考虑方块间关系和后续空间预留,背包大乱斗玩家要依据职业、已有物品和游戏局势做出购买、合成等决策,两者都要综合考虑多种因素;

从形状与组合多样性来看,俄罗斯方块有七种四格方块,通过旋转和移动创造多样组合,背包大乱斗有多种装备等,通过合成、镶嵌等形成不同搭配;

游戏的动态变化性上,俄罗斯方块随着方块下落和消除游戏区域不断变化,玩家要随之调整策略,背包大乱斗在游戏中因新物品获取、对手情况变化而使局势改变,玩家也需要及时调整背包布局和策略。

游戏是继承与发展的。背包大乱斗本质上是俄罗斯方块机制的延展,从后者的空间利用、策略决策、形状组合和动态变化中获取灵感,深化空间放置与规划理念,发展出更复杂多样的内容,如物品属性和合成搭配,其动态变化也更具挑战性,给玩家全新体验。

背包大乱斗

代码设计

透过现象看本质,背包大乱斗的核心玩法,其实与俄罗斯方块是一致的,在这个基础之上包装了一个RPG。
当然你要说这个游戏,有着更加丰富的玩法,固然如此。但这些玩法最终都要依赖这个核心的玩法。

说了那么多,想必大家心里都有了不同的实现方案。那么现在我们动手去实现一下这个玩法的核心算法。

从一个二维数组开始

在所有的一切开始之前,要定义一个基础 Map ,我们所有的坐标定位都要基于这个二维网格。
顺便,我们将基础坐标系定在第一象限,方便我们定位与计算。

对应的数据可以表示成这样 (10X10) 空白格用0来表示:

1
2
3
4
5
6
7
8
9
10
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000

创建基础单位

一个基础单位,有上下左右,共四种朝向,当然这个基础单位可以任意的长短,这是灵活的,可配置的。

我们用虚线格填充这个基础单位,则有了下图,这样的好处是方便通过数据去描述这个单位长什么样子。

虚线格子用0表示,实线格子用1表示, 结合前面我们提到的,将这个坐标系放在第一象限,并且从下往上,从左至右自增。对于单个方块,我们可以用如下的数据结构去描述,它的不同形态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

010
111


01
11
01


111
010


10
11
10

设计一个运行框架

根据上面的思路我们可以这样去设计程序:

  • 我们的主体就是一张图,其中有N个节点。这个节点有个状态字段,如果不能放置或者被占用则标注之。
  • xy就是节点的坐标,因为我们的节点最终是要存储到一个list内的。
  • ShapeTemplate就是设计的重点,一个图形模板,通常一个图形模板对应多个分形(可以变换的状态)。

实际一个图形,在图中所占用的确切坐标,仅是一个node,根据这个节点定位结合图形的形状,可以轻松的定位到其他几个被影响到的点位。

以上背包大乱斗与俄罗斯方块的玩法分析与代码设计。

❌
❌