<footer><div id=version></div></footer>
html, body {
height: 100%;
}
body {
margin: 0;
padding: 0;
background: #111;
color: #eee;
font: caption;
}
#version {
position: absolute;
left: 5px;
top: 5px;
}
/* global Phaser */
// Disable mask and draw geometry
const DEBUG = false;
// How many walls
const WALLS = 10;
// Colors
const BLACK = 0;
const WHITE = 0xffffff;
const FILL_COLOR = BLACK;
const DEBUG_STROKE_COLOR = WHITE;
const DEBUG_FILL_COLOR = 0xff0000;
// Shortcuts
const { Circle, Line, Point, Rectangle } = Phaser.Geom;
const { EPSILON } = Phaser.Math;
const { Extend } = Line;
const { ContainsPoint } = Rectangle;
const { LineToLine } = Phaser.Geom.Intersects;
function preload () {
this.load.image('block', 'assets/sprites/128x128.png');
this.load.image('clown', 'assets/sprites/clown.png');
this.load.image('background', 'assets/tests/grave/background.png');
this.load.image('light', 'assets/particles/yellow.png');
}
function create () {
this.add.grid(0, 0, 1024, 768, 64, 64, BLACK, 0, WHITE).setOrigin(0, 0).setAlpha(0.1);
const background = this.add.image(0, 0, 'background').setOrigin(0, 0);
const clown = this.add.image(0, 0, 'clown').setRandomPosition();
const walls = this.add.group({
key: 'block',
quantity: WALLS,
visible: false,
setScale: { x: 0.25, y: 0.25, stepX: 0.05, stepY: 0.05 }
});
Phaser.Actions.PlaceOnCircle(walls.getChildren(), new Circle(512, 384, 256));
const graphics = this.make.graphics({ lineStyle: { color: DEBUG_STROKE_COLOR, width: 0.5 } });
let mask;
if (DEBUG) {
mask = null;
graphics.setAlpha(0.5);
this.add.existing(graphics);
} else {
mask = new Phaser.Display.Masks.GeometryMask(this, graphics);
}
const light = this.add.image(400, 300, 'light')
.setBlendMode(1)
.setScale(1);
// Place and mask walls.
walls.getChildren().forEach((wall) => {
wall.setMask(mask);
});
// Mask objects and background.
clown.setMask(mask);
background.setMask(mask);
light.setMask(mask);
// Rectangles, will form the edges
const rects = [
...walls.getChildren().map(getSpriteRect),
// Outer boundary.
new Rectangle(0, 0, 1024, 768)
];
// Convert rectangles into edges (line segments)
const edges = rects.flatMap(getRectEdges);
// Convert rectangles into vertices
const vertices = rects.flatMap(getRectVertices);
// One ray will be sent through each vertex
const rays = vertices.map(() => new Line());
// Draw the mask once
draw(graphics, calc(light, vertices, edges, rays), rays, edges);
// And again each time the pointer moves
this.input.on('pointermove', (pointer) => {
light.setPosition(pointer.x, pointer.y);
draw(graphics, calc(light, vertices, edges, rays), rays, edges);
});
}
// Draw the mask shape, from vertices
function draw (graphics, vertices, rays, edges) {
graphics
.clear()
.fillStyle(FILL_COLOR)
.fillPoints(vertices, true);
if (DEBUG) {
rays.forEach((ray) => {
graphics.strokeLineShape(ray);
});
edges.forEach((edge) => {
graphics.strokeLineShape(edge);
});
graphics.fillStyle(DEBUG_FILL_COLOR);
vertices.forEach((vert) => {
graphics.fillPointShape(vert, 4);
});
}
}
// Place the rays, calculate and return intersections.
function calc (source, vertices, edges, rays) {
const sx = source.x;
const sy = source.y;
// Sort clockwise …
return sortClockwise(
// each ray-edge intersection, or the ray's endpoint if no intersection
rays.map((ray, i) => {
// placing the ray between the source and one vertex …
ray.setTo(sx, sy, vertices[i].x, vertices[i].y);
// extended through the wall vertex
Extend(ray, 0, 1000);
// placing its endpoint at the intersection with an edge, if any
edges.forEach((edge) => getRayToEdge(ray, edge));
// the new endpoint
return ray.getPointB();
}),
source
);
}
function getSpriteRect (sprite) {
const {displayWidth, displayHeight} = sprite;
return new Rectangle(
sprite.x - sprite.originX * displayWidth,
sprite.y - sprite.originY * displayHeight,
displayWidth,
displayHeight
);
}
function getRectEdges (rect) {
return [
rect.getLineA(),
rect.getLineB(),
rect.getLineC(),
rect.getLineD()
];
}
function getRectVertices (rect) {
const { left, top, right, bottom } = rect;
const left1 = left + EPSILON;
const top1 = top + EPSILON;
const right1 = right - EPSILON;
const bottom1 = bottom - EPSILON;
const left2 = left - EPSILON;
const top2 = top - EPSILON;
const right2 = right + EPSILON;
const bottom2 = bottom + EPSILON;
return [
new Point(left1, top1),
new Point(right1, top1),
new Point(right1, bottom1),
new Point(left1, bottom1),
new Point(left2, top2),
new Point(right2, top2),
new Point(right2, bottom2),
new Point(left2, bottom2)
];
}
// If a ray intersects with an edge, place the ray endpoint there and return the intersection.
function getRayToEdge (ray, edge, out) {
if (!out) out = new Point();
if (LineToLine(ray, edge, out)) {
ray.x2 = out.x;
ray.y2 = out.y;
return out;
}
return null;
}
function sortClockwise (points, center) {
// Adapted from <https://stackoverflow.com/a/6989383/822138> (ciamej)
var cx = center.x;
var cy = center.y;
var sort = function (a, b) {
if (a.x - cx >= 0 && b.x - cx < 0) {
return -1;
}
if (a.x - cx < 0 && b.x - cx >= 0) {
return 1;
}
if (a.x - cx === 0 && b.x - cx === 0) {
if (a.y - cy >= 0 || b.y - cy >= 0) {
return (a.y > b.y) ? 1 : -1;
}
return (b.y > a.y) ? 1 : -1;
}
// Compute the cross product of vectors (center -> a) * (center -> b)
var det = (a.x - cx) * -(b.y - cy) - (b.x - cx) * -(a.y - cy);
if (det < 0) {
return -1;
}
if (det > 0) {
return 1;
}
// Points a and b are on the same line from the center
// Check which point is closer to the center
var d1 = (a.x - cx) * (a.x - cx) + (a.y - cy) * (a.y - cy);
var d2 = (b.x - cx) * (b.x - cx) + (b.y - cy) * (b.y - cy);
return (d1 > d2) ? -1 : 1;
};
return points.sort(sort);
}
// eslint-disable-next-line no-unused-vars
function pointInRectangles (point, rects) {
return rects.some((rect) => ContainsPoint(rect, point));
}
const config = {
pixelArt: true,
scene: {
preload: preload,
create: create
},
loader: {
baseURL: 'https://labs.phaser.io',
crossOrigin: 'anonymous'
}
};
document.getElementById('version').textContent = 'Phaser v' + Phaser.VERSION;
// eslint-disable-next-line no-new
new Phaser.Game(config);
This Pen doesn't use any external CSS resources.