предисловие
Полгода назад я использовал js и canvas для имитации онлайн-игры Legend of Blood (адрес) После завершения основной функции остальные являются кучей данных, куча времени для завершения задачи, ничего свежая, поэтому прогресс очень медленный. Микросет писем «бомбы и одна бомба» больше огня, потому что он включает в себя физический двигатель (для реального), то руки попробовали его. Доля 10 часов, только завершила демо,
Сводка данных
-
Онлайн-демонстрация:Нажмите, чтобы играть
-
библиотека рендеринга холста:Поддержка физического движка и инструментов отладки Chrome здесь
Готов к работе
Правила игры в WeChat очень простые, карту видно, не зайдешь. Возникло несколько трудностей при разработке:
1. Физический движок
Конечно, это не обязательно, это не что иное, как изменение положения картинки, а эффекты падения и столкновения вы можете смоделировать сами. Однако, поскольку я гнался за (ву) поиском (ли) телом (курицей) опытом (ча), я начал искать сторонний физический движок.
В конце концов я использовалjs версия бурундука(Эта библиотека является низкоуровневой вычислительной библиотекой, поэтому звезд не так много, но более известные физические движки hilo и cocos2d используют эту библиотеку). Одна из основных причин заключается в том, что функциями этой библиотеки являются только физические операции и вспомогательные функции, такие как гравитация, упругость, трение, плавучесть и т. д. Объем конечно меньше. Ведь мы просто пишем небольшую демку, а внедрение игрового фреймворка, скорее всего, увеличит стоимость.
Тем не менее, я столкнулся с несколькими редкими ошибками, когда использовал его, что должно быть упущением автора, а также в выпуске были отзывы. Видя, что частота обновления у автора очень низкая, я внес некоторые изменения, когда использовал его. Если другие сталкиваются с ошибками js при их использовании, вы можете попробоватьздесь
2. Рендеринг пользовательского интерфейса
Я выбираю холст, потому что он требует частых обновлений стиля, а переписывание стиля в каждом кадре требует слишком много производительности. И если вы напишете это на холсте, вы сможете в будущем перебирать некоторые эффекты коллизий.
предварительно упакованныйeasycanvasБиблиотеки, которые «переводят» древовидные структуры данных в «объекты на холсте». На этот раз добавлен плагин, поддерживающий бурундука, так что для разработки всей «бум» нужно только управлять данными, а работы по рендерингу очень мало.
начать разработку
html и фон
Из-за небольшого размера проекта я свалил html, css и js в один файл (после того, как я его наконец написал, то обнаружил, что это всего 400 строк вместе с комментариями).
Сначала создайте пустой html, чтобы выглядеть высоким, я искал фоновое изображение темы неба.
<style>
body {
margin: 0;
text-align: center;
background: black;
}
canvas {
border: 1px solid grey;
height: 100%;
max-width: 100%;
background-image: url(http://a3.topitme.com/2/d4/ff/1144306867e94ffd42o.jpg);
background-size: auto 100%;
}
</style>
<body>
<canvas id="el"></canvas>
</body>
возможные переменные
Затем подготовьте некоторые данные, которые нам нужно использовать. Например, ширина и высота игры, размер мяча, текущее состояние игры (можно ли его бросить), количество маленьких мячей, которые можно бросить каждый раз, счет игрока, блабла.
Поскольку он написан непосредственно в html-коде, чтобы быть совместимым со старыми браузерами, может быть только var var go.
// 在html直接写代码,不编译、不构建,不然应该用const的
var width = 400, height = 600, ballSize = 20;
// 游戏状态
var canShoot = true;
var score = 0, ballLeft = 0, ballCount = 5;
var blockArray = [];
// 图片
var BALL = Easycanvas.imgLoader('./ball.png');
var BLOCK = Easycanvas.imgLoader('./block.jpg');
var TRIANGLE = Easycanvas.imgLoader('./triangle.png');
// 给每个东西起一个type,后面会用来做碰撞检测
var BALL_TYPE = 1, BLOCK_TYPE = 2, BORDER_TYPE = 3, BOTTOM_TYPE = 4, BONUS_TYPE = 5;
Затем напишите на холсте счет и количество мячей. Сначала создайте экземпляр easycanvas шириной 400 и высотой 600. Затем добавьте 2 объекта. В качестве вершины берется левый верхний угол (5,5), а дробь записывается в правый нижний угол. В качестве вершины берется правый верхний угол (395, 5), а в левый нижний угол записывается текущее количество шаров.
// 初始化easycanvas实例
var $Painter = new Easycanvas.painter();
$Painter.register(el, {
width: width,
height: height,
});
$Painter.start();
$Painter.add({
content: {
text: function () {
return '得分:' + score;
}
},
style: {
tx: 5, ty: 5,
textAlign: 'left', textVerticalAlign: 'top',
color: 'black'
}
});
$Painter.add({
content: {
text: function () {
return '小球个数:' + ballCount;
}
},
style: {
tx: 395, ty: 5,
textAlign: 'right', textVerticalAlign: 'top',
color: 'black'
}
});
добавить блок
Затем установите гравитацию всей сцены и добавьте к ней несколько блоков. Каждый блочный объект содержит дочерний элемент, который используется для отображения числа (и нескольких выпуклостей). Чтобы квадраты не перекрывались, мы позволяем координатам x квадратов циклически повторяться на 50, 100, 150, ..., 300, 350. При этом, чтобы не выглядеть «слишком аккуратно», добавляйте за раз небольшое случайное число, чтобы эти квадраты были разбросаны. («шахматное» относится к неравномерности, а «чжи» относится к заинтересованности. Хотя расположение вещей описывается как неравномерное, но оно очень интересное, что заставляет людей хорошо выглядеть. — Определенная степень)
Размер каждого квадрата 30x30, поэтому фигуры включают 4 стороны, например, от (0,0) до (30,0) — одна сторона. Эти блоки невесомы (не падают), поэтому для параметра static установлено значение true. Чтобы быть более неоднородным, мы даем ему случайный угол для поворота.
В каждом квадрате находится ребенок с написанным на нем числом. Нет необходимости устанавливать поворот на число, иначе 6 и 9 могут быть неразличимы.
// 初始化easycanvas物理引擎,添加一个有物理树形的空容器
var $space = new Easycanvas.class.sprite({
physics: {
gravity: 2, // 重力默认为1,但是游戏进程有点慢,看着不够爽
accuracy: 2,
},
});
$Painter.add($space);
var space = $space.launch();
// 防止方块重叠,记录上一次方块的X坐标
var lastBlockPositionX = 50;
function addBlock (max, boolAddToBottom) {
var deg = Math.floor(Math.random() * 360);
var sprite = $space.add(new Easycanvas.class.sprite({
name: 'block',
content: {
img: BLOCK,
},
physics: {
shape: [
[[0, 0], [0, 30]],
[[0, 30], [30, 30]],
[[30, 30], [30, 0]],
[[30, 0], [0, 0]]
],
mass: 1,
friction: 0.1,
elasticity: 0.9,
collisionType: BLOCK_TYPE,
static: true,
},
style: {
tw: 30, th: 30,
tx: lastBlockPositionX + Math.floor(Math.random() * 20 - 10),
ty: boolAddToBottom ? 500 : height - 100 - Math.floor(Math.random() * 100),
locate: 'lt',
rotate: deg,
},
children: [{
content: {
text: Math.floor(Math.random() * max) + 1,
},
style: {
color: 'yellow',
textAlign: 'center',
textVerticalAlign: 'middle',
textFont: '28px Arial',
tx: 15, ty: 10
}
}]
}));
sprite.physicsOn();
blockArray.push(sprite);
lastBlockPositionX += 50;
if (lastBlockPositionX > 350) {
lastBlockPositionX = 50;
}
}
Далее мы стремимся сделать часть. Грубо функционируют, есть ряд маленьких точек, последуют движение мыши, и есть ощущение весны.
Прежде всего, чтобы записать трек мыши, мы добавляем прослушиватели событий к экземпляру easycanvas $Painter. В игре «Отскок» мяч нельзя запускать вверх. Поэтому, записывая значение координаты Y мыши, пусть оно будет не меньше 30.
// 记录鼠标轨迹
var mouse = {x: 300, y: 50};
var mouseRecord = function ($e) {
mouse.x = $e.canvasX;
mouse.y = Math.max(30, $e.canvasY);
};
$Painter.register(el, {
width: width,
height: height,
events: {
mousemove: mouseRecord,
touchmove: mouseRecord,
mouseup: shoot,
touchend: shoot,
}
});
прицеливание мяча
Далее мы добавляем 7 шаров и располагаем их в линию, равномерно распределяя от точки (300, 20) прямо над игрой до положения мыши. Конкретная логика заключается в том, что мы делим разницу координат между положением мыши и (300, 20) на 6 равных частей, координаты первого шарика смещены к положению мыши на 0/6, второго шарика смещены на 1/ 6..., и, наконец, шарик со смещением 6/6 (только что приземлился на позицию мыши). Для этих шаров мы придаем им прозрачность и не включаем правила физики (поскольку шары не могут упасть на этом этапе). Мы надеваем крюк на каждый мяч, и когда игрок стреляет по реальному мячу, удаляем прицельный мяч.
// 显示瞄准轨迹
var startAim = function () {
for (var i = 0; i < 7; i ++) {
$Painter.add({
content: {
img: BALL,
},
data: {
gap: i / 6,
},
style: {
tx: function () {
return 200 + (mouse.x - 200) * this.data.gap;
},
ty: function () {
return 20 + (mouse.y - 20) * this.data.gap;
},
tw: 20, th: 20,
opacity: 0.4,
},
hooks: {
shoot: function () {
this.remove();
}
}
});
}
};
startAim();
бить по мячу
Далее мы добавляем настоящий мяч (тот, на который распространяются правила физики).
Во время броска мы транслируем событие броска, чтобы удалить мяч, на который только что нацелились.
После этого мы непрерывно вызываем метод addBall для создания шаров с интервалом в 100 миллисекунд. В методе addBall мы устанавливаем правила физики для каждого мяча. Включая форму, эластичность, трение и т.д.
Здесь есть яма, то есть, как только начнется стрельба, как бы мышка ни двигалась, направление стрельбы изменить нельзя. Поэтому сначала нам нужно записать текущее значение мыши.Здесь мы используем JSON.parse(JSON.stringify(mouse)) для копирования простого объекта.
Вот еще одна ямка: в игре «отскок» на только что брошенный мяч не действует гравитация (иначе нет смысла целиться). Поэтому мы добавляем силу, противоположную гравитации, на каждый шар, противодействующую гравитации. (В других частях кода есть реализация «отмены силы при однократном столкновении мяча», которая здесь не публикуется для ясности).
При этом мы прибавляем мячу начальную скорость.
Здесь есть еще одна яма (такая досадная): как ни стреляй, начальная скорость мяча одна и та же. Даже если положение прицеливания мяча очень близко к положению для броска, скорость не может быть медленной. Здесь нужно корректировать начальную скорость, здесь используется знаменитая теорема Пифагора: сумма квадратов двух прямоугольных сторон прямоугольного треугольника равна квадрату гипотенузы.
function shoot () {
if (!canShoot) return;
$Painter.broadcast('shoot');
canShoot = false;
var currentMouse = JSON.parse(JSON.stringify(mouse));
for (var i = 0; i < ballCount; i++) {
setTimeout(function () {
addBall(currentMouse);
}, i * 100);
}
};
function addBall (mouse) {
ballLeft++;
var $ball = new Easycanvas.class.sprite({
name: 'ball',
content: {
img: BALL,
},
physics: {
shape: [
// 形状是一个以(ballSize / 2, ballSize / 2)为圆心的,半径也是ballSize / 2的圆
// 改成位运算符吧,看着能高大上一点(其实在这里卵用没有)
[ballSize >> 1, ballSize >> 1, ballSize >> 1]
],
mass: 1, // 质量
friction: 0.1, // 摩擦(摩擦太大了会损失能量)
elasticity: 0.8, // 弹性
collisionType: BALL_TYPE,
},
style: {
tw: ballSize, th: ballSize,
sx: 0, sy: 0,
tx: 200,
ty: 20,
zIndex: 1,
},
});
$space.add($ball);
$ball.physicsOn();
// 抵消重力
$ball.$physics.body.applyForce({x: 0, y: 1000}, {x: 0, y: 0});
// 初速度
var speed = {
x: (mouse.x - 200) / (20 - mouse.y),
y: 1
};
// 修正速度,确保从各个角度射出小球的速度差不多
// 这里用到的著名的高等数学知识:勾股定理
var muti = Math.sqrt(Math.pow(speed.x, 2) + Math.pow(speed.y, 2)) / 700;
$ball.$physics.body.setVel({
x: -speed.x / muti,
y: -speed.y / muti,
});
}
разное
Набросок уже есть, и последняя часть уже не сложна. Но в итоге ям все же больше:
Например, мяч может остановиться на квадрате (это так умно), что является скоростью, которую люди должны дать небольшой шар («бомба игра» также делает эту игру).
Например, мяч попал в коробку, столкновение может сработать дважды, потому что небольшой эффект я отложу. Это связано с тем, что точность времени не слишком мала, столкновение мяча происходит не так быстро, пока следующий кадр достигает двух границ.