Files
GDevelop/Extensions/PathfindingBehavior/tests/legacypathfindingruntimebehavior.spec.js
T
Harsimran Singh Virk 599ccb677f Add support for visual effects ("shaders") on objects. (#2901)
* This allows to generate interesting visual effects, which can be controlled by events. For example, you can use an outline on an object to highlight, make the player glow when launching a spell, blur objects, etc...
* A new "effects" tab is now present in the objects editor. From there, you can add visual effects, that were already available for layers, customize them from this editor.
* Actions and conditions are available to manipulate effects and change their parameters during the game.
* Learn more on the wiki: http://wiki.compilgames.net/doku.php/gdevelop5/objects/effects

Co-authored-by: Florian Rival <Florian.Rival@gmail.com>
2021-08-20 15:01:06 +02:00

758 lines
24 KiB
JavaScript

// @ts-check
describe('gdjs.PathfindingRuntimeBehavior', function () {
// limit tests cases on the legacy collision methods.
let doLegacyPathFindingTests = (
cellSize,
objectCenteredOnCells,
direction
) => {
const pathFindingName = 'auto1';
let createScene = () => {
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: [],
});
runtimeScene._timeManager.getElapsedTime = function () {
return (1 / 60) * 1000;
};
return runtimeScene;
};
let addPlayer = (runtimeScene) => {
const player = new gdjs.SpriteRuntimeObject(runtimeScene, {
name: 'player',
type: '',
animations: [
{
name: 'animation',
directions: [
{
sprites: [
{
originPoint: objectCenteredOnCells
? { x: 80, y: 80 }
: { x: 87, y: 87 },
centerPoint: { x: 80, y: 80 },
points: [
{ name: 'Center', x: 80, y: 80 },
objectCenteredOnCells
? { name: 'Origin', x: 80, y: 80 }
: { name: 'Origin', x: 87, y: 87 },
],
hasCustomCollisionMask: false,
},
],
},
],
},
],
effects: [],
behaviors: [
{
type: 'PathfindingBehavior::PathfindingBehavior',
name: 'auto1',
allowDiagonals: false,
acceleration: 400,
maxSpeed: 200,
angularMaxSpeed: 180,
rotateObject: false,
angleOffset: 0,
cellWidth: cellSize,
cellHeight: cellSize,
extraBorder: 0,
},
],
});
player.getWidth = function () {
return 160;
};
player.getHeight = function () {
return 160;
};
runtimeScene.addObject(player);
return player;
};
let addObstacle = (runtimeScene) => {
const obstacle = new gdjs.SpriteRuntimeObject(runtimeScene, {
name: 'obstacle',
type: '',
animations: [
{
name: 'animation',
directions: [
{
sprites: [
{
originPoint: objectCenteredOnCells
? { x: 80, y: 80 }
: { x: 87, y: 87 },
centerPoint: { x: 80, y: 80 },
points: [
{ name: 'Center', x: 80, y: 80 },
objectCenteredOnCells
? { name: 'Origin', x: 80, y: 80 }
: { name: 'Origin', x: 87, y: 87 },
],
hasCustomCollisionMask: false,
},
],
},
],
},
],
effects: [],
behaviors: [
{
type: 'PathfindingBehavior::PathfindingObstacleBehavior',
impassable: true,
cost: 2,
},
],
});
obstacle.getWidth = function () {
return 160;
};
obstacle.getHeight = function () {
return 160;
};
runtimeScene.addObject(obstacle);
return obstacle;
};
let playerLeftBorder;
let playerRightBorder;
let playerTopBorder;
let playerBottomBorder;
let obstacleLeftBorder;
let obstacleRightBorder;
let obstacleTopBorder;
let obstacleBottomBorder;
if (cellSize == 160) {
if (objectCenteredOnCells) {
playerLeftBorder = 80;
playerRightBorder = 80;
playerTopBorder = 80;
playerBottomBorder = 80;
} else {
playerLeftBorder = 87;
playerRightBorder = 73;
playerTopBorder = 87;
playerBottomBorder = 73;
}
} else {
// The player radius is at most 2 cells
playerLeftBorder = 2 * cellSize;
playerRightBorder = 2 * cellSize;
playerTopBorder = 2 * cellSize;
playerBottomBorder = 2 * cellSize;
}
if (objectCenteredOnCells) {
obstacleLeftBorder = 80;
obstacleRightBorder = 80;
obstacleTopBorder = 80;
obstacleBottomBorder = 80;
} else {
obstacleLeftBorder = 87;
obstacleRightBorder = 73;
obstacleTopBorder = 87;
obstacleBottomBorder = 73;
}
let inclusiveMargin;
if (cellSize == 160) {
// The collision is strict
inclusiveMargin = 0;
} else {
// The collision is not strict
inclusiveMargin = 1;
}
// multiple of 20 and 160
const playerX = 480;
const playerY = 320;
let runtimeScene;
let player;
beforeEach(function () {
runtimeScene = createScene();
player = addPlayer(runtimeScene);
});
it('can find a path with an obstacle near the start', function () {
const obstacle = addObstacle(runtimeScene);
let obstacleX;
let obstacleY;
let targetX;
let targetY;
switch (direction) {
case 'right':
// right on the player right
obstacleX = playerX + playerRightBorder + obstacleLeftBorder;
obstacleY = playerY;
targetX = playerX - 2 * cellSize;
targetY = playerY;
break;
case 'left':
obstacleX = playerX - playerLeftBorder - obstacleRightBorder;
obstacleY = playerY;
targetX = playerX + 2 * cellSize;
targetY = playerY;
break;
case 'up':
obstacleX = playerX;
obstacleY = playerY - playerTopBorder - obstacleBottomBorder;
targetX = playerX;
targetY = playerY + 2 * cellSize;
break;
case 'down':
obstacleX = playerX;
obstacleY = playerY + playerBottomBorder + obstacleTopBorder;
targetX = playerX;
targetY = playerY - 2 * cellSize;
break;
}
obstacle.setPosition(obstacleX, obstacleY);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / 60);
// move away from the obstacle
player.setPosition(playerX, playerY);
player
.getBehavior(pathFindingName)
.moveTo(runtimeScene, targetX, targetY);
expect(player.getBehavior(pathFindingName).pathFound()).to.be(true);
expect(player.getBehavior(pathFindingName).getNodeCount()).to.be(3);
});
it('can find a path when an obstacle is overlapping only at the start', function () {
const obstacle = addObstacle(runtimeScene);
let obstacleX;
let obstacleY;
let targetX;
let targetY;
switch (direction) {
case 'right':
// The obstacle will be right on the player right after the first step.
// So, it's overlapping the player by one cell length at start.
obstacleX =
playerX +
playerRightBorder +
obstacleLeftBorder -
cellSize +
inclusiveMargin;
obstacleY = playerY;
targetX = playerX - 2 * cellSize;
targetY = playerY;
break;
case 'left':
obstacleX =
playerX -
playerLeftBorder -
obstacleRightBorder +
cellSize -
inclusiveMargin;
obstacleY = playerY;
targetX = playerX + 2 * cellSize;
targetY = playerY;
break;
case 'up':
obstacleX = playerX;
obstacleY =
playerY -
playerTopBorder -
obstacleBottomBorder +
cellSize -
inclusiveMargin;
targetX = playerX;
targetY = playerY + 2 * cellSize;
break;
case 'down':
obstacleX = playerX;
obstacleY =
playerY +
playerBottomBorder +
obstacleTopBorder -
cellSize +
inclusiveMargin;
targetX = playerX;
targetY = playerY - 2 * cellSize;
break;
}
obstacle.setPosition(obstacleX, obstacleY);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / 60);
// move away from the obstacle
player.setPosition(playerX, playerY);
player
.getBehavior(pathFindingName)
.moveTo(runtimeScene, targetX, targetY);
expect(player.getBehavior(pathFindingName).pathFound()).to.be(true);
expect(player.getBehavior(pathFindingName).getNodeCount()).to.be(3);
});
it("mustn't find a path when an obstacle is slightly overlapping the only first cell to go", function () {
const obstacle = addObstacle(runtimeScene);
let obstacleX;
let obstacleY;
let targetX;
let targetY;
switch (direction) {
case 'right':
// The obstacle will be right on the player right after the first step.
// So, it's overlapping the player by one cell length at start.
obstacleX =
playerX +
playerRightBorder +
obstacleLeftBorder -
cellSize +
inclusiveMargin -
1;
obstacleY = playerY;
targetX = playerX - 2 * cellSize;
targetY = playerY;
break;
case 'left':
obstacleX =
playerX -
playerLeftBorder -
obstacleRightBorder +
cellSize -
inclusiveMargin +
1;
obstacleY = playerY;
targetX = playerX + 2 * cellSize;
targetY = playerY;
break;
case 'up':
obstacleX = playerX;
obstacleY =
playerY -
playerTopBorder -
obstacleBottomBorder +
cellSize -
inclusiveMargin +
1;
targetX = playerX;
targetY = playerY + 2 * cellSize;
break;
case 'down':
obstacleX = playerX;
obstacleY =
playerY +
playerBottomBorder +
obstacleTopBorder -
cellSize +
inclusiveMargin -
1;
targetX = playerX;
targetY = playerY - 2 * cellSize;
break;
default:
throw new Error('Invalid direction for the test');
}
obstacle.setPosition(obstacleX, obstacleY);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / 60);
// move away from the obstacle
player.setPosition(playerX, playerY);
player
.getBehavior(pathFindingName)
.moveTo(runtimeScene, targetX, targetY);
if (cellSize == 160) {
// The obstacle is the size of the cell so the player can bypass it.
expect(player.getBehavior(pathFindingName).getNodeCount()).to.be.above(
3
);
} else {
expect(player.getBehavior(pathFindingName).pathFound()).to.be(false);
}
});
it('can find a path with an obstacle adjacent to the target', function () {
const obstacle = addObstacle(runtimeScene);
let obstacleX;
let obstacleY;
let targetX;
let targetY;
switch (direction) {
case 'right':
targetX = playerX + 2 * cellSize;
targetY = playerY;
// will be right on the player right at the end
obstacleX =
targetX + playerRightBorder + obstacleLeftBorder + inclusiveMargin;
obstacleY = playerY;
break;
case 'left':
targetX = playerX - 2 * cellSize;
targetY = playerY;
obstacleX =
targetX - playerRightBorder - obstacleLeftBorder - inclusiveMargin;
obstacleY = playerY;
break;
case 'up':
targetX = playerX;
targetY = playerY - 2 * cellSize;
obstacleX = playerX;
obstacleY =
targetY - playerTopBorder - obstacleBottomBorder - inclusiveMargin;
break;
case 'down':
targetX = playerX;
targetY = playerY + 2 * cellSize;
obstacleX = playerX;
obstacleY =
targetY + playerBottomBorder + obstacleTopBorder + inclusiveMargin;
break;
}
obstacle.setPosition(obstacleX, obstacleY);
// so obstacles register
runtimeScene.renderAndStep(1000 / 60);
// move as close as possible to the obstacle
player.setPosition(playerX, playerY);
player
.getBehavior(pathFindingName)
.moveTo(runtimeScene, targetX, targetY);
expect(player.getBehavior(pathFindingName).pathFound()).to.be(true);
expect(player.getBehavior(pathFindingName).getNodeCount()).to.be(3);
});
it("mustn't find a path with an obstacle slightly overlapping to the target", function () {
const obstacle = addObstacle(runtimeScene);
let obstacleX;
let obstacleY;
let targetX;
let targetY;
switch (direction) {
case 'right':
targetX = playerX + 2 * cellSize;
targetY = playerY;
obstacleX =
targetX +
playerRightBorder +
obstacleLeftBorder +
inclusiveMargin -
1;
obstacleY = playerY;
break;
case 'left':
targetX = playerX - 2 * cellSize;
targetY = playerY;
obstacleX =
targetX -
playerLeftBorder -
obstacleRightBorder -
inclusiveMargin +
1;
obstacleY = playerY;
break;
case 'up':
targetX = playerX;
targetY = playerY - 2 * cellSize;
obstacleX = playerX;
obstacleY =
targetY -
playerTopBorder -
obstacleBottomBorder -
inclusiveMargin +
1;
break;
case 'down':
targetX = playerX;
targetY = playerY + 2 * cellSize;
obstacleX = playerX;
obstacleY =
targetY +
playerBottomBorder +
obstacleTopBorder +
inclusiveMargin -
1;
break;
default:
throw new Error('Invalid direction for the test');
}
obstacle.setPosition(obstacleX, obstacleY);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / 60);
// move as close as possible to the obstacle
player.setPosition(playerX, playerY);
player
.getBehavior(pathFindingName)
.moveTo(runtimeScene, targetX, targetY);
expect(player.getBehavior(pathFindingName).pathFound()).to.be(false);
});
it('can find a path between 2 obstacles making a path perfectly adjusted to the object', function () {
const obstacleTop = addObstacle(runtimeScene);
const obstacleBottom = addObstacle(runtimeScene);
let topObstacleX;
let bottomObstacleX;
let topObstacleY;
let bottomObstacleY;
let targetX;
let targetY;
let straightLineCellCount;
switch (direction) {
case 'right': {
const obstacleX =
playerX + playerRightBorder + 2 * cellSize + obstacleLeftBorder;
topObstacleX = obstacleX;
bottomObstacleX = obstacleX;
topObstacleY =
playerY - playerTopBorder - obstacleBottomBorder - inclusiveMargin;
bottomObstacleY =
playerY + playerBottomBorder + obstacleTopBorder + inclusiveMargin;
targetX =
obstacleX + obstacleRightBorder + playerLeftBorder + 2 * cellSize;
targetY = playerY;
straightLineCellCount = (targetX - playerX) / cellSize + 1;
break;
}
case 'left': {
const obstacleX =
playerX - playerLeftBorder - 2 * cellSize - obstacleRightBorder;
topObstacleX = obstacleX;
bottomObstacleX = obstacleX;
topObstacleY =
playerY - playerTopBorder - obstacleBottomBorder - inclusiveMargin;
bottomObstacleY =
playerY + playerBottomBorder + obstacleTopBorder + inclusiveMargin;
targetX =
obstacleX - obstacleLeftBorder - playerRightBorder - 2 * cellSize;
targetY = playerY;
straightLineCellCount = (playerX - targetX) / cellSize + 1;
break;
}
case 'up': {
// topObstacle and bottomObstacle are actually left and right but for convenience...
const obstacleY =
playerY - playerTopBorder - 2 * cellSize - obstacleBottomBorder;
topObstacleY = obstacleY;
bottomObstacleY = obstacleY;
topObstacleX =
playerX - playerLeftBorder - obstacleRightBorder - inclusiveMargin;
bottomObstacleX =
playerX + playerRightBorder + obstacleLeftBorder + inclusiveMargin;
targetY =
obstacleY - obstacleTopBorder - playerBottomBorder - 2 * cellSize;
targetX = playerX;
straightLineCellCount = (playerY - targetY) / cellSize + 1;
break;
}
case 'down': {
// topObstacle and bottomObstacle are actually left and right but for convenience...
const obstacleY =
playerY + playerBottomBorder + 2 * cellSize + obstacleTopBorder;
topObstacleY = obstacleY;
bottomObstacleY = obstacleY;
topObstacleX =
playerX - playerLeftBorder - obstacleRightBorder - inclusiveMargin;
bottomObstacleX =
playerX + playerRightBorder + obstacleLeftBorder + inclusiveMargin;
targetY =
obstacleY + obstacleBottomBorder + playerTopBorder + 2 * cellSize;
targetX = playerX;
straightLineCellCount = (targetY - playerY) / cellSize + 1;
break;
}
}
obstacleTop.setPosition(topObstacleX, topObstacleY);
obstacleBottom.setPosition(bottomObstacleX, bottomObstacleY);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / 60);
player.setPosition(playerX, playerY);
player
.getBehavior(pathFindingName)
.moveTo(runtimeScene, targetX, targetY);
expect(player.getBehavior(pathFindingName).pathFound()).to.be(true);
expect(player.getBehavior(pathFindingName).getNodeCount()).to.be(
straightLineCellCount
);
});
it("mustn't find a direct path between 2 slightly too much tighten obstacles", function () {
const obstacleTop = addObstacle(runtimeScene);
const obstacleBottom = addObstacle(runtimeScene);
let topObstacleX;
let bottomObstacleX;
let topObstacleY;
let bottomObstacleY;
let targetX;
let targetY;
let straightLineCellCount;
switch (direction) {
case 'right': {
const obstacleX =
playerX + playerRightBorder + 2 * cellSize + obstacleLeftBorder;
topObstacleX = obstacleX;
bottomObstacleX = obstacleX;
topObstacleY =
playerY - playerTopBorder - obstacleBottomBorder - inclusiveMargin;
bottomObstacleY =
playerY +
playerBottomBorder +
obstacleTopBorder +
inclusiveMargin -
1;
targetX =
obstacleX + obstacleRightBorder + playerLeftBorder + 2 * cellSize;
targetY = playerY;
straightLineCellCount = (targetX - playerX) / cellSize + 1;
break;
}
case 'left': {
const obstacleX =
playerX - playerLeftBorder - 2 * cellSize - obstacleRightBorder;
topObstacleX = obstacleX;
bottomObstacleX = obstacleX;
topObstacleY =
playerY - playerTopBorder - obstacleBottomBorder - inclusiveMargin;
bottomObstacleY =
playerY +
playerBottomBorder +
obstacleTopBorder +
inclusiveMargin -
1;
targetX =
obstacleX - obstacleLeftBorder - playerRightBorder - 2 * cellSize;
targetY = playerY;
straightLineCellCount = (playerX - targetX) / cellSize + 1;
break;
}
case 'up': {
// topObstacle and bottomObstacle are actually left and right but for convenience...
const obstacleY =
playerY - playerTopBorder - 2 * cellSize - obstacleBottomBorder;
topObstacleY = obstacleY;
bottomObstacleY = obstacleY;
topObstacleX =
playerX - playerLeftBorder - obstacleRightBorder - inclusiveMargin;
bottomObstacleX =
playerX +
playerRightBorder +
obstacleLeftBorder +
inclusiveMargin -
1;
targetY =
obstacleY - obstacleTopBorder - playerBottomBorder - 2 * cellSize;
targetX = playerX;
straightLineCellCount = (playerY - targetY) / cellSize + 1;
break;
}
case 'down': {
// topObstacle and bottomObstacle are actually left and right but for convenience...
const obstacleY =
playerY + playerBottomBorder + 2 * cellSize + obstacleTopBorder;
topObstacleY = obstacleY;
bottomObstacleY = obstacleY;
topObstacleX =
playerX - playerLeftBorder - obstacleRightBorder - inclusiveMargin;
bottomObstacleX =
playerX +
playerRightBorder +
obstacleLeftBorder +
inclusiveMargin -
1;
targetY =
obstacleY + obstacleBottomBorder + playerTopBorder + 2 * cellSize;
targetX = playerX;
straightLineCellCount = (targetY - playerY) / cellSize + 1;
break;
}
default:
throw new Error('Invalid direction for the test');
}
obstacleTop.setPosition(topObstacleX, topObstacleY);
obstacleBottom.setPosition(bottomObstacleX, bottomObstacleY);
// To ensure obstacles are registered.
runtimeScene.renderAndStep(1000 / 60);
player.setPosition(playerX, playerY);
player
.getBehavior(pathFindingName)
.moveTo(runtimeScene, targetX, targetY);
expect(player.getBehavior(pathFindingName).pathFound()).to.be(true);
// had to do a little detour
expect(player.getBehavior(pathFindingName).getNodeCount()).to.be.above(
straightLineCellCount
);
});
};
describe('(collisionMethod: Legacy,', function () {
// Small cells and cells the object size
[20, 160].forEach((cellSize) => {
describe(`cellSize: ${cellSize},`, function () {
[false, true].forEach((objectCenteredOnCells) => {
describe(`centered: ${objectCenteredOnCells},`, function () {
['left', 'right', 'down', 'up'].forEach((direction) => {
describe(`direction: ${direction})`, function () {
doLegacyPathFindingTests(
cellSize,
objectCenteredOnCells,
direction
);
});
});
});
});
});
});
});
});