Files
GDevelop/Extensions/PathfindingBehavior/tests/commonpathfindingruntimebehavior.spec.js
T
D8H fa40299343 Remove an only in tests (#3474)
Don't show in changelog
2022-01-11 13:28:50 +00:00

297 lines
10 KiB
JavaScript

// @ts-check
describe('gdjs.PathfindingRuntimeBehavior', function () {
const epsilon = 1 / (2 << 16);
// tests cases where every collisionMethod has the same behavior.
let doCommonPathFindingTests = (collisionMethod, allowDiagonals) => {
const pathFindingName = 'auto1';
const createScene = (framePerSecond = 60) => {
const runtimeGame = new gdjs.RuntimeGame({
variables: [],
// @ts-ignore - missing properties.
properties: { windowWidth: 800, windowHeight: 600 },
resources: { resources: [] },
});
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
runtimeScene.loadFromScene({
layers: [
{
name: '',
visibility: true,
effects: [],
cameras: [],
ambientLightColorR: 0,
ambientLightColorG: 0,
ambientLightColorB: 0,
isLightingLayer: false,
followBaseLayerCamera: true,
},
],
variables: [],
r: 0,
v: 0,
b: 0,
mangledName: 'Scene1',
name: 'Scene1',
stopSoundsOnStartup: false,
title: '',
behaviorsSharedData: [],
objects: [],
instances: [],
});
setFramePerSecond(runtimeScene, framePerSecond);
return runtimeScene;
};
const setFramePerSecond = (runtimeScene, framePerSecond) => {
runtimeScene._timeManager.getElapsedTime = function () {
return 1000 / framePerSecond;
};
};
const addPlayer = (runtimeScene) => {
const player = new gdjs.RuntimeObject(runtimeScene, {
name: 'player',
type: '',
behaviors: [
{
type: 'PathfindingBehavior::PathfindingBehavior',
name: 'auto1',
// @ts-ignore - properties are not typed
allowDiagonals: allowDiagonals,
acceleration: 400,
maxSpeed: 200,
angularMaxSpeed: 180,
rotateObject: false,
angleOffset: 0,
cellWidth: 20,
cellHeight: 20,
extraBorder: 0,
collisionMethod: collisionMethod,
},
],
effects: [],
});
player.getWidth = function () {
return 90;
};
player.getHeight = function () {
return 90;
};
runtimeScene.addObject(player);
return player;
};
const addObstacle = (runtimeScene) => {
const obstacle = new gdjs.RuntimeObject(runtimeScene, {
name: 'obstacle',
type: '',
behaviors: [
{
type: 'PathfindingBehavior::PathfindingObstacleBehavior',
// @ts-ignore - properties are not typed
impassable: true,
cost: 2,
},
],
effects: [],
});
obstacle.getWidth = function () {
return 100;
};
obstacle.getHeight = function () {
return 100;
};
runtimeScene.addObject(obstacle);
return obstacle;
};
let runtimeScene;
let player;
beforeEach(function () {
runtimeScene = createScene();
player = addPlayer(runtimeScene);
});
it('can find a path without any obstacle at all', function () {
player.setPosition(480, 300);
player.getBehavior(pathFindingName).moveTo(runtimeScene, 720, 300);
expect(player.getBehavior(pathFindingName).pathFound()).to.be(true);
expect(player.getBehavior(pathFindingName).getNodeCount()).to.be(13);
});
it('can find a path without any obstacle in the way', function () {
const obstacle = addObstacle(runtimeScene);
obstacle.setPosition(100, 100);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / 60);
player.setPosition(480, 300);
player.getBehavior(pathFindingName).moveTo(runtimeScene, 720, 300);
expect(player.getBehavior(pathFindingName).pathFound()).to.be(true);
expect(player.getBehavior(pathFindingName).getNodeCount()).to.be(13);
});
it("mustn't find a path to the obstacle inside", function () {
const obstacle = addObstacle(runtimeScene);
obstacle.setPosition(600, 300);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / 60);
player.setPosition(480, 300);
player.getBehavior(pathFindingName).moveTo(runtimeScene, 650, 350);
expect(player.getBehavior(pathFindingName).pathFound()).to.be(false);
});
it('can find a path with an obstacle in the way', function () {
const obstacle = addObstacle(runtimeScene);
obstacle.setPosition(600, 300);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / 60);
player.setPosition(480, 300);
player.getBehavior(pathFindingName).moveTo(runtimeScene, 720, 300);
expect(player.getBehavior(pathFindingName).pathFound()).to.be(true);
expect(player.getBehavior(pathFindingName).getNodeCount()).to.be.above(
13
);
});
if (allowDiagonals) {
[20, 30, 60, 120].forEach((framePerSecond) => {
describe(`(${framePerSecond} fps)`, function () {
it('can move on the path at the right speed', function () {
setFramePerSecond(runtimeScene, framePerSecond);
const obstacle = addObstacle(runtimeScene);
obstacle.setPosition(600, 300);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / framePerSecond);
player.setPosition(480, 300);
player.getBehavior(pathFindingName).moveTo(runtimeScene, 720, 300);
expect(player.getBehavior(pathFindingName).pathFound()).to.be(true);
expect(
player.getBehavior(pathFindingName).getNodeCount()
).to.be.above(13);
// Move on the path and stop before the last 1/10 of second.
for (let i = 0; i < (framePerSecond * 17) / 10; i++) {
runtimeScene.renderAndStep(1000 / framePerSecond);
expect(
player.getBehavior(pathFindingName).destinationReached()
).to.be(false);
}
// The position is the same no matter the frame rate.
expect(player.getX()).to.be(720);
expect(player.getY()).to.be.within(
288.5786437626905 - epsilon,
288.5786437626905 + epsilon
);
// Let 1/10 of second pass,
// because the calculus interval is not the same for each case.
for (let i = 0; i < framePerSecond / 10; i++) {
runtimeScene.renderAndStep(1000 / framePerSecond);
}
// The destination is reached for every frame rate within 1/10 of second.
expect(player.getX()).to.be(720);
expect(player.getY()).to.be(300);
expect(
player.getBehavior(pathFindingName).destinationReached()
).to.be(true);
});
});
});
} else {
[20, 30, 60, 120].forEach((framePerSecond) => {
describe(`(${framePerSecond} fps)`, function () {
it('can move on the path at the right speed', function () {
setFramePerSecond(runtimeScene, framePerSecond);
const obstacle = addObstacle(runtimeScene);
obstacle.setPosition(600, 300);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / framePerSecond);
player.setPosition(480, 300);
player.getBehavior(pathFindingName).moveTo(runtimeScene, 720, 300);
expect(player.getBehavior(pathFindingName).pathFound()).to.be(true);
expect(
player.getBehavior(pathFindingName).getNodeCount()
).to.be.above(13);
// Move on the path and stop before the last 1/10 of second.
for (let i = 0; i < (framePerSecond * 20) / 10; i++) {
runtimeScene.renderAndStep(1000 / framePerSecond);
expect(
player.getBehavior(pathFindingName).destinationReached()
).to.be(false);
}
expect(player.getX()).to.be(710);
expect(player.getY()).to.be.within(300 - epsilon, 300 + epsilon);
// Let 1/10 of second pass,
// because the calculus interval is not the same for each case.
for (let i = 0; i < (framePerSecond * 1) / 10; i++) {
runtimeScene.renderAndStep(1000 / framePerSecond);
}
// The destination is reached for every frame rate within 1/10 of second.
expect(player.getX()).to.be(720);
expect(player.getY()).to.be(300);
expect(
player.getBehavior(pathFindingName).destinationReached()
).to.be(true);
});
});
});
}
it('can find a path between 2 obstacles', function () {
const obstacleTop = addObstacle(runtimeScene);
const obstacleBottom = addObstacle(runtimeScene);
obstacleTop.setPosition(600, 180);
obstacleBottom.setPosition(600, 420);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / 60);
player.setPosition(480, 300);
player.getBehavior(pathFindingName).moveTo(runtimeScene, 720, 300);
expect(player.getBehavior(pathFindingName).pathFound()).to.be(true);
expect(player.getBehavior(pathFindingName).getNodeCount()).to.be(13);
});
it("mustn't find a path to a closed room", function () {
const obstacleTop = addObstacle(runtimeScene);
const obstacleBottom = addObstacle(runtimeScene);
const obstacleLeft = addObstacle(runtimeScene);
const obstacleRight = addObstacle(runtimeScene);
obstacleTop.setPosition(600, 180);
obstacleBottom.setPosition(600, 420);
obstacleLeft.setPosition(480, 300);
obstacleRight.setPosition(720, 300);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / 60);
player.setPosition(360, 300);
player.getBehavior(pathFindingName).moveTo(runtimeScene, 600, 300);
expect(player.getBehavior(pathFindingName).pathFound()).to.be(false);
});
};
['Legacy'].forEach((collisionMethod) => {
describe(`(collisionMethod: ${collisionMethod}, `, function () {
[false, true].forEach((allowDiagonals) => {
describe(`(allowDiagonals: ${allowDiagonals})`, function () {
doCommonPathFindingTests(collisionMethod, allowDiagonals);
});
});
});
});
});