Создано на основе материалов с сайта HTML Game Example.
Напишем простую игру, напоминающую "Коптер", когда объект уворачивается от препятствий, которые движутся навстречу.
Создадим игровое поле на основе Canvas.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<style>
canvas {
border: 1px solid #d3d3d3;
background-color: #f1f1f1;
}
</style>
</head>
<body onload="startGame()">
<script>
function startGame() {
myGameArea.start();
}
var myGameArea = {
canvas : document.createElement("canvas"),
start : function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
}
}
</script>
<p>Мы создали игровое поле.</p>
</body>
</html>
Функция startGame() вызывает метод start() объекта myGameArea. Метод start() создаёт холст для игры с указанными размерами и вставляет его как первый дочерний элемент body.
Теперь добавим новый объект, которым будем управлять. Для простоты будем использовать красный прямоугольник.
Создадим конструктор component, который позволит нам добавлять игровые объекты в игровое поле. В качестве параметров нужно будет указать размеры, цвет и позицию.
function component(width, height, color, x, y) {
this.width = width;
this.height = height;
this.x = x;
this.y = y;
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
Добавим новую переменную copter для красного прямоугольник (это наш коптер) и создадим новый компонент игры в методе startGame().
var copter;
function startGame() {
myGameArea.start();
copter = new component(40, 30, "red", 10, 120);
}
В позиции (10, 120) появится красный квадрат размером 40х30 пикселей.
Для динамичной игры нам следует обновлять экран 50 раз в секунду, как кадры киноплёнки для создания иллюзии движения.
Создадим новую функцию updateGameArea().
function updateGameArea() {
myGameArea.clear();
copter.update();
}
В объекте myGameArea добавим интервал, который будет запускать функцию updateGameArea() каждую двадцатую миллисекунду (50 раз в секунду). А также добавим функцию clear(), которая будет очищать холст игры.
var myGameArea = {
...
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.interval = setInterval(updateGameArea, 20);
},
clear : function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
В конструкторе component добавим функцию update(), который будет отвечать за отрисовку компонента.
function component(width, height, color, x, y) {
this.width = width;
this.height = height;
this.x = x;
this.y = y;
this.update = function(){
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
В функции updateGameArea() вызываем оба метода clear() и update(). В результате наших изменений наш красный прямоугольник рисуется и стирается 50 раз в секунду. На неподвижном объекте это пока не заметно.
Заставим объект двигаться по горизонтали, меняя его позицию x на один пиксель при каждом обновлении экрана.
function updateGameArea() {
myGameArea.clear();
copter.x += 1;
copter.update();
}
Для понимания, зачем нужна функция clear(), временно закомментируйте строчку myGameArea.clear(); и посмотрите на результат. Объект будет двигаться, оставляя за собой шлейф. Получится большая красная полоса. Уберите комментарий, чтобы восстановить первоначальный вариант.
Вернёмся к нашему коптеру. Мы можем менять его размеры, начальную позицию и цвет. Кстати, цвет можно задавать не только предопределёнными именами, но и в формате RGBA.
// полупрозрачный квадрат
copter = new component(30, 30, "rgba(0, 0, 255, 0.5)", 10, 20);
Если вам нужны дополнительные объекты, то добавляем их по аналогии
var copter2, copter3;
function startGame() {
copter = new component(40, 30, "red", 10, 120);
// зададим новым объектам другие цвета и позицию
copter2 = new component(40, 30, "blue", 10, 170);
copter3 = new component(40, 30, "green", 10, 60);
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
copter.x += 1;
copter.update();
copter2.update();
copter3.update();
}
Двигаться объекты также могут в разных направлениях.
function updateGameArea() {
myGameArea.clear();
// по горизонтали
copter.x += 1;
// по диагонали вниз
copter2.x += 1;
copter2.y += 1;
// по диагонали вверх
copter3.x += 1;
copter3.y -= 1;
copter.update();
copter2.update();
copter3.update();
}
Добавим возможность управления объектом. Создадим четыре кнопки для движения объекта в разные стороны. Напишем функции для каждой кнопки для движения объекта в выбранном направлении.
function moveup() {
copter.speedY -= 1;
}
function movedown() {
copter.speedY += 1;
}
function moveleft() {
copter.speedX -= 1;
}
function moveright() {
copter.speedX += 1;
}
Добавим кнопки на страницу.
<button onclick="moveup()">UP</button>
<button onclick="movedown()">DOWN</button>
<button onclick="moveleft()">LEFT</button>
<button onclick="moveright()">RIGHT</button>
Сначала добавим два новых свойства в конструктор, которые помогут управлять скоростью движения. А также добавим функцию в этот же конструктор, который будет изменять значения скорости.
function component(width, height, color, x, y) {
this.width = width;
this.height = height;
this.x = x;
this.y = y;
this.speedX = 0;
this.speedY = 0;
this.update = function(){
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
this.newPos = function() {
this.x += this.speedX;
this.y += this.speedY;
}
}
Функцию newPos() следует вызывать в updateGameArea() до рисования объекта.
function updateGameArea() {
myGameArea.clear();
copter.newPos()
copter.update();
}
Если мы хотим, чтобы объект прекращал движение при отпускании кнопки, а при нажатой продолжал движение, то следует видоизменить код для кнопок.
function stopMove() {
copter.speedX = 0;
copter.speedY = 0;
}
<button onmousedown="moveup()" onmouseup="stopMove()" ontouchstart="moveup()">UP</button>
<button onmousedown="movedown()" onmouseup="stopMove()" ontouchstart="movedown()">DOWN</button>
<button onmousedown="moveleft()" onmouseup="stopMove()" ontouchstart="moveleft()">LEFT</button>
<button onmousedown="moveright()" onmouseup="stopMove()" ontouchstart="moveright()">RIGHT</button>
Добавим поддержку клавиатуры. Управлять будем клавишами-стрелками. В коде нужно проверять нажатия и отпускания нужных клавишей.
var myGameArea = {
canvas : document.createElement("canvas"),
start : function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.interval = setInterval(updateGameArea, 20);
window.addEventListener('keydown', function (e) {
myGameArea.key = e.keyCode;
})
window.addEventListener('keyup', function (e) {
myGameArea.key = false;
})
},
clear : function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function updateGameArea() {
myGameArea.clear();
copter.speedX = 0;
copter.speedY = 0;
if (myGameArea.key && myGameArea.key == 37) {copter.speedX = -1; }
if (myGameArea.key && myGameArea.key == 39) {copter.speedX = 1; }
if (myGameArea.key && myGameArea.key == 38) {copter.speedY = -1; }
if (myGameArea.key && myGameArea.key == 40) {copter.speedY = 1; }
copter.newPos()
copter.update();
}
Если нужна поддержка одновременных нажатий нескольких клавиш, то следует создать массив для объекта.
window.addEventListener('keydown', function (e) {
myGameArea.keys = (myGameArea.keys || []);
myGameArea.keys[e.keyCode] = true;
})
window.addEventListener('keyup', function (e) {
myGameArea.keys[e.keyCode] = false;
})
if (myGameArea.keys && myGameArea.keys[37]) {copter.speedX = -1; }
if (myGameArea.keys && myGameArea.keys[39]) {copter.speedX = 1; }
if (myGameArea.keys && myGameArea.keys[38]) {copter.speedY = -1; }
if (myGameArea.keys && myGameArea.keys[40]) {copter.speedY = 1; }
Теперь при нажатии сразу двух клавиш можно добиться движения по диагонали.
Если мы хотим управлять объектом с помощью мыши, то добавим код в myGameArea и обновим updateGameArea(). Объект будет следовать за курсором мыши (кстати, он не будет виден на холсте).
var myGameArea = {
canvas : document.createElement("canvas"),
start : function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.canvas.style.cursor = "none"; //hide the original cursor
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.interval = setInterval(updateGameArea, 20);
window.addEventListener('mousemove', function (e) {
myGameArea.x = e.pageX;
myGameArea.y = e.pageY;
})
},
clear : function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function updateGameArea() {
myGameArea.clear();
if (myGameArea.x && myGameArea.y) {
copter.x = myGameArea.x;
copter.y = myGameArea.y;
}
copter.update();
}
Если вместо мыши нужна поддержка тачскринов, то меняем код.
window.addEventListener('touchmove', function (e) {
myGameArea.x = e.touches[0].screenX;
myGameArea.y = e.touches[0].screenY;
})
function updateGameArea() {
myGameArea.clear();
if (myGameArea.touchX && myGameArea.touchY) {
myGamePiece.x = myGameArea.x;
myGamePiece.y = myGameArea.y;
}
myGamePiece.update();
}
Можно нарисовать на холсте свои собственные кнопки - Controllers on The Canvas (https://www.w3schools.com/graphics/game_controllers.asp).
Теперь создадим препятствия (Obstacles).
var obstacle;
function startGame() {
copter = new component(40, 30, "red", 10, 120);
obstacle = new component(10, 200, "green", 300, 120);
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
obstacle.update();
copter.newPos();
copter.update();
}
Появится вертикальная зелёная полоска приблизительно по центру в нижней части. А сверху останется место для пролёта.
Коптер может свободно пролететь сквозь препятствие. Необходимо добавить обработку столкновений.
Любое столкновение - это конец игры. Создадим новый метод в конструкторе component, который будет отслеживать столкновения с другим объектами. Функция должна вызываться каждый раз при обновлении кадров (50 раз в секунду).
Также добавим метод stop() объекту myGameArea, который будет сбрасывать интервал.
function component(width, height, color, x, y) {
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.update = function(){
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
this.newPos = function() {
this.x += this.speedX;
this.y += this.speedY;
},
this.crashWith = function(otherobj) {
var myleft = this.x;
var myright = this.x + (this.width);
var mytop = this.y;
var mybottom = this.y + (this.height);
var otherleft = otherobj.x;
var otherright = otherobj.x + (otherobj.width);
var othertop = otherobj.y;
var otherbottom = otherobj.y + (otherobj.height);
var crash = true;
if ((mybottom < othertop) ||
(mytop > otherbottom) ||
(myright < otherleft) ||
(myleft > otherright)) {
crash = false;
}
return crash;
}
}
var myGameArea = {
canvas : document.createElement("canvas"),
start : function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.interval = setInterval(updateGameArea, 20);
},
clear : function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
stop : function() {
clearInterval(this.interval);
}
}
function updateGameArea() {
if (copter.crashWith(obstacle)) {
myGameArea.stop();
} else {
myGameArea.clear();
obstacle.update();
copter.x += copter.speedX;
copter.y += copter.speedY;
copter.update();
}
}
Неподвижные препятствия не слишком интересны. Заставим их двигаться, создавая иллюзию полёта. Изменим значение свойство obstacle.x при каждом обновлении.
function updateGameArea() {
if (copter.crashWith(obstacle)) {
myGameArea.stop();
} else {
myGameArea.clear();
obstacle.x += -1;
obstacle.update();
copter.x += copter.speedX;
copter.y += copter.speedY;
copter.update();
}
}
Теперь препятствие само движется на наш коптер. Если его обогнуть, то оно уйдёт за пределы экрана.
Одного препятствия мало. Добавим ещё. Нам понадобится свойство для подсчёта кадров и метод, который будет выполняться с заданной частотой кадра.
var myGameArea = {
canvas : document.createElement("canvas"),
start : function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.frameNumber = 0;
this.interval = setInterval(updateGameArea, 20);
},
clear : function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
stop : function() {
clearInterval(this.interval);
}
}
function everyinterval(n) {
if ((myGameArea.frameNumber / n) % 1 == 0) {return true;}
return false;
}
Функция everyinterval() возвращает true, если текущий frameNumber совпадает с заданным интервалом.
Для создания множества препятствий понадобится массив.
var obstacles = [];
Внесём изменения в функцию updateGameArea().
function updateGameArea() {
var x, y;
for (i = 0; i < obstacles.length; i += 1) {
if (copter.crashWith(obstacles[i])) {
myGameArea.stop();
return;
}
}
myGameArea.clear();
myGameArea.frameNumber += 1;
if (myGameArea.frameNumber == 1 || everyinterval(150)) {
x = myGameArea.canvas.width;
y = myGameArea.canvas.height - 200;
obstacles.push(new component(10, 200, "green", x, y));
}
for (i = 0; i < obstacles.length; i += 1) {
obstacles[i].x += -1;
obstacles[i].update();
}
copter.newPos();
copter.update();
}
Новое препятствие будет появляться на каждом 150 кадре. У нас обновление 50 кадров в секунду, таким образом препятствие будет появляться каждую третью секунду. В цикле проверяем столкновение и останавливаем игру в случае необходимости.
Добавим случайность в формировании препятствий.
function updateGameArea() {
var x, y;
for (i = 0; i < obstacles.length; i += 1) {
if (copter.crashWith(obstacles[i])) {
myGameArea.stop();
return;
}
}
myGameArea.clear();
myGameArea.frameNumber += 1;
if (myGameArea.frameNumber == 1 || everyinterval(150)) {
x = myGameArea.canvas.width;
minHeight = 20;
maxHeight = 200;
height = Math.floor(Math.random() * (maxHeight - minHeight + 1) + minHeight);
minGap = 50;
maxGap = 200;
gap = Math.floor(Math.random() * (maxGap - minGap + 1) + minGap);
obstacles.push(new component(10, height, "green", x, 0));
obstacles.push(new component(10, x - height - gap, "green", x, height + gap));
}
for (i = 0; i < obstacles.length; i += 1) {
obstacles[i].x += -1;
obstacles[i].update();
}
copter.newPos();
copter.update();
}
Отобразим подсчёт очков. Добавим табло на холст. А также изменим конструктор, чтобы он принимал новые значения для отображения текста.
var score;
function startGame() {
copter = new component(40, 30, "red", 10, 120);
score = new component("30px", "Consolas", "black", 280, 40, "text");
myGameArea.start();
}
function component(width, height, color, x, y, type) {
this.type = type;
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.update = function(){
ctx = myGameArea.context;
if (this.type == "text") {
ctx.font = this.width + " " + this.height;
ctx.fillStyle = color;
ctx.fillText(this.text, this.x, this.y);
} else {
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
this.newPos = function() {
this.x += this.speedX;
this.y += this.speedY;
}
this.crashWith = function(otherobj) {
var myleft = this.x;
var myright = this.x + (this.width);
var mytop = this.y;
var mybottom = this.y + (this.height);
var otherleft = otherobj.x;
var otherright = otherobj.x + (otherobj.width);
var othertop = otherobj.y;
var otherbottom = otherobj.y + (otherobj.height);
var crash = true;
if ((mybottom < othertop) ||
(mytop > otherbottom) ||
(myright < otherleft) ||
(myleft > otherright)) {
crash = false;
}
return crash;
}
}
Для подсчёта будем использовать значение frameNumber.
function updateGameArea() {
var x, y;
for (i = 0; i < obstacles.length; i += 1) {
if (copter.crashWith(obstacles[i])) {
myGameArea.stop();
return;
}
}
myGameArea.clear();
myGameArea.frameNumber += 1;
if (myGameArea.frameNumber == 1 || everyinterval(150)) {
x = myGameArea.canvas.width;
minHeight = 20;
maxHeight = 200;
height = Math.floor(Math.random() * (maxHeight - minHeight + 1) + minHeight);
minGap = 50;
maxGap = 200;
gap = Math.floor(Math.random() * (maxGap - minGap + 1) + minGap);
obstacles.push(new component(10, height, "green", x, 0));
obstacles.push(new component(10, x - height - gap, "green", x, height + gap));
}
for (i = 0; i < obstacles.length; i += 1) {
obstacles[i].x += -1;
obstacles[i].update();
}
score.text = "SCORE: " + myGameArea.frameNumber;
score.update();
copter.newPos();
copter.update();
}
https://www.w3schools.com/graphics/game_images.asp