Не ожидал! Эта прекрасная игра на самом деле реализована с помощью ECharts!

внешний интерфейс разработка игр ECharts
Не ожидал! Эта прекрасная игра на самом деле реализована с помощью ECharts!

Добро пожаловать, чтобы следовать:История DevUI с открытым исходным кодом, и добавил кDevUIГрядет строительство экологии с открытым исходным кодом!

elccc.png

предисловие

echartsэто очень мощная библиотека диаграмм, в дополнение к нашим общим функциям диаграмм,echartsСуществует функция пользовательской графики, эта функция позволяет нам очень просто рисовать на холсте какую-то нетрадиционную графику, исходя из этого, давайте пофантазируем.

Адрес мини-игры Flappy Bird (узнайте, сколько вы можете играть):

дурак cursed.GitHub.IO/E chart-flap…

Давайте реализуем это шаг за шагом.

1 Нарисуйте движущуюся птицу в системе координат

Сначала создайте контейнер echart, затем найдите изображение пиксельной птицы в Интернете и измените форму рассеяния диаграммы рассеяния на птицу, настроив изображение.

const myChart = echarts.init(document.getElementById('main'));
option = {
  series: [
    {
      name: 'bird',
      type: 'scatter',
      symbolSize: 50,
      symbol: 'image://bird.png',
      data: [
        [50, 80]
      ],
      animation: false
    },
  ]
};

myChart.setOption(option);

Чтобы заставить птицу двигаться, вам нужно задать правильную скорость и нисходящее ускорение, а также обновлять положение птицы в каждом кадре сцены. Действие птицы, летящей вверх, может быть реализовано поворотом угла, а условие срабатывания полета вверх задается космическим событием.

option = {
  series: [
    {
      xAxis: {
        show: false,
        type: 'value',
        min: 0,
        max: 200,
      },
      yAxis: {
        show: false,
        min: 0,
        max: 100
      },
      name: 'bird',
      type: 'scatter',
      symbolSize: 50,
      symbol: 'image://bird.png',
      data: [
        [50, 80]
      ],
      animation: false
    },
  ]
};

// 设置速度和加速度
let a = 0.05;
let vh = 0;
let vw = 0.5

timer = setInterval(() => {
  // 小鸟位置和仰角调整
  vh = vh - a;
  option.series[0].data[0][1] += vh;
  option.series[0].data[0][0] += vw;
  option.series[0].symbolRotate = option.series[0].symbolRotate ? option.series[0].symbolRotate - 5 : 0;

  // 坐标系范围调整
  option.xAxis.min += vw;
  option.xAxis.max += vw;

  myChart.setOption(option);
}, 25);

Эффект следующий

GIF1.gif

2 Нарисуйте препятствия с помощью пользовательской графики

пользовательские серии echarts, логика рендеринга передается разработчикомrenderItemреализация функции. Эта функция получает два параметра params и api. params содержит текущую информацию о данных и информацию о системе координат. api представляет собой набор методов, которые могут вызывать разработчики. Обычно используемые методы:

  • api.value(...), что означает вынутьdataItemзначение в . Напримерapi.value(0)Указывает на удаление текущегоdataItemЗначение первого измерения в .

  • api.coord(...), что означает выполнение вычисления преобразования координат. Напримерvar point = api.coord([api.value(0), api.value(1)])выражатьdataItemЗначения в преобразуются в точки в системе координат.

  • api.size(...), вы можете получить длину, соответствующую диапазону значений в системе координат.

  • api.style(...), вы можете получитьseries.itemStyleинформация о стиле, определенная в .

Гибко используя вышеуказанные API, вы можете преобразовывать данные данных, переданные пользователем, в положение пикселя в нужной системе координат.

renderItemфункция возвращаетechartsсерединаgraphicкласс, вы можете комбинировать разнообразную графику в нужную вам форму,графический тип. Для препятствий в нашей игре нам нужно использовать только прямоугольники для их рисования Мы используем следующие два класса.

  • тип: группа, комбинированный класс, вы можете объединить несколько классов графики в один график и поместить подкласс в дочерние элементы.

  • type: rect, класс прямоугольника, путем определения точки координат верхнего левого угла прямоугольника, а также ширины и высоты прямоугольника для определения графики.

// 数据项定义为[x坐标,下方水管上侧y坐标, 上方水管下侧y坐标]
data: [
  [150, 50, 80],
  ...
]

renderItem: function (params, api) {
    // 获取每个水管主体矩形的起始坐标点
    let start1 = api.coord([api.value(0) - 10, api.value(1)]);
    let start2 = api.coord([api.value(0) - 10, 100]);
    // 获取两个水管头矩形的起始坐标点
    let startHead1 = api.coord([api.value(0) - 12, api.value(1)]);
    let startHead2 = api.coord([api.value(0) - 12, api.value(2) + 8])
    // 水管头矩形的宽高
    let headSize = api.size([24, 8])
    // 水管头矩形的宽高
    let rect = api.size([20, api.value(1)]);
    let rect2 = api.size([20, 100 - api.value(2)]);
    // 坐标系配置
    const common = {
        x: params.coordSys.x,
        y: params.coordSys.y,
        width: params.coordSys.width,
        height: params.coordSys.height
    }
    // 水管形状
    const rectShape = echarts.graphic.clipRectByRect(
      {
        x: start1[0],
        y: start1[1],
        width: rect[0],
        height: rect[1]
      },common
    );
    const rectShape2 = echarts.graphic.clipRectByRect(
      {
        x: start2[0],
        y: start2[1],
        width: rect2[0],
        height: rect2[1]
      },
      common
    )

    // 水管头形状
    const rectHeadShape = echarts.graphic.clipRectByRect(
      {
        x: startHead1[0],
        y: startHead1[1],
        width: headSize[0],
        height: headSize[1]
      },common
    );

    const rectHeadShape2 = echarts.graphic.clipRectByRect(
      {
        x: startHead2[0],
        y: startHead2[1],
        width: headSize[0],
        height: headSize[1]
      },common
    );

    // 返回一个group类,由四个矩形组成
    return {
        type: 'group',
        children: [{
            type: 'rect',
            shape: rectShape,
            style: {
              ...api.style(),
              lineWidth: 1,
              stroke: '#000'
            }
        }, {
            type: 'rect',
            shape: rectShape2,
            style: {
              ...api.style(),
              lineWidth: 1,
              stroke: '#000'
            }
        },
        {
            type: 'rect',
            shape: rectHeadShape,
            style: {
              ...api.style(),
              lineWidth: 1,
              stroke: '#000'
            }
        },
        {
            type: 'rect',
            shape: rectHeadShape2,
            style: {
              ...api.style(),
              lineWidth: 1,
              stroke: '#000'
            }
        }]
    };
  },

Определение цвета, мы использовали, чтобы сделать водопровод блестящимechartsлинейный градиентный объект.

itemStyle: {
  // 渐变色对象
  color: {
    type: 'linear',
    x: 0,
    y: 0,
    x2: 1,
    y2: 0,
    colorStops: [{
        offset: 0, color: '#ddf38c' // 0% 处的颜色
    }, {
        offset: 1, color: '#587d2a' // 100% 处的颜色
    }],
    global: false // 缺省为 false
  },
  borderWidth: 3
},

Кроме того, используйте цикл for для случайной генерации данных для нескольких столбцов одновременно.

function initObstacleData() {
    // 添加minHeight防止空隙太小
    let minHeight = 20;
    let start = 150;
    obstacleData = [];
    for (let index = 0; index < 50; index++) {
      const height = Math.random() * 30 + minHeight;
      const obstacleStart = Math.random() * (90 - minHeight);
      obstacleData.push(
        [
          start + 50 * index,
          obstacleStart,
          obstacleStart + height > 100 ? 100 : obstacleStart + height
        ]
      )
    }
  }

Затем залейте фон картинкой из игры, и мы нарисуем всю игровую сцену:

3 Выполните обнаружение столкновений

Поскольку данные о траектории полета и препятствиях очень просты, мы можем упростить логику столкновения до квадрата изображения птицы и оценить, входят ли правый верхний и правый нижний углы в диапазон пользовательской графики.

Для диапазона столкновений по определенным координатам, поскольку столбец фиксируется на 50 значениях координат на сетку, а ширина также фиксирована, диапазон столкновений по абсциссам можно упростить до(x / 50 % 1) < 0.6

в определенном диапазоне, в зависимости отMath.floor(x / 50)После получения соответствующих данных можно определить, перекрываются ли координаты двух углов с площадью столбца. Судя по кадру анимации, если он перекрывается, останавливаем воспроизведение анимации и игра окончена.

// centerCoord为散点坐标点
function judgeCollision(centerCoord) {
  if (centerCoord[1] < 0 || centerCoord[1] > 100) {
    return false;
  }
  let coordList = [
    [centerCoord[0] + 15, centerCoord[1] + 1],
    [centerCoord[0] + 15, centerCoord[1] - 1],
  ]

  for (let i = 0; i < 2; i++) {
    const coord = coordList[i];
    const index = coord[0] / 50;
    if (index % 1 < 0.6 && obstacleData[Math.floor(index) - 3]) {
      if (obstacleData[Math.floor(index) - 3][1] > coord[1] || obstacleData[Math.floor(index) - 3][2] < coord[1]) {
        return false;
      }
    }
  }
  return false
}

function initAnimation() {
  // 动画设置
  timer = setInterval(() => {
    // 小鸟速度和仰角调整
    vh = vh - a;
    option.series[0].data[0][1] += vh;
    option.series[0].data[0][0] += vw;
    option.series[0].symbolRotate = option.series[0].symbolRotate ? option.series[0].symbolRotate - 5 : 0;

    // 坐标系范围调整
    option.xAxis.min += vw;
    option.xAxis.max += vw;

    // 碰撞判断
    const result = judgeCollision(option.series[0].data[0])

    if(result) { // 产生碰撞后结束动画
      endAnimation();
    }

    myChart.setOption(option);
  }, 25);
}

Суммировать

echartsОн предоставляет мощные возможности настройки графического рисунка. Чтобы правильно использовать эту возможность, вы должны понимать логику преобразования между точками координат данных и точками координат пикселей. Это важный шаг для визуализации данных на холсте.

Используйте эту функцию с пользой, и вы больше не будете бояться странных требований к диаграмме продукта.

Адрес источника:GitHub.com/дурак ругает/о…

DevUI набирает участников

В настоящее времяVue DevUIиNg DevUIМы набираем большое количество участников сообщества, добро пожаловать к нам!

Добавьте помощника WeChat:devui-official, пригласить вас в группу технического обмена DevUI.

Добро пожаловать, чтобы следовать:История DevUI с открытым исходным кодом, и добавил кDevUIГрядет строительство экологии с открытым исходным кодом!