О чем этот пример
Добавление света в 2D-игру может кардинально изменить ее атмосферу и глубину восприятия. В этом примере мы рассмотрим, как совместить систему освещения Phaser 3 с тайловыми картами, создавая динамическую сцену, где источники света следуют за игроком и плавно движутся по карте. Этот подход позволяет легко реализовать такие механики, как исследование темных подземелий со светильником или проезд машины по ночному городу.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
light;
offsets = [];
player;
layer;
preload() {
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('tiles', [ 'assets/tilemaps/tiles/drawtiles1.png', 'assets/tilemaps/tiles/drawtiles1_n.png' ]);
this.load.image('car', 'assets/sprites/car90.png');
this.load.tilemapCSV('map', 'assets/tilemaps/csv/grid.csv');
}
create ()
{
const map = this.make.tilemap({ key: 'map', tileWidth: 32, tileHeight: 32 });
const tileset = map.addTilesetImage('tiles', null, 32, 32, 1, 2);
this.layer = map.createLayer(0, tileset, 0, 0).setLighting(true);
this.player = this.add.image(32+16, 32+16, 'car');
this.cursors = this.input.keyboard.createCursorKeys();
this.lights.enable();
this.lights.setAmbientColor(0x808080);
this.light = this.lights.addLight(0, 0, 200);
this.lights.addLight(0, 100, 140).setColor(0xff0000).setIntensity(3.0);
this.lights.addLight(0, 250, 140).setColor(0x00ff00).setIntensity(3.0);
this.lights.addLight(0, 400, 140).setColor(0xff00ff).setIntensity(3.0);
this.lights.addLight(0, 550, 140).setColor(0xffff00).setIntensity(3.0);
this.offsets = [ 0.1, 0.3, 0.5, 0.7 ];
}
update ()
{
if (this.input.keyboard.checkDown(this.cursors.left, 100))
{
const tile = this.layer.getTileAtWorldXY(this.player.x - 32, this.player.y, true);
if (tile.index === 2)
{
// Blocked, we can't move
}
else
{
this.player.x -= 32;
this.player.angle = 180;
}
}
else if (this.input.keyboard.checkDown(this.cursors.right, 100))
{
const tile = this.layer.getTileAtWorldXY(this.player.x + 32, this.player.y, true);
if (tile.index === 2)
{
// Blocked, we can't move
}
else
{
this.player.x += 32;
this.player.angle = 0;
}
}
else if (this.input.keyboard.checkDown(this.cursors.up, 100))
{
const tile = this.layer.getTileAtWorldXY(this.player.x, this.player.y - 32, true);
if (tile.index === 2)
{
// Blocked, we can't move
}
else
{
this.player.y -= 32;
this.player.angle = -90;
}
}
else if (this.input.keyboard.checkDown(this.cursors.down, 100))
{
const tile = this.layer.getTileAtWorldXY(this.player.x, this.player.y + 32, true);
if (tile.index === 2)
{
// Blocked, we can't move
}
else
{
this.player.y += 32;
this.player.angle = 90;
}
}
this.light.x = this.player.x;
this.light.y = this.player.y;
this.lights.lights.forEach(function (currLight, index)
{
if (this.light !== currLight)
{
currLight.x = 400 + Math.sin(this.offsets[index]) * 300;
this.offsets[index] += 0.01;
index += 1;
}
}, this);
}
}
const config = {
type: Phaser.WEBGL,
width: 800,
height: 600,
parent: 'phaser-example',
pixelArt: true,
backgroundColor: '#000000',
scene: Example
};
const game = new Phaser.Game(config);
Подготовка сцены и загрузка ресурсов
Класс Example наследуется от Phaser.Scene и описывает нашу игровую сцену. В методе preload() загружаются все необходимые ресурсы: тайловая текстура с двумя вариантами (обычным и для нормалей), спрайт машины и сама карта в формате CSV.
this.load.image('tiles', [ 'assets/tilemaps/tiles/drawtiles1.png', 'assets/tilemaps/tiles/drawtiles1_n.png' ]);
this.load.image('car', 'assets/sprites/car90.png');
this.load.tilemapCSV('map', 'assets/tilemaps/csv/grid.csv');
Создание карты и включение освещения
В методе `create()` происходит основная настройка. Сначала создается тайловая карта (`this.make.tilemap`), к ней добавляется набор тайлов (`map.addTilesetImage`). Ключевой момент — создание слоя (`map.createLayer`) с параметром `.setLighting(true)`. Это говорит движку, что на данный слой должно влиять освещение.
Затем включается система света (`this.lights.enable()`) и задается цвет окружающего освещения (`this.lights.setAmbientColor`). Без него области вне зоны действия источников света были бы полностью черными.
this.layer = map.createLayer(0, tileset, 0, 0).setLighting(true);
this.lights.enable();
this.lights.setAmbientColor(0x808080);
Расстановка источников света
Далее создаются источники света с помощью this.lights.addLight(x, y, radius). Первый свет привязан к игроку. Еще четыре статичных источника создаются с разными цветами и увеличенной интенсивностью (setIntensity(3.0)). Интенсивность влияет на яркость и радиус свечения.
this.light = this.lights.addLight(0, 0, 200);
this.lights.addLight(0, 100, 140).setColor(0xff0000).setIntensity(3.0);
Логика движения и навигации по карте
Метод update() обрабатывает ввод с клавиатуры для перемещения спрайта машины по сетке. Важная деталь — проверка на возможность хода. С помощью метода слоя getTileAtWorldXY() определяется тайл в точке, куда хочет переместиться игрок. Если индекс этого тайла равен 2, движение блокируется. Это простой способ реализовать препятствия на тайловой карте.
const tile = this.layer.getTileAtWorldXY(this.player.x + 32, this.player.y, true);
if (tile.index === 2) { /* Blocked */ } else { this.player.x += 32; }
Динамика освещения: светильник и плавающие огни
Основной источник света (this.light) постоянно следует за координатами игрока (this.player.x, this.player.y), имитируя, например, фонарик. Остальные четыре источника света не статичны — их положение по оси X плавно меняется с помощью синусоидальной функции, создавая эффект мерцания или плавающего движения. Для этого используется массив смещений offsets.
this.light.x = this.player.x;
this.light.y = this.player.y;
currLight.x = 400 + Math.sin(this.offsets[index]) * 300;
this.offsets[index] += 0.01;
Что попробовать дальше
Этот пример демонстрирует, как легко система освещения Phaser 3 интегрируется с тайловыми картами, добавляя визуальную глубину и интерактивность. Для экспериментов попробуйте изменить setAmbientColor на более темный оттенок, чтобы усилить контраст, или заставьте источники света реагировать на события (например, мигать при столкновении). Можно также поиграть с интенсивностью и радиусом света, чтобы создать разные типы освещения — от тусклого факела до яркого прожектора.
