Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 3 months ago.
Improve this question
I have a game and I want to be able to have enemies pathfind to the player across a tilemap, is there a way to do this without programming my own a* algorithm? I've seen a couple supposedly still updated libraries for this, but neither of which have worked, they have both been broken with npm and not worked when added as a script. The libraries I've tried are Easystar and Navmesh.
Any suggestions would be great, and while Phaser support would be useful, pathfinding over a 2D array would still be a good solution for me. Thanks!
I never tried it myself (until now), but https://github.com/mikewesthad/navmesh is a great plugin and is very extensive.
... A JS plugin for fast pathfinding using navigation meshes, with optional wrappers for the Phaser v2 and Phaser v3 game engines. ...
p.s.: on this page you can find a list of available plugins for phaser https://phaserplugins.com/
Update:
After testing in on an simple an example it worked fine, just be careful not to miss any steps.
Here the steps needed:
get the latest plugin file phaser-navmesh-plugin.js (currently from https://github.com/mikewesthad/navmesh/releases/tag/2.1.0)
load it from the html file
<script src="phaser-navmesh-plugin.js"></script>
configure the plugin, in the config (there are other ways but this is the more convinient)
var config = {
...
plugins: {
scene: [
{
key: "PhaserNavMeshPlugin", // Key to store the plugin class under in cache
plugin: PhaserNavMeshPlugin, // Class that constructs plugins
mapping: "navMeshPlugin", // Property mapping to use for the scene, e.g. this.navMeshPlugin
start: true
}
]
},
scene: {
...
}
};
setup the map and the layer which should collide:
var map = this.make.tilemap({ data: [[0,1,0],[0,1,0],[0,1,0],[0,0,0]], tileWidth: 8, tileHeight: 8 });
var tiles = map.addTilesetImage('tile01');
var layer = map.createLayer(0, tiles, 0, 0);
// this is important, can also be multiple indices
layer.setCollision(1);
create navMesh, pass the map and the created layer with should have to collisions.
const navMesh = this.navMeshPlugin.buildMeshFromTilemap("mesh", map, [layer]);
when needed execute pathfinding (x/y positions are in pixels, not tilesId):
const path = navMesh.findPath({ x: 4, y: 4 }, { x: 17, y: 4 });
You can load the plugin in also in the preload function like this
this.load.scenePlugin({
key: 'PhaserNavMeshPlugin',
url: PhaserNavMeshPlugin,
sceneKey: 'navMeshPlugin'
});
Here the demo code with a mini path trace when done:
var config = {
scene: {
preload,
create,
}
};
var game = new Phaser.Game(config);
function preload() {
this.load.image('tile01', 'tile01.png');
this.load.scenePlugin({
key: 'PhaserNavMeshPlugin',
url: PhaserNavMeshPlugin,
sceneKey: 'navMeshPlugin'
});
}
function create() {
var map = this.make.tilemap({ data: [[0,1,0],[0,1,0],[0,1,0],[0,0,0]], tileWidth: 8, tileHeight: 8 });
var tiles = map.addTilesetImage('tile01');
var layer = map.createLayer(0, tiles, 0, 0);
layer.setCollision(1);
const navMesh = this.navMeshPlugin.buildMeshFromTilemap("mesh", map, [layer]);
const path = navMesh.findPath({ x: 4, y: 4 }, { x: 17, y: 4 });
if (!path) {
return;
}
let graphics = this.add.graphics();
graphics.lineStyle(2, 0xff0000, 1);
graphics.beginPath();
graphics.moveTo(path[0].x, path[0].y);
for (let idx = 1; idx < path.length; idx++) {
graphics.lineTo(path[idx].x, path[idx].y);
}
graphics.strokePath();
}
Related
Is it possible to create a single gravity / force point in matter.js that is at the center of x/y coordinates?
I have managed to do it with d3.js but wanted to enquire about matter.js as it has the ability to use multiple polyshapes.
http://bl.ocks.org/mbostock/1021841
The illustrious answer has arisen:
not sure if there is any interest in this. I'm a fan of what you have created. In my latest project, I used matter-js but I needed elements to gravitate to a specific point, rather than into a general direction. That was very easily accomplished. I was wondering if you are interested in that feature as well, it would not break anything.
All one has to do is setting engine.world.gravity.isPoint = true and then the gravity vector is used as point, rather than a direction. One might set:
engine.world.gravity.x = 355;
engine.world.gravity.y = 125;
engine.world.gravity.isPoint = true;
and all objects will gravitate to that point.
If this is not within the scope of this engine, I understand. Either way, thanks for the great work.
You can do this with the matter-attractors plugin. Here's their basic example:
Matter.use(
'matter-attractors' // PLUGIN_NAME
);
var Engine = Matter.Engine,
Events = Matter.Events,
Runner = Matter.Runner,
Render = Matter.Render,
World = Matter.World,
Body = Matter.Body,
Mouse = Matter.Mouse,
Common = Matter.Common,
Bodies = Matter.Bodies;
// create engine
var engine = Engine.create();
// create renderer
var render = Render.create({
element: document.body,
engine: engine,
options: {
width: Math.min(document.documentElement.clientWidth, 1024),
height: Math.min(document.documentElement.clientHeight, 1024),
wireframes: false
}
});
// create runner
var runner = Runner.create();
Runner.run(runner, engine);
Render.run(render);
// create demo scene
var world = engine.world;
world.gravity.scale = 0;
// create a body with an attractor
var attractiveBody = Bodies.circle(
render.options.width / 2,
render.options.height / 2,
50,
{
isStatic: true,
// example of an attractor function that
// returns a force vector that applies to bodyB
plugin: {
attractors: [
function(bodyA, bodyB) {
return {
x: (bodyA.position.x - bodyB.position.x) * 1e-6,
y: (bodyA.position.y - bodyB.position.y) * 1e-6,
};
}
]
}
});
World.add(world, attractiveBody);
// add some bodies that to be attracted
for (var i = 0; i < 150; i += 1) {
var body = Bodies.polygon(
Common.random(0, render.options.width),
Common.random(0, render.options.height),
Common.random(1, 5),
Common.random() > 0.9 ? Common.random(15, 25) : Common.random(5, 10)
);
World.add(world, body);
}
// add mouse control
var mouse = Mouse.create(render.canvas);
Events.on(engine, 'afterUpdate', function() {
if (!mouse.position.x) {
return;
}
// smoothly move the attractor body towards the mouse
Body.translate(attractiveBody, {
x: (mouse.position.x - attractiveBody.position.x) * 0.25,
y: (mouse.position.y - attractiveBody.position.y) * 0.25
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.12.0/matter.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/matter-attractors#0.1.6/build/matter-attractors.min.js"></script>
Historical note: the "gravity point" functionality was proposed as a feature in MJS as PR #132 but it was closed, with the author of MJS (liabru) offering the matter-attractors plugin as an alternate. At the time of writing, this answer misleadingly seems to indicate that functionality from the PR was in fact merged.
Unfortunately, the attractors library is 6 years outdated at the time of writing and raises a warning when using a newer version of MJS than 0.12.0. From discussion in issue #11, it sounds like it's OK to ignore the warning and use this plugin with, for example, 0.18.0. Here's the warning:
matter-js: Plugin.use: matter-attractors#0.1.4 is for matter-js#^0.12.0 but installed on matter-js#0.18.0.
Behavior seemed fine on cursory glance, but I'll keep 0.12.0 in the above example to silence it anyway. If you do update to a recent version, note that Matter.World is deprecated and should be replaced with Matter.Composite and engine.gravity.
[Duplicate] I am asking this question again as my original question was incorrectly closed by linking the question below to mine, but the provided solution doesn't answer my question.
Previously provided "solution": (Getting waypoints from leaflet routing machine) - This question does not answer my question as the answer only returns the waypoints that I have already provided, it doesn't return the points between the waypoints that comprise the route.
My original question is here: How can I fetch all points along route between waypoints?
I want to animate a marker along a route. I've been able to animate a marker successfully between waypoints but I want to fetch the points between two waypoints programmatically so that I can use a function to automatically set a route and animate along the route.
Two waypoints will be known, i.e. Point A and Point B. What I want is to be able to get all points between Point A and Point B automatically so that the marker is animated along the route instead of as the crow flies. I can animate the route successfully by entering the desired waypoints manually so I know that this will work if I can retrieve the points between the waypoints.
Here's my code so far:
// Draw route
var routeData = L.Routing.control({
router: L.Routing.mapbox(L.mapbox.accessToken,{
profile : 'mapbox/driving',
language: 'en',
}),
waypoints: [
L.latLng(52.501737, -2.119792),
L.latLng(52.498798, -2.102591)
],
lineOptions:{
styles: [
{
color: '#006a4e', opacity: 1, weight: 5
}
],
},
draggableWaypoints: false,
createMarker: function() { return false; },
show: false,
}).addTo(mymap);
var routeArray = new Array();
routeArray = routeData.getWaypoints();
console.log(routeArray);
// Draw animation
L.motion.polyline([[52.501737, -2.119792], [52.501267, -2.114707], [52.500313, -2.110361], [52.499243, -2.108751], [52.498596, -2.105886], [52.498812, -2.104953], [52.498798, -2.102591]], {
color: "transparent"
}, {
auto: true,
duration: 30000,
easing: L.Motion.Ease.easeInOutQuart
}, {
removeOnEnd: false,
showMarker: true,
icon: L.icon({iconUrl: 'marker.png', iconSize: [32,37]})
}).addTo(mymap);
The plan is to find the points between the waypoints, enter them into an array and explode the array within the polyline, like so:
var pointsArray = new Array(???);
L.motion.polyline(pointsArray,...
I just don't know how to get the data for pointsArray. I'm using Leaflet Routing Machine with MapBox data.
Any help?
I hope I've explained myself clearly enough.
I'm working on a project and I need to add 3d sounds effects, like the sound is continually moving around the listener effects. Is it possible to achieve that with howlerjs i see that with howler i'm able to play a sound from specific coordinates/orientation but how to achieve surrounding/ambisonics sounds ?
Or another library in JavaScript to achieve that?
Thanks for your help.
Half a year late, but yeah that's entirely possible in howler.js, haven't used it myself but judging from the docs you can just update the position. there's some more libraries that do it that I've found, check here how 3dage does exactly what you want:
https://codepen.io/naugtur/pen/QgmvOB?editors=1010
var world = IIIdage.World({
tickInterval: 200
})
var annoyingFly = IIIdage.Thing({
is: ['fly'],
sounds: {
'buzzing constantly': {
sound: 'buzz',
times: Infinity
}
},
reacts: [
{
// to: world.random.veryOften(),
to: world.time.once(),
with: 'buzzing constantly'
}
]
})
// scene should create and expose a default world or accept one
var scene = IIIdage.Scene({
title: 'Annoying fly',
library: {
sounds: {
'buzz': {
src: ['https://webaudiogaming.github.io/3dage/fly.mp3']
}
}
},
world: world,
things: [ // scene iterates all things and spawns them into the world. same can be done manually later on.
annoyingFly({
pos: [-1, -15, 0],
dir: [1, 0, 0],
v: 1
})
]
}).load().run()
setTimeout(function () {
scene.dev.trace(IIIdage.dev.preview.dom())
}, 500)
setInterval(function rotateVector() {
var angleRad = 0.15
var d=scene.things[0].attributes.dir
var x=d[0], y=d[1]
var cosAngle = Math.cos(angleRad), sinAngle = Math.sin(angleRad)
scene.things[0].attributes.dir = [x * cosAngle - y * sinAngle, y * cosAngle + x * sinAngle, 0]
}, 500)
window.scene = scene
There's still some others that do similar stuff:
https://www.npmjs.com/package/songbird-audio
https://www.npmjs.com/package/ambisonics
Hope this pushes you in the right direction if you still want help with it.
I found an interesting demo of how to find the largest rectangle in an irregular shaped polygon here using D3plus.
I'm trying to recreate this for a polygon I'm working on but currently the code is not working. It seems to runs endlessly. The code I'm using is as follows:
d3.csv("data/polyPoints.csv", function(error, polyPoints) {
if (error) return console.error(error);
// coerce string values to numbers
polyPoints.forEach(function(d) {
d3.keys(d).forEach(function(k) {
d[k] = +d[k]
})
});
// settings for geom.largestRect
var rectOptions = {
angle: 0,
maxAspectRatio: 5,
nTries: 1
};
console.log(rectOptions);
console.log(polyPoints);
var lRect = d3plus.geom.largestRect(polyPoints, rectOptions);
console.log(lRect);
});
I suspect my polygon is not in the correct format.
Update
I'm making progress. My original polygon object was taken from a csv and created an array of arrays of key value pairs (e.g. {"x": 0 "y": 1},{"x": 2, "y": 1}....)
I converted this to an array of arrays (e.g. [[1,0],[2,0]....])
Now the code is running but the output is defining rectangles that cross the boundary of the original polygon.
For anyone working with this. The largestRect docs are https://d3plus.org/docs/#largestRect and can be run with the following code.
const d3p = require('d3plus');
const polygon = [[x,y],[x,y],[x,y]...]
const rectOptions = {
maxAspectRatio: 5,
nTries: 20
};
let lRect = d3p.largestRect(rdp, rectOptions);
The algorithm used is an approximation and random points inside the polygon are chosen to do calculations from. Because of this the edges of the box won't always be touching the edge but should be "close enough".
The options.tolerance value might affect this as well but I haven't played around with it much. This is a pretty old question but hopefully it helps someone.
What i have done so far:
i'm developing an application where i have to display more than(50K) points/Markers on the Navteq map divided into different segments.
for example: if i have 50K points i will divide all points into different segments.
if i divide 50K points into 50 segments each segment would have 1000 points (may not be 50 segments , it may depend).
right now it is working but it takes long time and hangs to render all the points on the MAP.so that i would like to perform segmentation displaying to display only few points with clustering.
so that i can get an idea of how the segment will look like.
but the problem here is i should only perform the clustering based on the segments.otherwise points from different segments willbe mixed together and displayed
as single unit and that conveys the wrong information to the user.
so here my question is: is it possible to perform the clustering based on the segment. so that only points from same segment will be clustered.
Note: if this is not possible, i would like to use Latest version of here-maps 2.5.3 (Asynchronous) may reduce some time while loading, so that i would like to use indexing functionality also while rendering the points
to improve the rendering time using nokia.maps.clustering.Index class.
i studied that indexing would reduce the time while rendering the points/markers on map. does it help in my case? could anybody please suggest how to perform indexing ?
This is the code with which i'm displaying points on map:
function displayAllLightPoints(arrLightPointCoordinats, totalLightPoints,
selectedSegmentId, totalSegmentsCount,segmentColorcode)
{
var MyTheme1 = function () {
};
segmentColorcode = segmentColorcode.substring(2,segmentColorcode.length-1);
MyTheme1.prototype.getNoisePresentation = function (dataPoint) {
var markerLightPoint = new nokia.maps.map.Marker(dataPoint, {
icon: new nokia.maps.gfx.BitmapImage("..//Images//Lightpoint//" +
segmentColorcode + ".png"),
anchor: {
x: 12,
y: 12
}
});
return markerLightPoint;
};
MyTheme1.prototype.getClusterPresentation = function (data) {
var markerLightPoint = new
nokia.maps.map.StandardMarker(data.getBounds().getCenter(), {
icon: new nokia.maps.gfx.BitmapImage("..//Images//
Segment/" + segmentColorcode + ".png", null, 66, 65),
text: data.getSize(),
zIndex: 2,
anchor: {
x: 12,
y: 12
}
});
return markerLightPoint;
};
var ClusterProvider = nokia.maps.clustering.ClusterProvider,
theme = new MyTheme1(),
clusterProvider = new ClusterProvider(map, {
eps: 0.00000000001,
minPts: 1000000,
strategy: nokia.maps.clustering.ClusterProvider.
STRATEGY_DENSITY_BASED,
theme: theme,
dataPoints: []
});
var lightpointsDataSet1 = new Array();
for (var i = 0; i < totalLightPoints; i++) {
lightpointsDataSet1[i] = { latitude: arrLightPointCoordinats[i][0],
longitude: arrLightPointCoordinats[i][1], title:
'LightPoint ' + (i + 1) };
}
clusterProvider.addAll(lightpointsDataSet1);
clusterProvider.cluster();
}
To deal with a very large (50K+) data set , I would do all the heavy number crunching server side and send over a new JSON response whenever the map is updated. Something like the HTML page described here
The key section of the code is the ZoomObserver:
var zoomObserver = function (obj, key, newValue, oldValue) {
zoom = newValue;
if (zoom < 7)
{ zoom = 7;}
if (zoom > 16)
{ zoom = 16;}
// Define the XML filename to read that contains the marker data
placeMarkersOnMaps('http://api.maps.nokia.com/downloads/java-me/cluster/'+ zoom + '.xml'
+ '?lat1=' + map.getViewBounds().topLeft.latitude
+ '&lng1='+ map.getViewBounds().topLeft.longitude
+ '&lat2='+ map.getViewBounds().bottomRight.latitude
+ '&lng2='+ map.getViewBounds().bottomRight.longitude);
};
map.addObserver("zoomLevel", zoomObserver );
Where the REST service returns a "well-known" data format which can be used to add markers and clusters to the map.
Now assuming you have two massive data sets you could make two requests to different endpoints, or somehow distinguish which cluster of data belongs to which so that you would just be returning information of the form:
{latitude':51.761,'longitude':14.33128,'value':102091},
i.e. using the DataPoint standard (which means you could use a heat map as well.
Of course, what I'm not showing here is the back-end functionality to cluster in the first place - but this leaves the client (and the API) to do what it does best displaying data, not number crunching.