Приветствую уважаемое хабра-сообщество. После прочтения поста «Создаём игру, используя canvas и спрайты» в день его выхода, решил углубить свои познания в Canvas. Так, как пока в работе не приходилось сталкиваться с этим элементом, пришлось пробежаться на скорую руку по API.
Конечно, рисование линий, прямоугольников, треугольников и полукругов весьма занимательное занятие. Но для приобретения реального опыта была поставлена задача – создать что-то функциональное и простое.
Вот так родилась идея написать собственную игру, всем знакомую змейку.
К сожалению, работа и отдых занимают столько времени, что игра была закончена лишь сейчас.
Пусть это будет как демонстрация бета-версии игры. Может быть, кому-нибудь пригодится мой труд. Возможно, я получу инвайт, а заодно с интересом послушаю, критические или похвальные мнения коллег.
Поехали!
Код написан с применением библиотеки jQuery, ибо так удобнее. Все переменные и функции объявляются уже после события загрузки странички. Перечисляем переменные и некоторым задаём дефолтные настройки, также задаём размеры холста и определяем «тело» змейки. Которое будет многомерным массивом. Идея состоит в том, что тело змейки – набор секторов с площадью 9px на 9px, и начало координат каждого сектора является число кратное 10. 1px «справа» от сектора не будет зарисовываться, для визуального разделения.
// объявление переменных, задание им дефолтных значений
var canvas, context, first_x, first_y,
rabbit_pos = new Array(), rabbit_on_field = false,
start = true, state = false, g_over = false,
direction = 'right';
// определение слоя и его размеров
canvas = $('#mycanvas').get(0);
canvas.width = 310;
canvas.height = 310;
context = canvas.getContext('2d');
// определение цвета змейки
context.fillStyle = "#CE3429";
// определение дефолтных секторов змейки
var snake_sectors = [[10, 50], [20, 50], [30, 50], [40, 50]];
Функция вывода рамок игрового поля, которую вызовем сразу после объявления дефолтных настроек.
// создание игрового поля
function field(context){
context.strokeStyle = "#546DEA";
context.lineWidth = 1;
context.strokeRect(7, 7, 295, 295);
}
Функция вывода змейки, в которой вызываются другие необходимые функции.
// вывод змейки
function snake(){
if(state){
// удаление "хвоста" при движении
// при первом проходе координаты есть undefined
context.clearRect(first_x, first_y, 9, 9);
// определение "поведения" змейки: начало игры, пауза/старт
if(start){
// зырисовывается тело змейки в цикле, и объявляется о завершении запуска игры
// для визуализации секторов, будем зарисовывать 9px из 10
for(var i in snake_sectors){
context.fillRect(snake_sectors[i][0], snake_sectors[i][1], 9, 9);
}
start = false;
}
else{
// зарисовывать новый сектор змейки - "голову"
context.fillRect(snake_sectors.slice(-1)[0][0], snake_sectors.slice(-1)[0][1], 9, 9);
}
// проверка существования кролика на поле и его вывод
if(!rabbit_on_field){
rabbit();
}
// присваивание переменным значений положения "головы" и "хвоста"
var last_x = snake_sectors.slice(-1)[0][0];
var last_y = snake_sectors.slice(-1)[0][1];
first_x = snake_sectors[0][0];
first_y = snake_sectors[0][1];
// определение поведения змейки при различных направлениях движения
if(direction == 'right'){
var next_x = last_x + 10;
if(next_x > 290){
// врезание в правое поле, конец игры
window.setTimeout(game_over, 700);
return false;
}
snake_sectors.push([next_x, last_y]);
}
if(direction == 'down'){
var next_y = last_y + 10;
if(next_y > 290){
// врезание в нижнее поле, конец игры
window.setTimeout(game_over, 700);
return false;
}
snake_sectors.push([last_x, next_y]); // добавление нового элемента массива ("голова" змейки)
}
if(direction == 'up'){
var next_y = last_y - 10;
if(next_y < 10) {
// врезание в верхнее поле, конец игры
window.setTimeout(game_over, 700);
return false;
}
snake_sectors.push([last_x, next_y]);
}
if(direction == 'left'){
var next_x = last_x - 10;
if(next_x < 10) {
// врезание в левое поле, конец игры
window.setTimeout(game_over, 700);
return false;
}
snake_sectors.push([next_x, last_y]);
}
// проверка на совпадение последнего элемента массива с другими элементами
// т.е. "врезание" змейки в себя
for(var i = 0; i < snake_sectors.length - 1; i++){
if(snake_sectors.slice(-1)[0][0] == snake_sectors[i][0] && snake_sectors.slice(-1)[0][1] == snake_sectors[i][1]){
// конец игры
window.setTimeout(game_over, 700);
return false;
}
}
// проверка на съедание кролика
// и определение дальнейшей судьбы "хвоста"
if(!is_catching()) snake_sectors.splice(0, 1);
else rabbit();
// таймер перезапуска функции, он же - скорость змейки
setTimeout(snake, 200); // 200 ms
}
}
По порядку. При state == true (true/false состояние игры старт/пауза), функция выполняет ряд действий, описание которых приведено в комментариях.
Ниже приведены все остальные функции, которые вызываются в snake().
Коротко по сути: вывод кролика (с генерацией случайных значений его координат и проверкой на существование таких в «теле» змейки);
// вывод кролика на игровое поле
function rabbit(){
// задание координат кролику
rabbit_pos[0] = math_rand();
rabbit_pos[1] = math_rand();
// проверка на совпадение сгенерированых координат с "телом" змейки
for(var i in snake_sectors){
if(rabbit_pos[0] == snake_sectors[i][0]){
rabbit_pos[0] = math_rand();
}
if(rabbit_pos[1] == snake_sectors[i][1]){
rabbit_pos[1] = math_rand();
}
}
// зарисовать сектор, и объявить о наличии живности на поле
context.fillRect(rabbit_pos[0], rabbit_pos[1], 9, 9);
rabbit_on_field = true;
}
функция-генератор случайных значений в заданном диапазоне;
// генерация случайных чисел в заданном диапазоне
// для определения координат кролика
function math_rand(){
return Math.ceil((Math.random() * 2.9) * 10) * 10;
}
проверка на «съедение» кролика (совпадение координат «головы» с координатами кролика);
// проверка на "съедание" кролика
// т.е. совпадение координат "головы" змейки с координатами кролика
function is_catching(){
if(rabbit_pos[0] == snake_sectors.slice(-1)[0][0] && rabbit_pos[1] == snake_sectors.slice(-1)[0][1])
return true;
else
return false;
}
объявление о конце игры и возврат к настройкам по умолчанию (для нового старта);
// "конец игры", функция вызываемая при наступлении событий -
// врезания змейки в себя или края игрового поля
function game_over(){
// объявляем конец игры, эта переменная нужна для того,
// чтоб заблокировать отклик на нажатие пробела во время заставки "Game Over"
g_over = true;
// чистим поле от треша
context.clearRect(8, 8, 293, 293);
// радуем пользователя о конце игры
context.font='35px Verdana';
context.strokeStyle="#DB733B";
context.strokeText('Game Over!',47,160);
// создаём некую разновидность анимации - задержка надписи "Game Over!"
setTimeout(function(){context.clearRect(8, 8, 293, 293); g_over = false}, 1500);
// задание дефолтных значений - снова начало игры
snake_sectors = [[10, 50], [20, 50], [30, 50], [40, 50]];
state = false;
direction = 'right';
start = true;
rabbit_on_field = false;
}
отлавливание событий нажатия на «игровые» клавиши и соответствующие последствия;
// отлавливание событий нажатия на "игровые" клавиши
document.onkeydown = function(event){
var keyCode;
if(event == null){
keyCode = window.event.keyCode;
}
else{
keyCode = event.keyCode;
}
switch(keyCode){
// space/пробел
case 32:
// действие при нажатии пробела
if(!g_over){
// чередуем паузу со стартом(continue)
state = (!state) ? true : false;
// вызываем змейку
snake();
}
break;
// left/стрелка влево
case 37:
// действие при нажатии "влево"
if(direction == 'right') return;
direction = 'left';
break;
// up/стрелка вверх
case 38:
// действие при нажатии "вверх"
if(direction == 'down') return;
direction = 'up';
break;
// right/стрелка вправо
case 39:
// действие при нажатии "вправо"
if(direction == 'left') return;
direction = 'right';
break;
// down/стрелка вниз
case 40:
// действие при нажатии "вниз"
if(direction == 'up') return;
direction = 'down';
break;
default:
break;
}
}
Поиграть можно здесь.
Буду рад услышать здоровую критику в комментах. Благодарю за внимание!
Автор: habro_user