问题描述
我目前正在“处理”中重新创建“文明”游戏。我正计划实现该功能,在该功能中,给定的单位可以看到允许移动的给定数量的十六进制可以进行的所有可能的移动。所有可能的端点都标有红色圆圈。但是,单位无法穿越山脉或水域。我试图通过找出在没有装置进入山脉或水域的情况下可以做出的所有可能动作组合来解决此问题,但是我不知道如何确定每种组合。
任何单位可以进入6个方向,即东北,北方,西北,东南,南方,西南。我分配给任何单元的最大移动次数可能会增加到6。任何更高的运动,恐怕每次移动单元时处理速度都会变慢。
我正在尝试重新创建它:
我希望结果在两个可能的移动(没有黑色箭头)的情况下看起来像:
该图片的原始版本:
这是我用来绘制十六进制网格的代码。绘制每个十六进制后,立即将其中心的x坐标和y坐标分别存储在xHexe和yHexe中。同样,在生成图块类型(例如草地,海滩)之后,图块类型也立即存储在名为hexTypes的数组中。因此,只要引用索引,我就可以在地图上得到我想要的十六进制的x和y坐标以及十六进制的类型。
用于绘制单个六边形的代码:
beginShape();
for (float a = PI/6; a < TWO_PI; a += TWO_PI/6) {
float vx = x + cos(a) * gs*2;
float vy = y + sin(a) * gs*2;
vertex(vx,vy);
}
x是六边形中心的x坐标 y是六边形中心的y坐标 gs =六角半径
用于在窗口上修饰十六进制以创建十六进制网格的代码:
void redrawMap() {
float xChange = 1.7;
float yChange = 6;
for (int y = 0; y < ySize/hexSize; y++) {
for (int x = 0; x < xSize/hexSize; x++) {
if (x % 2 == 1) {
// if any part of this hexagon being formed will be visible on the window and not off the window.
if (x*hexSize*xChange <= width+2*hexSize && int(y*hexSize*yChange) <= height+3*hexSize) {
drawHex(x*hexSize*xChange,y*hexSize*yChange,hexSize);
}
// only record and allow player to react with it if the entire tile is visible on the window
if (x*hexSize*xChange < width && int(y*hexSize*yChange) < height) {
xHexes.add(int(x*hexSize*xChange));
yHexes.add(int(y*hexSize*yChange));
}
} else {
if (x*hexSize*xChange <= width+2*hexSize && int(y*hexSize*yChange) <= height+3*hexSize) {
drawHex(x*hexSize*xChange,y*hexSize*yChange+(hexSize*3),hexSize);
}
if (x*hexSize*xChange < width && int(y*hexSize*yChange+(hexSize*3)) < height) {
xHexes.add(int(x*hexSize*xChange));
yHexes.add(int(y*hexSize*yChange+(hexSize*3)));
}
}
}
}
}
hexSize是用户为每个六边形指定的尺寸,确定将在屏幕上显示的六边形数量。
解决方法
这个答案将帮助您达到这一目标(绿色是平原,红色是山丘,蓝色是水,也请不要燃烧我可怕的网格):
请注意,此解决方案中没有寻路功能,只有一些非常简单的“我能到达那里”数学。我将在最后包括草图的完整代码,以便您可以复制我所做的工作并自己进行测试。最后一件事:这个答案没有使用任何高级设计模式,但是它假定您对基础知识和面向对象编程很满意。如果我不确定您是否了解某些内容,可以(也应该)询问。
也:这是一个概念证明,而不是“复制并粘贴我”解决方案。我没有您的代码,因此无论如何都不可能是第二件事,但是由于您的问题可以以不计其数的方式解决,因此,这只是我特意设计的尽可能简单和直观的方式,以便您理解和理解。运行它。
首先,我强烈建议您将图块变成对象。首先,因为它们需要携带大量信息(每个图块上的内容,它们要穿越的难度有多少,资源或产量之类的东西……我不知道,但是会有很多东西)。>
基础
我这样组织全局变量:
// Debug
int unitTravelPoints = 30; // this is the number if "travel points" currently being tested,you can change it
// Golbals
float _tileSize = 60;
int _gridWidth = 10;
int _gridHeight = 20;
ArrayList<Tile> _tiles = new ArrayList<Tile>(); // all the tiles
ArrayList<Tile> _canTravel = new ArrayList<Tile>(); // tiles you can currently travel to
最基本的是,我希望能够即时更改网格大小,但这只是一个细节。接下来是为网格选择坐标系。我选择了最简单的一个,因为我不想在复杂的事情上大吃一惊,但您可能希望将其适应于另一个坐标系。我选择网格的偏移坐标类型:我的“第二行”是平铺偏移的一半。所以,不要这样:
我有这个:
其余的只是调整图块的空间坐标,因此看上去并不太糟,但是它们的坐标保持不变:
请注意,我如何认为空间坐标和网格坐标是两个不同的东西。我将主要使用空间坐标进行邻近度检查,但这是因为我很懒,因为您可以创建一个不错的算法,而无需空间坐标就可以执行相同的操作,而且成本可能更低。
旅行点如何?这就是我决定工作的方式:您单位的“旅行点”数量有限。这里没有单位,但是有一个全局变量unitTravelPoints
可以执行相同的操作。我决定使用这种比例尺:一个普通的瓷砖值得10个旅行点。所以:
- 平原:10分
- 山:15分
- 水:1000点(这是不可逾越的地形,但没有详细介绍)
我将不讨论绘制网格的细节,但这主要是因为在这方面,您的算法看上去比我的算法更好。另一方面,我将花一些时间来解释如何设计Tiles。
瓷砖
我们正在输入OOP:它们是Drawable
。 Drawable
是一个基类,其中包含一些基本信息,每个可见事物都应具有:位置和可以关闭的isVisible
设置。还有一种绘制它的方法,由于Render()
已被Processing处理,因此我称之为draw()
:
class Drawable {
PVector position;
boolean isVisible;
public Drawable() {
position = new PVector(0,0);
isVisible = true;
}
public void Render() {
// If you forget to overshadow the Render() method you'll see this error message in your console
println("Error: A Drawable just defaulted to the catch-all Render(): '" + this.getClass() + "'.");
}
}
图块将更加复杂。它会提供更多基本信息:行,列,当前是否被选中(为什么不选择),平原,丘陵或水之类的类型,一堆相邻的瓷砖,绘制自身的方法以及知道单元是否可以绘制的方法通过它:
class Tile extends Drawable {
int row,column;
boolean selected = false;
TileType type;
ArrayList<Tile> neighbors = new ArrayList<Tile>();
Tile(int row,int column,TileType type) {
super(); // this calls the parent class' constructor
this.row = row;
this.column = column;
this.type = type;
// the hardcoded numbers are all cosmetics I included to make my grid looks less awful,nothing to see here
position.x = (_tileSize * 1.5) * (column + 1);
position.y = (_tileSize * 0.5) * (row + 1);
// this part checks if this is an offset row to adjust the spatial coordinates
if (row % 2 != 0) {
position.x += _tileSize * 0.75;
}
}
// this method looks recursive,but isn't. It doesn't call itself,but it calls it's twin from neighbors tiles
void FillCanTravelArrayList(int travelPoints,boolean originalTile) {
if (travelPoints >= type.travelCost) {
// if the unit has enough travel points,we add the tile to the "the unit can get there" list
if (!_canTravel.contains(this)) {
// well,only if it's not already in the list
_canTravel.add(this);
}
// then we check if the unit can go further
for (Tile t : neighbors) {
if (originalTile) {
t.FillCanTravelArrayList(travelPoints,false);
} else {
t.FillCanTravelArrayList(travelPoints - type.travelCost,false);
}
}
}
}
void Render() {
if (isVisible) {
// the type knows which colors to use,so we're letting the type draw the tile
type.Render(this);
}
}
}
磁贴类型
TileType是一种奇怪的动物:它是一个真实的类,但从未在任何地方使用过。这是因为它是所有图块类型的通用根,它将继承其基本知识。 “城市”图块可能需要与例如“沙漠”图块完全不同的变量。但是两者都需要能够绘制自己,并且都需要由图块拥有。
class TileType {
// cosmetics
color fill = color(255,255,255);
color stroke = color(0);
float strokeWeight = 2;
// every tile has a "travelCost" variable,how much it cost to travel through it
int travelCost = 10;
// while I put this method here,it could have been contained in many other places
// I just though that it made sense here
void Render(Tile tile) {
fill(fill);
if (tile.selected) {
stroke(255);
} else {
stroke(stroke);
}
strokeWeight(strokeWeight);
DrawPolygon(tile.position.x,tile.position.y,_tileSize/2,6);
textAlign(CENTER,CENTER);
fill(255);
text(tile.column + "," + tile.row,tile.position.x,tile.position.y);
}
}
每种图块类型现在都可以自定义,但无论其内容如何,每个图块都是……只是一个图块。这是我在本演示中使用的TileType
:
// each different tile type will adjust details like it's travel cost or fill color
class Plains extends TileType {
Plains() {
this.fill = color(0,125,0);
this.travelCost = 10;
}
}
class Water extends TileType {
// here I'm adding a random variable just to show that you can custom those types with whatever you need
int numberOfFishes = 10;
Water() {
this.fill = color(0,125);
this.travelCost = 1000;
}
}
class Hill extends TileType {
Hill() {
this.fill = color(125,50,50);
this.travelCost = 15;
}
}
非类方法
我添加了一个mouseClicked()
方法,因此我们可以选择一个十六进制以检查单元可以移动多远。在游戏中,您必须做到这一点,以便在选择一个单位时所有这些东西都就位了,但是由于这只是概念上的证明,因此该单位是虚构的,它的位置在您单击的任何位置。
void mouseClicked() {
// clearing the array which contains tiles where the unit can travel as we're changing those
_canTravel.clear();
for (Tile t : _tiles) {
// select the tile we're clicking on (and nothing else)
t.selected = IsPointInRadius(t.position,new PVector(mouseX,mouseY),_tileSize/2);
if (t.selected) {
// if a tile is selected,check how far the imaginary unit can travel
t.FillCanTravelArrayList(unitTravelPoints,true);
}
}
}
最后,我添加了2个“帮助器方法”以简化操作:
// checks if a point is inside a circle's radius
boolean IsPointInRadius(PVector center,PVector point,float radius) {
// simple math,but with a twist: I'm not using the square root because it's costly
// we don't need to know the distance between the center and the point,so there's nothing lost here
return pow(center.x - point.x,2) + pow(center.y - point.y,2) <= pow(radius,2);
}
// draw a polygon (I'm using it to draw hexagons,but any regular shape could be drawn)
void DrawPolygon(float x,float y,float radius,int npoints) {
float angle = TWO_PI / npoints;
beginShape();
for (float a = 0; a < TWO_PI; a += angle) {
float sx = x + cos(a) * radius;
float sy = y + sin(a) * radius;
vertex(sx,sy);
}
endShape(CLOSE);
}
旅行的计算方式
在幕后,程序便知道了该单元可以在哪里行驶:在此示例中,该单元有30个行驶点。平原成本10,丘陵成本15。如果该单位剩下足够的点数,则该瓷砖标记为“可以在该处行驶”。每当瓷砖进入行驶距离时,我们还会检查该单位是否也能从该瓷砖上移开。
现在,如果您仍在关注我,您可能会问:这些图块如何知道它们的邻居是哪个其他图块?这是个好问题。我想一种检查其坐标的算法将是处理此问题的最佳方法,但是由于此操作只会在创建地图时发生一次,因此我决定采用简单的路线并检查哪些图块在空间上足够接近:
void setup() {
// create the grid
for (int i=0; i<_gridWidth; i++) {
for (int j=0; j<_gridHeight; j++) {
int rand = (int)random(100);
if (rand < 20) {
_tiles.add(new Tile(j,i,new Water()));
} else if (rand < 50) {
_tiles.add(new Tile(j,new Hill()));
} else {
_tiles.add(new Tile(j,new Plains()));
}
}
}
// detect and save neighbor tiles for every Tile
for (Tile currentTile : _tiles) {
for (Tile t : _tiles) {
if (t != currentTile) {
if (IsPointInRadius(currentTile.position,t.position,_tileSize)) {
currentTile.neighbors.add(t);
}
}
}
}
}
用于粘贴粘贴的完整代码
这里是一整件事,因此您可以轻松地将其复制并粘贴到Processing IDE中并对其进行操作(还包括如何绘制可怕的网格):
// Debug
int unitTravelPoints = 30; // this is the number if "travel points" currently being tested,you can change it
// Golbals
float _tileSize = 60;
int _gridWidth = 10;
int _gridHeight = 20;
ArrayList<Tile> _tiles = new ArrayList<Tile>();
ArrayList<Tile> _canTravel = new ArrayList<Tile>();
void settings() {
// this is how to make a window size's dynamic
size((int)(((_gridWidth+1) * 1.5) * _tileSize),(int)(((_gridHeight+1) * 0.5) * _tileSize));
}
void setup() {
// create the grid
for (int i=0; i<_gridWidth; i++) {
for (int j=0; j<_gridHeight; j++) {
int rand = (int)random(100);
if (rand < 20) {
_tiles.add(new Tile(j,_tileSize)) {
currentTile.neighbors.add(t);
}
}
}
}
}
void draw() {
background(0);
// show the tiles
for (Tile t : _tiles) {
t.Render();
}
// show how far you can go
for (Tile t : _canTravel) {
fill(0,0);
if (t.selected) {
stroke(255);
} else {
stroke(0,0);
}
strokeWeight(5);
DrawPolygon(t.position.x,t.position.y,6);
}
}
class Drawable {
PVector position;
boolean isVisible;
public Drawable() {
position = new PVector(0,0);
isVisible = true;
}
public void Render() {
// If you forget to overshadow the Render() method you'll see this error message in your console
println("Error: A Drawable just defaulted to the catch-all Render(): '" + this.getClass() + "'.");
}
}
class Tile extends Drawable {
int row,nothing to see here
position.x = (_tileSize * 1.5) * (column + 1);
position.y = (_tileSize * 0.5) * (row + 1);
// this part checks if this is an offset row to adjust the spatial coordinates
if (row % 2 != 0) {
position.x += _tileSize * 0.75;
}
}
// this method looks recursive,but it calls it's twin from neighbors tiles
void FillCanTravelArrayList(int travelPoints,boolean originalTile) {
if (travelPoints >= type.travelCost) {
// if the unit has enough travel points,we add the tile to the "the unit can get there" list
if (!_canTravel.contains(this)) {
// well,only if it's not already in the list
_canTravel.add(this);
}
// then we check if the unit can go further
for (Tile t : neighbors) {
if (originalTile) {
t.FillCanTravelArrayList(travelPoints,false);
} else {
t.FillCanTravelArrayList(travelPoints - type.travelCost,false);
}
}
}
}
void Render() {
if (isVisible) {
// the type knows which colors to use,so we're letting the type draw the tile
type.Render(this);
}
}
}
class TileType {
// cosmetics
color fill = color(255,tile.position.y);
}
}
// each different tile type will adjust details like it's travel cost or fill color
class Plains extends TileType {
Plains() {
this.fill = color(0,0);
this.travelCost = 10;
}
}
class Water extends TileType {
// here I'm adding a random variable just to show that you can custom those types with whatever you need
int numberOfFishes = 10;
Water() {
this.fill = color(0,50);
this.travelCost = 15;
}
}
void mouseClicked() {
// clearing the array which contains tiles where the unit can travel as we're changing those
_canTravel.clear();
for (Tile t : _tiles) {
// select the tile we're clicking on (and nothing else)
t.selected = IsPointInRadius(t.position,true);
}
}
}
// checks if a point is inside a circle's radius
boolean IsPointInRadius(PVector center,sy);
}
endShape(CLOSE);
}
希望会有所帮助。玩得开心!
,您将必须使用在寻路中使用的类似算法。您创建了一个堆栈或队列,该堆栈或队列将保存一个存储十六进制位置和从该点开始剩余移动数的类,首先,将起始位置插入您拥有的移动数并将该十六进制标记为完成( -使用您已经在上的位置,然后弹出一个元素,然后以-1的移动次数插入该十六进制的每个邻居。当您插入零移动的六边形时,这些就是您的端点。在插入任何十六进制之前,请检查是否尚未完成。
我希望我很清楚,您的问题有点含糊,但是我试图让您了解这些解决方案通常是如何完成的,而且我认为您的问题更适合算法而不是处理
祝你好运