Всем привет! В этой статье я продемонстрирую процесс создания игры про грузовик-монстр с использованием Box2DCreateJS.

Если вы не знакомы с Box2DCreateJS, вы можете обратиться к этой статье, чтобы узнать больше.

В нашем сценарии у нас будет грузовик-монстр и рампа, которую мы можем использовать для прыжков и маневров. Мы будем использовать клавиатуру для управления грузовиком.

К концу статьи вот чего мы добьемся!

Прежде чем приступить к кодированию, давайте создадим первоначальную настройку проекта, как описано в этой статье. Как только наш проект будет готов, давайте начнем!

Скачать изображения игры

В этой игре мы будем использовать три изображения:

Загрузите и добавьте их в папку проекта images.

Импорт Box2DCreateJS

Давайте включим несколько файлов библиотеки Box2DCreateJS в index.html. Для этого включите следующие строки кода.

<!DOCTYPE html>
<html lang="en">
  
    <head>
        ...
        <script src="box2dcreatejs/js/Box2dWeb/Box2dWeb-2.1.a.3.js"></script>
        <script src="box2dcreatejs/js/CreateJS/easeljs-1.0.0.min.js"></script>
        <script src="box2dcreatejs/js/CreateJS/preloadjs-1.0.0.min.js"></script>

        <script src="box2dcreatejs/js/Box2DCreateJS/WorldManager.js"></script>
        <script src="box2dcreatejs/js/Box2DCreateJS/Entity.js"></script>
        <script src="box2dcreatejs/js/Box2DCreateJS/Render.js"></script>
        <script src="box2dcreatejs/js/Box2DCreateJS/LoadingIndicator.js"></script>
        <script src="box2dcreatejs/js/Box2DCreateJS/Link.js"></script>

        <script src="MyGame.js"></script>
        ...
    </head>
    ...
</html>

Создайте и запустите WorldManager

В MyGame.js определите переменную _worldManager и создайте ее экземпляр внутри функции initialize.

В этом примере мы используем свойство preLoad для загрузки изображений перед запуском игры. После завершения предварительной загрузки вызывается функция gameLogic. Функция gameLogic, которая в настоящее время пуста, определена вне функции initialize.

Важно отметить, что мы включили режим отладки (свойство enableDebug) для _worldManager, установив для него значение true. Это позволит нам увидеть сущности мира и то, как они выглядят за кулисами.

this.Box2DCreateJS = this.Box2DCreateJS || {};

(function () {

    function MyGame() {
        this.initialize()
    }

    Box2DCreateJS.MyGame = MyGame

    let _worldManager

    MyGame.prototype.initialize = function () {
        const easeljsCanvas = document.getElementById("easeljsCanvas")
        const box2dCanvas = document.getElementById("box2dCanvas")

        _worldManager = new Box2DCreateJS.WorldManager(
            easeljsCanvas, box2dCanvas, {
                world: new box2d.b2World(new box2d.b2Vec2(0, 10), true),
                enableDebug: true,
                preLoad: {
                    files: [
                        'images/monster-chassis.png',
                        'images/monster-tire.png',
                        'images/background.jpg',
                    ],
                    onComplete: gameLogic
               }
            }
        )
    }

    function gameLogic() {
    }

}())

Создайте нижний предел и пределы сценария

Теперь мы приступим к созданию пола и установке ограничений для нашего сценария. Для этого мы реализуем функцию createFloorAndLimits в MyGame.js, а затем добавим к ней вызов в функции gameLogic.

...
function gameLogic() {
    createFloorAndLimits()
}

function createFloorAndLimits() {
    const staticRender = {
        type: 'draw',
        drawOpts: {
            bgColorStyle: 'solid',
            bgSolidColorOpts: { color: 'black' }
        }
    }

    // Floor
    _worldManager.createEntity({
        type: 'static',
        x: 5000, y: 490,
        shape: 'box',
        boxOpts: { width: 10000, height: 20 },
        render: staticRender
    })

    // Left Wall
    _worldManager.createEntity({
        type: 'static',
        x: 0, y: 0,
        shape: 'box',
        boxOpts: { width: 10, height: 1000 },
        render: staticRender
    })

    // Right Wall
    _worldManager.createEntity({
        type: 'static',
        x: 10000, y: 0,
        shape: 'box',
        boxOpts: { width: 10, height: 1000 },
        render: staticRender
    })
}
...

Холст EaselJS и Box2D имеет ширину 980 пикселей и высоту 500 пикселей.

На представленном ниже изображении вы можете получить приблизительное представление о размерах static объектов по отношению к холсту.

Давайте проверим, как это до сих пор

Создайте пейзаж

Вместо того, чтобы довольствоваться простым синим фоном, давайте улучшим его, добавив изображение. Для этого нам нужно импортировать файл Landscape.js в index.html.

<!DOCTYPE html>
<html lang="en">
    <head>
        ...
        <script src="box2dcreatejs/js/Box2DCreateJS/Landscape.js"></script>
        ...
    </head>
    ...
</html>

Затем реализуйте функцию createLandscape и включите ее вызов в функцию gameLogic.

...
function gameLogic() {
    createFloorAndLimits()
    createLandscape()
}

...

function createLandscape() {
    _worldManager.createLandscape({
        x: 5000, y: 230,
        shape: 'box',
        boxOpts: { width: 10000, height: 500 },
        render: {
            opacity: 0.7,
            type: 'draw',
            drawOpts: {
                bgColorStyle: 'transparent',
                bgImage: 'images/background.jpg',
                repeatBgImage: 'repeat-x'
            }
        }
    })
}
...

После включения ландшафта это упрощенный вид размеров.

Проверим как получается.

Создайте грузовик-монстр

Приступим к созданию монстр-трака. Для этого мы реализуем функцию createMonsterTruck в MyGame.js и включим ее вызов в функцию gameLogic.

...
function gameLogic() {
    createFloorAndLimits()
    createLandscape()
    const monsterTruck = createMonsterTruck()
}

...

function createMonsterTruck() {
    const TRUCK_X = 360, TRUCK_Y = 250

    const chassis = _worldManager.createEntity({
        type: 'dynamic',
        x: TRUCK_X, y: TRUCK_Y,
        shape: 'box',
        boxOpts: { width: 150, height: 50 },
        render: {
            type: 'image',
            imageOpts: {
                image: 'images/monster-chassis.png',
                adjustImageSize: true
            }
        },
        name: 'chassis'
    })

    const tireRender = {
        type: 'image',
        imageOpts: {
            image: 'images/monster-tire.png',
            adjustImageSize: true
        }
    }

    const backTire = _worldManager.createEntity({
        type: 'dynamic',
        x: TRUCK_X - 45, y: TRUCK_Y + 45,
        shape: 'circle',
        circleOpts: { radius: 30 },
        render: tireRender,
        bodyDefOpts: { angularVelocity: 70 },
        fixtureDefOpts: { restitution: 0.2 }
    })

    const frontTire = _worldManager.createEntity({
        type: 'dynamic',
        x: TRUCK_X + 50, y: TRUCK_Y + 45,
        shape: 'circle',
        circleOpts: { radius: 30 },
        render: tireRender,
        bodyDefOpts: { angularVelocity: 70 },
        fixtureDefOpts: { restitution: 0.2 }
    })

    const link1 = _worldManager.createLink({
        entityA: chassis,
        entityB: backTire,
        type: 'revolute',
        localAnchorA: { x: -1.6, y: 1.6 }
    })

    const link2 = _worldManager.createLink({
        entityA: chassis,
        entityB: frontTire,
        type: 'revolute',
        localAnchorA: { x: 1.6, y: 1.6 },
    })

    return { chassis, backTire, frontTire }
}
...

Короче говоря, грузовик-монстр состоит из трех объектов dynamic: грузовик-монстр chassis, который имеет форму box, и шины грузовика-монстра, frontTire и backTire, которые имеют форму circle. chassis подключен к frontTire и backTire с помощью Link.

Вот как связаны динамические объекты

Давайте проверим, как это происходит до сих пор

Установите плеер

Давайте добавим файлы Player.js и Camera.js в наш проект. Мы можем сделать это, включив следующие строки в ваш файл index.html.

<!DOCTYPE html>
<html lang="en">
    <head>
        ...
        <script src="box2dcreatejs/js/Box2DCreateJS/Player.js"></script>
        <script src="box2dcreatejs/js/Box2DCreateJS/Camera.js"></script>
        ...
    </head>
    ...
</html>

Далее мы создадим экземпляр переменной player в MyGame.js, указав события, которые можно с ней выполнять, например, перемещение forward и backward и вращение clockwise или anticlockwise.

Кроме того, мы установим угловую скорость 70 и -70, чтобы вращать frontTire, чтобы переместить игрока forward или backward соответственно. Кроме того, мы установим угловую скорость на chassis, чтобы позволить игроку маневрировать, вращая его по часовой стрелке (угловая скорость 1) или против часовой стрелки (угловая скорость -1).

Наконец, мы включим camera из player, чтобы мы всегда могли держать грузовик-монстр в поле зрения.

...
function gameLogic() {
    createFloorAndLimits()
    createLandscape()
    const monsterTruck = createMonsterTruck()
    const chassis = monsterTruck.chassis
    const frontTire = monsterTruck.frontTire

    const player = _worldManager.createPlayer(chassis, {
        camera: {
            adjustX: 490,
            xAxisOn: true
        },
        events: {
            backward: () => frontTire.getB2Body().SetAngularVelocity(-70),
            forward: () => frontTire.getB2Body().SetAngularVelocity(70),
            anticlockwise: () => chassis.getB2Body().SetAngularVelocity(-1),
            clockwise: () => chassis.getB2Body().SetAngularVelocity(1)
        }
    })
}
...

Добавьте клавиатуру

Чтобы включить управление player, нам нужно включить клавиатуру. Для этого нам нужно импортировать файл KeyboardHandler.js в index.html.

<!DOCTYPE html>
<html lang="en">
    <head>
        ...
        <script src="box2dcreatejs/js/Box2DCreateJS/KeyboardHandler.js"></script>
        ...
    </head>
    ...
</html>

В MyGame.js мы используем _worldManager для вызова функции createKeyboardHandler. Эта функция принимает карту клавиш клавиатуры, которые будут использоваться для элементов управления, таких как:

  • ArrowDown: переместить игрока назад;
  • ArrowUp: переместить игрока вперед;
  • ArrowLeft: вращать игрока против часовой стрелки;
  • ArrowRight: вращать игрока по часовой стрелке
...
function gameLogic() {
    ...

    const player = _worldManager.createPlayer(chassis, {
        ...
    })

    _worldManager.createKeyboardHandler({
        keys: {
            ArrowDown: {
                onkeydown: () => _worldManager.getPlayer().backward(),
                keepPressed: true
            },
            ArrowUp: {
                onkeydown: () => _worldManager.getPlayer().forward(),
                keepPressed: true
            },
            ArrowLeft: {
                onkeydown: () => _worldManager.getPlayer().anticlockwise(),
                keepPressed: true
            },
            ArrowRight: {
                onkeydown: () => _worldManager.getPlayer().clockwise(),
                keepPressed: true
            }
        }
    })
}
...

Давай проверим

Создать рампу

Чтобы добавить азарта в нашу игру, давайте создадим пандус. Для этого нам потребуется реализовать функцию createRamp в MyGame.js и включить ее вызов в нашу функцию gameLogic.

...
function gameLogic() {
    ...
    createRamp()
}

...

function createRamp() {
    const staticRender = {
        type: 'draw',
        drawOpts: {
            bgColorStyle: 'solid',
            bgSolidColorOpts: { color: 'black' }
        }
    }

    // Ramp 1
    _worldManager.createEntity({
        type: 'static',
        x: 4000, y: 450, angle: 75,
        shape: 'box',
        boxOpts: { width: 10, height: 400 },
        render: staticRender
    })

    // Ramp 2
    _worldManager.createEntity({
        type: 'static',
        x: 4390, y: 450, angle: -75,
        shape: 'box',
        boxOpts: { width: 10, height: 400 },
        render: staticRender
    })
}
...

Проверим, достаточно ли высок пандус 😅

Отключить отладку

Теперь, когда наша игра завершена, мы можем отключить свойство WorldManager enabledDebug. Для этого нам просто нужно установить для свойства enabledDebug значение false в MyGame.js.

this.Box2DCreateJS = this.Box2DCreateJS || {};

(function () {

    ...
    MyGame.prototype.initialize = function () {
        ...
        _worldManager = new Box2DCreateJS.WorldManager(
            easeljsCanvas, box2dCanvas, {
                world: new box2d.b2World(new box2d.b2Vec2(0, 10), true),
                enableDebug: false,
                ...
            }
        )
    }
    ...

}())

Пришло время играть! 🕹

Следите за обновлениями

В ближайшие дни я поделюсь серией статей, целью которых является демонстрация обширных возможностей Box2DCreateJS.

В этих статьях я покажу, насколько просто и просто создавать увлекательные игры с помощью Box2DCreateJS.

Исходный код можно найти в Box2DCreateJS GitHub Репозиторий.

Так что следите за обновлениями, чтобы отправиться в захватывающее путешествие в мир разработки игр на JavaScript!