Fixed many pathfinding issues

This commit is contained in:
Anuken
2017-11-23 22:10:31 -05:00
parent b2cd95899c
commit f5583f6bc8
13 changed files with 141 additions and 84 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -34,12 +34,16 @@ public class Vars{
public static boolean debug = false; public static boolean debug = false;
//whether to debug openGL info //whether to debug openGL info
public static boolean debugGL = false; public static boolean debugGL = false;
//whether profiling is shown
public static boolean profile = false;
//whether the player can clip through walls
public static boolean noclip = false;
//whether to draw chunk borders //whether to draw chunk borders
public static boolean debugChunks = false; public static boolean debugChunks = false;
//whether turrets have infinite ammo (only with debug) //whether turrets have infinite ammo (only with debug)
public static boolean infiniteAmmo = true; public static boolean infiniteAmmo = true;
//whether to show paths of enemies //whether to show paths of enemies
public static boolean showPaths = false; public static boolean showPaths = true;
//number of save slots-- increasing may lead to layout issues //number of save slots-- increasing may lead to layout issues
//TODO named save slots, possibly with a scroll dialog //TODO named save slots, possibly with a scroll dialog
public static final int saveSlots = 4; public static final int saveSlots = 4;

View File

@@ -4,22 +4,29 @@ import com.badlogic.gdx.ai.pfa.Heuristic;
import io.anuke.mindustry.Vars; import io.anuke.mindustry.Vars;
import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.World;
public class MHueristic implements Heuristic<Tile>{ public class MHueristic implements Heuristic<Tile>{
//so this means that the cost of going through solids is 10x going through non solids //so this means that the cost of going through solids is 10x going through non solids
float multiplier = 10f; static float multiplier = 10f;
@Override @Override
public float estimate(Tile node, Tile other){ public float estimate(Tile node, Tile other){
return estimateStatic(node, other);
}
public static float estimateStatic(Tile node, Tile other){
float cost = Math.abs(node.worldx() - other.worldx()) + Math.abs(node.worldy() - other.worldy()); float cost = Math.abs(node.worldx() - other.worldx()) + Math.abs(node.worldy() - other.worldy());
//TODO balance multiplier //TODO balance multiplier
if(node.breakable() && node.block().solid) cost += Vars.tilesize*multiplier; if(node.breakable() && node.block().solid) cost += Vars.tilesize*multiplier;
if(other.breakable() && other.block().solid) cost += Vars.tilesize*multiplier; if(other.breakable() && other.block().solid) cost += Vars.tilesize*multiplier;
for(Tile tile : node.getNearby()){ for(int dx = -1; dx <= 1; dx ++){
if(tile != null && tile.solid()){ for(int dy = -1; dy <= 1; dy ++){
//don't go near solid tiles! Tile tile = World.tile(node.x + dx, node.y + dy);
cost += Vars.tilesize*3; if(tile != null && tile.solid()){
cost += Vars.tilesize*5;
}
} }
} }
return cost; return cost;

View File

@@ -1,57 +1,88 @@
package io.anuke.mindustry.ai; package io.anuke.mindustry.ai;
import com.badlogic.gdx.ai.pfa.PathFinder; import com.badlogic.gdx.ai.pfa.PathFinder;
import com.badlogic.gdx.ai.pfa.PathFinderRequest;
import com.badlogic.gdx.ai.pfa.PathSmoother; import com.badlogic.gdx.ai.pfa.PathSmoother;
import com.badlogic.gdx.ai.pfa.indexed.IndexedAStarPathFinder; import com.badlogic.gdx.ai.pfa.indexed.IndexedAStarPathFinder;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.entities.effect.Fx;
import io.anuke.mindustry.entities.enemies.Enemy; import io.anuke.mindustry.entities.enemies.Enemy;
import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.World; import io.anuke.mindustry.world.World;
import io.anuke.ucore.core.Timers; import io.anuke.ucore.core.Effects;
import io.anuke.ucore.util.Angles;
import io.anuke.ucore.util.Tmp;
public class Pathfind{ public class Pathfind{
static MHueristic heuristic = new MHueristic(); static MHueristic heuristic = new MHueristic();
static PassTileGraph graph = new PassTileGraph(); static PassTileGraph passgraph = new PassTileGraph();
static PathFinder<Tile> finder = new IndexedAStarPathFinder<Tile>(graph); static PathFinder<Tile> passpathfinder;
static Array<SmoothGraphPath> paths = new Array<>();
static Tile[][] pathSequences;
static PathSmoother<Tile, Vector2> smoother = new PathSmoother<Tile, Vector2>(new Raycaster()); static PathSmoother<Tile, Vector2> smoother = new PathSmoother<Tile, Vector2>(new Raycaster());
static Vector2 vector = new Vector2(); static Vector2 vector = new Vector2();
static public Vector2 find(Enemy enemy){ static public Vector2 find(Enemy enemy){
findNode(enemy); if(enemy.node == -1){
findNode(enemy);
if(enemy.node <= -1) return vector.set(enemy.x, enemy.y); }
//-1 is only possible here if both pathfindings failed, which should NOT happen //-1 is only possible here if both pathfindings failed, which should NOT happen
//check graph code //check graph code
//Tile[] path = enemy.path; Tile[] path = enemy.path;
Array<Tile> path = enemy.gpath.nodes;
Tile target = path.get(enemy.node); Tile prev = path[enemy.node - 1];
Tile target = path[enemy.node];
float projectLen = Vector2.dst(prev.worldx(), prev.worldy(), target.worldx(), target.worldy()) / 6f;
Vector2 projection = projectPoint(prev.worldx(), prev.worldy(),
target.worldx(), target.worldy(), enemy.x, enemy.y);
boolean canProject = true;
if(projectLen < 8 || !onLine(projection, prev.worldx(), prev.worldy(), target.worldx(), target.worldy())){
canProject = false;
}else{
projection.add(Angles.translation(Angles.angle(prev.worldx(), prev.worldy(),
target.worldx(), target.worldy()), projectLen));
}
float dst = Vector2.dst(enemy.x, enemy.y, target.worldx(), target.worldy()); float dst = Vector2.dst(enemy.x, enemy.y, target.worldx(), target.worldy());
if(dst < 2){ if(dst < 8){
if(enemy.node <= path.size-2) if(enemy.node <= path.length-2)
enemy.node ++; enemy.node ++;
target = path.get(enemy.node); target = path[enemy.node];
} }
//near the core, stop if(canProject && projection.dst(enemy.x, enemy.y) < Vector2.dst(target.x, target.y, enemy.x, enemy.y)){
if(enemy.node == path.size - 1){ vector.set(projection);
}else{
vector.set(target.worldx(), target.worldy()); vector.set(target.worldx(), target.worldy());
} }
return vector.set(target.worldx(), target.worldy()); //near the core, stop
if(enemy.node == path.length - 1){
vector.set(target.worldx(), target.worldy());
}
return vector;
} }
static public void updatePath(){ static public void reset(){
paths.clear();
pathSequences = null;
passpathfinder = new IndexedAStarPathFinder<Tile>(passgraph);
}
/* static public void updatePath(){
if(paths.size == 0 || paths.size != World.spawnpoints.size){ if(paths.size == 0 || paths.size != World.spawnpoints.size){
paths.clear(); paths.clear();
pathSequences = new Tile[World.spawnpoints.size][0]; pathSequences = new Tile[World.spawnpoints.size][0];
@@ -61,6 +92,13 @@ public class Pathfind{
} }
} }
//TODO make this work?
/*
PathFinderRequest<Tile> request = new PathFinderRequest<Tile>();
request.startNode = World.spawnpoints.get(0);
request.endNode = World.core;
passpathfinder.search(request, 1000); */
for(int i = 0; i < paths.size; i ++){ for(int i = 0; i < paths.size; i ++){
SmoothGraphPath path = paths.get(i); SmoothGraphPath path = paths.get(i);
@@ -85,47 +123,18 @@ public class Pathfind{
Effects.effect(Fx.ind, tile.worldx(), tile.worldy()); Effects.effect(Fx.ind, tile.worldx(), tile.worldy());
} }
}*/ }
} }
static void findNode(Enemy enemy){ static void findNode(Enemy enemy){
/*
enemy.path = pathSequences[enemy.spawn]; enemy.path = pathSequences[enemy.spawn];
Tile[] path = enemy.path; Tile[] path = enemy.path;
*/
if(enemy.node == -1 || (Timers.get(enemy, "pathfind", 120) && enemy.request.pathFound)){
enemy.gpath = new SmoothGraphPath();
enemy.finder = new IndexedAStarPathFinder<Tile>(graph);
enemy.gpath.clear();
enemy.request = new PathFinderRequest<Tile>(World.tileWorld(enemy.x, enemy.y),
World.core,
heuristic, enemy.gpath);
enemy.request.statusChanged = true;
enemy.node = -2;
}
if(enemy.gpath != null && !enemy.request.pathFound){
enemy.request.executionFrames ++;
if(enemy.finder.search(enemy.request, 1000000 / 5)){
smoother.smoothPath(enemy.gpath);
enemy.node = 1;
//UCore.log("done in " + enemy.request.executionFrames + " frames with path of size " + enemy.gpath.getCount());
}
}
/*
Tile closest = null; Tile closest = null;
float ldst = 0f; float ldst = 0f;
int cindex = -1; int cindex = -1;
for(int i = 0; i < path.size; i ++){ for(int i = 0; i < path.length; i ++){
Tile tile = path.get(i); Tile tile = path[i];
float dst = Vector2.dst(tile.worldx(), tile.worldy(), enemy.x, enemy.y); float dst = Vector2.dst(tile.worldx(), tile.worldy(), enemy.x, enemy.y);
if(closest == null || dst < ldst){ if(closest == null || dst < ldst){
@@ -134,6 +143,17 @@ public class Pathfind{
cindex = i; cindex = i;
} }
} }
enemy.node = cindex;*/ enemy.node = Math.max(cindex, 1);
}
private static boolean onLine(Vector2 vector, float x1, float y1, float x2, float y2){
return MathUtils.isEqual(vector.dst(x1, y1) + vector.dst(x2, y2), Vector2.dst(x1, y1, x2, y2), 0.01f);
}
private static Vector2 projectPoint(float x1, float y1, float x2, float y2, float pointx, float pointy){
float px = x2-x1, py = y2-y1, dAB = px*px + py*py;
float u = ((pointx - x1) * px + (pointy - y1) * py) / dAB;
float x = x1 + u * px, y = y1 + u * py;
return Tmp.v3.set(x, y); //this is D
} }
} }

View File

@@ -4,7 +4,7 @@ import com.badlogic.gdx.ai.pfa.Connection;
import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.Tile;
public class TileConnection implements Connection{ public class TileConnection implements Connection<Tile>{
Tile a, b; Tile a, b;
public TileConnection(Tile a, Tile b){ public TileConnection(Tile a, Tile b){
@@ -14,16 +14,16 @@ public class TileConnection implements Connection{
@Override @Override
public float getCost(){ public float getCost(){
return Math.abs(a.worldx() - b.worldx()) + Math.abs(a.worldy() - b.worldy()); return MHueristic.estimateStatic(a, b);
} }
@Override @Override
public Object getFromNode(){ public Tile getFromNode(){
return a; return a;
} }
@Override @Override
public Object getToNode(){ public Tile getToNode(){
return b; return b;
} }

View File

@@ -243,7 +243,7 @@ public class Control extends Module{
for(int i = 0; i < spawnamount; i ++){ for(int i = 0; i < spawnamount; i ++){
int index = i; int index = i;
float range = 8f; float range = 12f;
Timers.run(index*50f, ()->{ Timers.run(index*50f, ()->{
try{ try{
@@ -419,6 +419,10 @@ public class Control extends Module{
} }
} }
if(Inputs.keyUp(Keys.O)){
Vars.noclip = !Vars.noclip;
}
if(Inputs.keyUp(Keys.Y)){ if(Inputs.keyUp(Keys.Y)){
if(Inputs.keyDown(Keys.SHIFT_LEFT)){ if(Inputs.keyDown(Keys.SHIFT_LEFT)){
new HealerEnemy(0).set(player.x, player.y).add(); new HealerEnemy(0).set(player.x, player.y).add();

View File

@@ -35,6 +35,8 @@ public class Player extends DestructibleEntity{
@Override @Override
public void onDeath(){ public void onDeath(){
if(Vars.debug) return;
remove(); remove();
Effects.effect(Fx.explosion, this); Effects.effect(Fx.explosion, this);
Effects.shake(4f, 5f, this); Effects.shake(4f, 5f, this);
@@ -85,7 +87,12 @@ public class Player extends DestructibleEntity{
vector.limit(speed); vector.limit(speed);
move(vector.x*Timers.delta(), vector.y*Timers.delta()); if(!Vars.noclip){
move(vector.x*Timers.delta(), vector.y*Timers.delta());
}else{
x += vector.x*Timers.delta();
y += vector.y*Timers.delta();
}
if(!shooting){ if(!shooting){
direction.add(vector); direction.add(vector);

View File

@@ -1,7 +1,5 @@
package io.anuke.mindustry.entities.enemies; package io.anuke.mindustry.entities.enemies;
import com.badlogic.gdx.ai.pfa.PathFinder;
import com.badlogic.gdx.ai.pfa.PathFinderRequest;
import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
@@ -9,7 +7,6 @@ import com.badlogic.gdx.utils.reflect.ClassReflection;
import io.anuke.mindustry.Vars; import io.anuke.mindustry.Vars;
import io.anuke.mindustry.ai.Pathfind; import io.anuke.mindustry.ai.Pathfind;
import io.anuke.mindustry.ai.SmoothGraphPath;
import io.anuke.mindustry.entities.Bullet; import io.anuke.mindustry.entities.Bullet;
import io.anuke.mindustry.entities.BulletType; import io.anuke.mindustry.entities.BulletType;
import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.entities.Player;
@@ -23,7 +20,12 @@ import io.anuke.ucore.util.Mathf;
import io.anuke.ucore.util.Tmp; import io.anuke.ucore.util.Tmp;
public class Enemy extends DestructibleEntity{ public class Enemy extends DestructibleEntity{
public final static Color[] tierColors = {Color.YELLOW, Color.ORANGE, Color.RED, Color.MAGENTA}; public final static Color[] tierColors = {
Color.valueOf("ffe451"),
Color.valueOf("f48e20"),
Color.valueOf("ff6757"),
Color.valueOf("ff2d86")
};
public final static int maxtier = 4; public final static int maxtier = 4;
protected float speed = 0.3f; protected float speed = 0.3f;
@@ -37,11 +39,9 @@ public class Enemy extends DestructibleEntity{
protected String shootsound = "enemyshoot"; protected String shootsound = "enemyshoot";
protected int damage; protected int damage;
public PathFinderRequest<Tile> request = new PathFinderRequest<>();;
public SmoothGraphPath gpath;
public int spawn; public int spawn;
public int node = -1; public int node = -1;
public PathFinder<Tile> finder; public Tile[] path;
public Vector2 direction = new Vector2(); public Vector2 direction = new Vector2();
public float xvelocity, yvelocity; public float xvelocity, yvelocity;
@@ -80,16 +80,23 @@ public class Enemy extends DestructibleEntity{
Vector2 shift = Tmp.v3.setZero(); Vector2 shift = Tmp.v3.setZero();
float shiftRange = hitbox.width + 3f; float shiftRange = hitbox.width + 3f;
float avoidRange = 16f;
float avoidSpeed = 0.1f;
for(SolidEntity other : entities){ for(SolidEntity other : entities){
float dst = other.distanceTo(this); float dst = other.distanceTo(this);
if(other != this && other instanceof Enemy && dst < shiftRange){ if(other != this && other instanceof Enemy){
float scl = Mathf.clamp(1.4f - dst/shiftRange); if(dst < shiftRange){
shift.add((x - other.x) * scl, (y - other.y) * scl); float scl = Mathf.clamp(1.4f - dst/shiftRange);
shift.add((x - other.x) * scl, (y - other.y) * scl);
}else if(dst < avoidRange){
Tmp.v2.set((x - other.x), (y - other.y)).setLength(avoidSpeed);
shift.add(Tmp.v2);
}
} }
} }
shift.nor(); shift.limit(1f);
vec.add(shift.scl(0.5f)); vec.add(shift.scl(0.5f));
move(vec.x*Timers.delta(), vec.y*Timers.delta()); move(vec.x*Timers.delta(), vec.y*Timers.delta());
@@ -127,7 +134,6 @@ public class Enemy extends DestructibleEntity{
public void findClosestNode(){ public void findClosestNode(){
Pathfind.find(this); Pathfind.find(this);
/*
int index = 0; int index = 0;
int cindex = -1; int cindex = -1;
@@ -143,6 +149,8 @@ public class Enemy extends DestructibleEntity{
index ++; index ++;
} }
node = Math.max(cindex, 1);
//set node to that index //set node to that index
node = cindex; node = cindex;
@@ -153,7 +161,7 @@ public class Enemy extends DestructibleEntity{
Timers.run(Mathf.random(15f), ()->{ Timers.run(Mathf.random(15f), ()->{
set(x2 * Vars.tilesize, y2 * Vars.tilesize); set(x2 * Vars.tilesize, y2 * Vars.tilesize);
}); });
}*/ }
} }
@Override @Override

View File

@@ -49,7 +49,7 @@ public enum Weapon{
bullet(p, p.x, p.y, ang + Mathf.range(8)); bullet(p, p.x, p.y, ang + Mathf.range(8));
Effects.effect(Fx.shoot, p.x + vector.x, p.y+vector.y); Effects.effect(Fx.shoot2, p.x + vector.x, p.y+vector.y);
} }
}, },
flamer(5, BulletType.flame, "Shoots a stream of fire.", stack(Item.steel, 60), stack(Item.coal, 60)){ flamer(5, BulletType.flame, "Shoots a stream of fire.", stack(Item.steel, 60), stack(Item.coal, 60)){

View File

@@ -7,6 +7,7 @@ import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import io.anuke.mindustry.Mindustry; import io.anuke.mindustry.Mindustry;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.core.GameState; import io.anuke.mindustry.core.GameState;
import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.resource.Item; import io.anuke.mindustry.resource.Item;
@@ -141,18 +142,22 @@ public class HudFragment implements Fragment{
aleft(); aleft();
new label((StringSupplier)()->"[purple]entities: " + Entities.amount()).left(); new label((StringSupplier)()->"[purple]entities: " + Entities.amount()).left();
row(); row();
new label((StringSupplier)()->"[orange]noclip: " + Vars.noclip).left();
row();
new label("[red]DEBUG MODE").scale(0.5f).left(); new label("[red]DEBUG MODE").scale(0.5f).left();
}}.end(); }}.end();
new table(){{ if(profile){
atop(); new table(){{
new table("button"){{
defaults().left().growX();
atop(); atop();
aleft(); new table("button"){{
new label((StringSupplier)()->Profiler.formatDisplayTimes()); defaults().left().growX();
}}.width(400f).end(); atop();
}}.end(); aleft();
new label((StringSupplier)()->Profiler.formatDisplayTimes());
}}.width(400f).end();
}}.end();
}
} }
} }

View File

@@ -150,6 +150,8 @@ public class World{
World.seed = seed; World.seed = seed;
Generator.generate(mapPixmaps[map.ordinal()]); Generator.generate(mapPixmaps[map.ordinal()]);
Pathfind.reset();
//TODO multiblock core //TODO multiblock core
placeBlock(core.x, core.y, ProductionBlocks.core, 0); placeBlock(core.x, core.y, ProductionBlocks.core, 0);