2019-08-18 15:14:35 +02:00
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Linq;
|
|
|
|
using Microsoft.Xna.Framework;
|
|
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
|
|
using MLEM.Extensions;
|
|
|
|
using MLEM.Input;
|
2020-01-30 00:55:02 +01:00
|
|
|
using MLEM.Misc;
|
2019-08-18 15:14:35 +02:00
|
|
|
using MLEM.Pathfinding;
|
|
|
|
using MLEM.Startup;
|
2020-01-30 00:55:02 +01:00
|
|
|
using MLEM.Textures;
|
2019-08-18 15:14:35 +02:00
|
|
|
|
|
|
|
namespace Demos {
|
2019-09-01 11:55:41 +02:00
|
|
|
public class PathfindingDemo : Demo {
|
2019-08-18 15:14:35 +02:00
|
|
|
|
|
|
|
private bool[,] world;
|
|
|
|
private AStar2 pathfinder;
|
|
|
|
private List<Point> path;
|
|
|
|
|
2019-09-01 11:55:41 +02:00
|
|
|
public PathfindingDemo(MlemGame game) : base(game) {
|
|
|
|
}
|
2020-01-30 00:55:02 +01:00
|
|
|
|
2020-02-06 17:43:34 +01:00
|
|
|
private async void Init() {
|
|
|
|
this.path = null;
|
|
|
|
|
2019-08-18 15:14:35 +02:00
|
|
|
// 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 float.MaxValue, 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
|
|
|
|
AStar<Point>.GetCost cost = (pos, nextPos) => {
|
|
|
|
if (nextPos.X < 0 || nextPos.Y < 0 || nextPos.X >= 50 || nextPos.Y >= 50)
|
|
|
|
return float.MaxValue;
|
|
|
|
return this.world[nextPos.X, nextPos.Y] ? 1 : float.MaxValue;
|
|
|
|
};
|
|
|
|
// 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)
|
2020-02-06 17:43:34 +01:00
|
|
|
var foundPath = await this.pathfinder.FindPathAsync(Point.Zero, new Point(49, 49));
|
2019-08-18 15:14:35 +02:00
|
|
|
this.path = foundPath != null ? foundPath.ToList() : null;
|
|
|
|
|
|
|
|
// print out some info
|
2020-01-30 00:55:02 +01:00
|
|
|
Console.WriteLine("Pathfinding took " + this.pathfinder.LastTriesNeeded + " tries and " + this.pathfinder.LastTimeNeeded.TotalSeconds + " seconds");
|
2020-01-30 01:08:10 +01:00
|
|
|
if (this.path == null) {
|
|
|
|
Console.WriteLine("Couldn't find a path, trying again");
|
|
|
|
this.Init();
|
|
|
|
}
|
2019-08-18 15:14:35 +02:00
|
|
|
}
|
|
|
|
|
2019-09-01 11:55:41 +02:00
|
|
|
public override void LoadContent() {
|
2019-08-18 15:14:35 +02:00
|
|
|
base.LoadContent();
|
|
|
|
this.Init();
|
|
|
|
}
|
|
|
|
|
2019-09-01 11:55:41 +02:00
|
|
|
public override void Update(GameTime gameTime) {
|
2019-08-18 15:14:35 +02:00
|
|
|
base.Update(gameTime);
|
|
|
|
|
|
|
|
// when pressing the left mouse button, generate a new world and find a new path
|
2019-09-01 11:55:41 +02:00
|
|
|
if (this.InputHandler.IsMouseButtonPressed(MouseButton.Left)) {
|
2019-08-18 15:14:35 +02:00
|
|
|
this.Init();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-01 11:55:41 +02:00
|
|
|
public override void DoDraw(GameTime gameTime) {
|
2019-08-18 15:14:35 +02:00
|
|
|
this.GraphicsDevice.Clear(Color.White);
|
|
|
|
|
|
|
|
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, transformMatrix: Matrix.CreateScale(14));
|
2020-01-30 00:55:02 +01:00
|
|
|
var tex = this.SpriteBatch.GetBlankTexture();
|
2019-08-18 15:14:35 +02:00
|
|
|
// 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]) {
|
2020-01-30 00:55:02 +01:00
|
|
|
this.SpriteBatch.Draw(tex, new Rectangle(x, y, 1, 1), Color.Black);
|
2019-08-18 15:14:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// 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++) {
|
2020-01-30 01:08:10 +01:00
|
|
|
var (firstX, firstY) = this.path[i - 1];
|
|
|
|
var (secondX, secondY) = this.path[i];
|
|
|
|
this.SpriteBatch.Draw(tex, RectangleF.FromCorners(new Vector2(firstX + 0.25F, firstY + 0.25F), new Vector2(secondX + 0.75F, secondY + 0.75F)), Color.Blue);
|
2019-08-18 15:14:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
this.SpriteBatch.End();
|
|
|
|
|
|
|
|
base.DoDraw(gameTime);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|