У меня сложилось впечатление, что TotemDestroyer - это своеобразный helloworld для тех, кто начинает разрабатывать игры, как когда-то гостевая книга была первым проектом для веб-программистов (сейчас пишут приложение для микроблогинга). Пусть у меня тоже будет свой totem destroyer.
Впрочем, цель не просто написать TotemDestroyer, используя Phaser, а сделать что-то близкое к игре. Т.е. хочется кнопку перезапуска игры, проверку на конец игры, состояние победы и проигрыша. В техническом плане хотел брать картинки не из нескольких файлов, а загружать все спрайты в одном файле.
Spritesheet
Тут всё просто. После того, как я нарисовал в Inkscape все необходимые для меня картинки, то сгруппировал их вместе, выровнял центры, поставил промежуток в 1 пиксель и сохранил. В начале поста эти же спрайты, только с промежутком в 10 пикселей.
Загрузка картинок:
game.load.spritesheet('spritesheet', 'sprites.png', 64, 64, 7, 0, 1);
После этого можно использовать картинки по номеру фрейма.
Кнопка
Сделать кнопку несложно:
game.add.button(WIDTH - 84 + OFFSET, OFFSET + 20, 'spritesheet', recreateLevel, this, 1, 0, 2, 0);
Четвёртым параметром идёт обработчик клика на кнопку, у меня там перезапуск уровня (ну в данном случае всей игры).
World
Ещё мне хотелось, чтобы падающие вниз объекты не валялись как в банке, а проваливались за границу видимой области, для этого выставил размер мира больше чем размер видимой части игры.
game.world.setBounds(OFFSET, OFFSET, WIDTH + OFFSET * 2, HEIGHT + OFFSET * 2);
Грабли
Не обошлось и без граблей. Скорее всего это есть в документации, но я начал использовать Phaser недавно, поэтому нахожу много сюрпризов. Одним из них стало то, что позиция простого спрайта считается от верхнего левого угла, а у физического объекта (для которого вызвали game.physics.p2.enable(block)
) - от центра. Из-за этого не сразу понял, почему у меня прыгают блоки по всему полю.
Игра
В самой игре не совсем корректно реализована проверка на остановку объектов, но сейчас меня устроит и такой вариант. Дело в том, что если застопорить шарик удаляемыми блоками, а затем быстро их удалить, то шар не наберёт нужно скорость, что приводит к концу игры и победе, хотя на самом деле шар должен был упасть. Чинить это не хочу, в данном случае меня это устраивает. В реальной игре сделал бы совсем иначе (может напишу корректную проверку в будущем).
Ну и сама игра:
Исходник (totemdestroyer.js):
(function(){ var OFFSET = 128; var WIDTH = $(window).width(); var HEIGHT = $(window).height(); var removables = []; var statics = []; var totem = { sprite: null, freeze: function() { if (this.sprite) { this.sprite.body.velocity.x = 0; this.sprite.body.velocity.y = 0; } }, isFall: function() { if (this.sprite) { if (this.sprite.body.y > (HEIGHT + OFFSET)) { return true; } } return false; }, isMoving: function() { if (this.sprite) { var nearlyToFreeze = 0.025; return Math.abs(this.sprite.body.velocity.x) > nearlyToFreeze || Math.abs(this.sprite.body.velocity.y) > nearlyToFreeze; } }, create: function(x, y) { if (this.sprite) { this.sprite.destroy(); } this.sprite = createBlock(x, y, 6, false); this.sprite.body.setCircle(32); }, destroy: function() { if (this.sprite) { this.sprite.destroy(); } this.sprite = null; } }; var createBlock = function(x, y, frame, isStatic, isRemovable) { var block = game.add.sprite(x, y, 'spritesheet', frame); game.physics.p2.enable(block); block.body.static = isStatic; block.removable = isRemovable; if (block.removable) { removables.push(block); } else { statics.push(block); } return block; }; var hasRemovable = function() { return removables.length > 0; }; var win = function() { game.stage.backgroundColor = '#00CD00'; }; var lose = function() { game.stage.backgroundColor = '#990000'; }; var recreateLevel = function() { game.stage.backgroundColor = '#DECD87'; var block; while(block = removables.pop()) { block.destroy(); } while(block = statics.pop()) { block.destroy(); } totem.destroy(); var worldCenterX = game.world.centerX; var groundPositionY = game.world.height - 256; createBlock(worldCenterX, groundPositionY, 3, true, false); for (var i = 0; i < 4; i++) { createBlock(worldCenterX - 64 * (i + 1), groundPositionY, 3, true, false); createBlock(worldCenterX + 64 * (i + 1), groundPositionY, 3, true, false); } var onGroundY = groundPositionY - 64; createBlock(worldCenterX, onGroundY - 1, 5, false, true); createBlock(worldCenterX - 80, onGroundY - 1, 5, false, true); createBlock(worldCenterX + 80, onGroundY - 1, 5, false, true); createBlock(worldCenterX + 40, onGroundY - 64 - 2, 5, false, true); createBlock(worldCenterX - 40, onGroundY - 64 - 2, 5, false, true); createBlock(worldCenterX, onGroundY - 129 - 3, 4, false, false); totem.create(worldCenterX, onGroundY - 197); game.input.onDown.add(onClick, this); }; var onClick = function(pointer) { var worlPointerPosition = {'x': pointer.position.x + OFFSET, 'y': pointer.position.y + OFFSET}; var bodiesClicked = game.physics.p2.hitTest(worlPointerPosition); for (var i = 0; i < bodiesClicked.length; i++) { var sprite = bodiesClicked[i].parent.sprite; if (sprite.removable) { var i = removables.indexOf(sprite); if (!(i < 0)) { removables.splice(i, 1); } sprite.destroy(); } } }; var onUpdate = function() { if (! hasRemovable()) { if(! totem.isMoving()) { totem.freeze(); win(); } } if (totem.isFall()) { lose(); } }; var onCreate = function() { game.world.setBounds(OFFSET, OFFSET, WIDTH + OFFSET * 2, HEIGHT + OFFSET * 2); game.physics.startSystem(Phaser.Physics.P2JS); game.physics.p2.gravity.y = 1000; game.physics.p2.friction = 3.0; game.physics.p2.restitution = 0; game.add.button(WIDTH - 84 + OFFSET, OFFSET + 20, 'spritesheet', recreateLevel, this, 1, 0, 2, 0); recreateLevel(); }; var onPreload = function() { game.load.spritesheet('spritesheet', 'sprites.png', 64, 64, 7, 0, 1); }; var game = new Phaser.Game( WIDTH, HEIGHT, Phaser.CANVAS, '', {preload: onPreload, create: onCreate, update: onUpdate} ); })();
index.html:
<!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <title>Totem destroyer with pahser</title> <style type="text/css" media="all"> * { margin: 0; padding: 0; } </style> </head> <body> <script src="jquery.min.js" type="text/javascript" charset="utf-8"></script> <script src="phaser.min.js" type="text/javascript" charset="utf-8"></script> <script src="totemdestroyer.js" type="text/javascript" charset="utf-8"></script> </body> </html>