我正在寻找一种模拟游戏直到输赢的方法,以输入蒙特卡罗树搜索算法。
我的游戏是一款回合制、基于图 block 的战术角色扮演游戏,类似于《最终幻想战略版》、《火焰纹章》等。
这个想法是人工智能将执行数千次比赛(或直到达到阈值),直到他们确定最佳的下一步行动。
模拟
每个人工智能和玩家代理都会做出随机有效的移动,直到游戏结束。
为什么要进行 MCTS 模拟?为什么不是最小最大?
出于以下几个原因,我需要尽可能接近真实地模拟游戏:
- 将游戏状态编码为低维结构是不可能的,因为大多数 Action 都与碰撞体和射线等 Unity 结构紧密耦合。
- 在不了解之前的 Action 的情况下,静态评估前进
X
步的游戏状态即使不是不可能,也是相当困难的。因此,我需要在游戏状态上按顺序执行每个 Action ,以在评估任何内容之前生成下一个游戏状态。
扩展第 2 点: 使用简单的最小最大方法,并通过查看所有玩家的当前健康状况等静态评估游戏状态,虽然有用,但并不准确。 因为并非每项行动都会立即改变健康状况。
示例:
这会在 2 回合内产生更高的(最大伤害):
- 移动到玩家前面,攻击 -> 移动到玩家后面,攻击
或者
- 移动到玩家面前,使用攻击增益 -> 攻击造成 x4 伤害
在这个例子中,最小最大方法永远不会导致第二个选项,即使它在 2 回合内造成更多伤害,因为它对 buff 移动的静态评估导致 0,甚至可能是负值。
为了选择第二个选项,它需要保留先前操作的知识。 IE。它需要几乎完美地模拟游戏。
当我们添加其他元素时,例如:舞台陷阱、可破坏的环境和状态效果。使用静态评估几乎变得不可能
我尝试过的
时间.timeScale
这使我能够加快物理和其他交互的速度,这正是我所需要的。 但是 - 这是一个全局属性,因此当人工智能“思考”时,游戏会在几分之一秒内以超快的速度运行。
提高 NavMesh 代理的速度
我的所有 Action 都发生在导航网格上 - 因此使这些 Action “即时”的唯一可感知的方法是提高速度。这是有问题的,因为移动速度不够快 - 并且由于速度增加而导致物理问题,有时角色会失去控制并飞出 map 。
这里是我的(正在积极开发的)游戏的屏幕截图,供引用。
问题
我需要的是一种能够非常快速地“玩”我的游戏的方法。
我只需要能够在每次 AI 移动之前快速有效地运行这些模拟。
我很乐意听到有一些经验的人做类似的事情 - 但任何意见将不胜感激!
谢谢
最佳答案
构建核心机制的抽象模型
为了让某些东西快速运行,我们需要它简单 - 这意味着将游戏剥离到其基 native 制,并代表它(并且仅此而已)。
那么,这是什么意思呢?好吧,首先我们有一个基于图 block 的世界。一个简单的表示是 Tile 对象的 2D 数组,如下所示:
/// <summary>
/// This gameworld is 20x20 tiles
/// </summary>
public Tile[,] Tiles = new Tile[20,20];
世界上无法到达的地方 - 例如因为房间不是矩形的或者有非交互式家具挡住了 - 该数组中可以简单地为空值。
接下来,角色会在这些图 block 上移动。每个角色也有生命值:
public class Character {
/// <summary>
/// The tile this character is on.
/// </summary>
private Tile _currentLocation;
/// <summary>
/// The tile this character is on.
/// </summary>
public Tile CurrentLocation{
get{
return _currentLocation;
}
set{
if(_currentLocation != null)
{
_currentLocation.Occupier = null;
}
_currentLocation = value;
_currentLocation.Occupier = this;
}
}
/// <summary>
/// Its HP
/// </summary>
public float Hitpoints = 100f;
public void SetLocation(Tile newLocation){
CurrentLocation = newLocation;
}
}
我们可以制作更专业的角色变体,例如具有特殊属性的 class Hero : Character
,但我们暂时忽略它。
上面的 CurrentLocation 也会更新图 block - 为了提高速度,它有助于了解哪个角色站在特定图 block 上,因此这里有一个图 block 类:
public class Tile{
public Character Occupier;
public int X;
public int Y;
}
好吧,让我们来举例一个世界 - 为了简单起见,这个世界是矩形的,因此所有图 block 都已填充:
for(var x=0;x<20;x++){
for(var y=0;y<20;y++){
Tiles[x,y] = new Tile(){X = x, Y = y};
}
}
我们将在特定位置拍下 2 个角色:
var badGuy = new Character();
badGuy.CurrentLocation = Tiles[3,4];
var hero = new Character();
hero.CurrentLocation = Tiles[9,9];
从这一点来看,我们有了一个非常简单的世界模型。例如,我们可以通过检查Tiles[x,y].Occupier
来询问它在tile x,y处有什么。从这里开始,您开始将机制添加到模型中 - 例如,这可能是某种 aTile.Attack(AttackType.IceBlast)
方法:
public void Attack(AttackType type, Character attackedBy){
// impacts type.Range tiles around this one, dealing damage to each Occupier.
// Get a neighbouring tile via e.g. yourReferenceToTheTilesArray[X+1, Y].
}
重要的是,游戏随后被修改为仅反射(reflect)模型 - 这样模型总是准确的,因为它驱动游戏并且是伤害量等的真正来源。您可以向其中添加事件,因此当调用上述 Attack 方法时,会触发一些 OnAttack 处理程序,然后在整个游戏运行时触发动画。
这样的模型可以作为独立的 C# 程序运行,完全在 Unity 之外运行,能够每秒生成数百万圈以用于测试或 AI 训练目的。
关于unity-game-engine - Unity即时模拟游戏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60155133/