/
PathfindingDemo.cs
106 lines (91 loc) · 4.59 KB
/
PathfindingDemo.cs
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MLEM.Extensions;
using MLEM.Misc;
using MLEM.Pathfinding;
using MLEM.Startup;
using MLEM.Ui;
using MLEM.Ui.Elements;
namespace Demos {
public class PathfindingDemo : Demo {
private bool[,] world;
private AStar2 pathfinder;
private List<Point> path;
private Button regenerateButton;
public PathfindingDemo(MlemGame game) : base(game) {}
private async void Init() {
this.path = null;
// generate a simple random world for testing, where true is walkable area, and false is a wall
var random = new Random();
this.world = new bool[50, 50];
for (var x = 0; x < 50; x++) {
for (var y = 0; y < 50; y++) {
if (random.NextDouble() >= 0.25)
this.world[x, y] = true;
}
}
// Create a cost function, which determines how expensive (or difficult) it should be to move from a given position
// to the next, adjacent position. In our case, the only restriction should be walls and out-of-bounds positions, which
// both have a cost of AStar2.InfiniteCost, meaning they are completely unwalkable.
// If your game contains harder-to-move-on areas like, say, a muddy pit, you can return a higher cost value for those
// locations. If you want to scale your cost function differently, you can specify a different default cost in your
// pathfinder's constructor
float Cost(Point pos, Point nextPos) {
if (nextPos.X < 0 || nextPos.Y < 0 || nextPos.X >= 50 || nextPos.Y >= 50)
return float.PositiveInfinity;
return this.world[nextPos.X, nextPos.Y] ? 1 : float.PositiveInfinity;
}
// Actually initialize the pathfinder with the cost function, as well as specify if moving diagonally between tiles should be
// allowed or not (in this case it's not)
this.pathfinder = new AStar2(Cost, false);
// Now find a path from the top left to the bottom right corner and store it in a variable
// If no path can be found after the maximum amount of tries (10000 by default), the pathfinder will abort and return no path (null)
var foundPath = await Task.Run(() => this.pathfinder.FindPath(Point.Zero, new Point(49, 49)));
this.path = foundPath != null ? foundPath.ToList() : null;
// print out some info
Console.WriteLine("Pathfinding took " + this.pathfinder.LastTriesNeeded + " tries and " + this.pathfinder.LastTimeNeeded.TotalSeconds + " seconds");
if (this.path == null) {
Console.WriteLine("Couldn't find a path, trying again");
this.Init();
}
}
public override void LoadContent() {
this.regenerateButton = new Button(Anchor.TopCenter, new Vector2(30, 10), "Regenerate") {
OnPressed = e => this.Init()
};
this.UiRoot.AddChild(this.regenerateButton);
this.Init();
}
public override void DoDraw(GameTime gameTime) {
this.GraphicsDevice.Clear(Color.White);
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null, null, Matrix.CreateScale(14));
var tex = this.SpriteBatch.GetBlankTexture();
// draw the world with simple shapes
for (var x = 0; x < 50; x++) {
for (var y = 0; y < 50; y++) {
if (!this.world[x, y]) {
this.SpriteBatch.Draw(tex, new Rectangle(x, y, 1, 1), Color.Black);
}
}
}
// draw the path
// in a real game, you'd obviously make your characters walk along the path instead of drawing it
if (this.path != null) {
for (var i = 1; i < this.path.Count; i++) {
var first = this.path[i - 1];
var second = this.path[i];
this.SpriteBatch.Draw(tex, RectangleF.FromCorners(new Vector2(first.X + 0.25F, first.Y + 0.25F), new Vector2(second.X + 0.75F, second.Y + 0.75F)), Color.Blue);
}
}
this.SpriteBatch.End();
base.DoDraw(gameTime);
}
public override void Clear() {
this.UiRoot.RemoveChild(this.regenerateButton);
}
}
}