Изучите pixi.js из League of Legends

JavaScript
Изучите pixi.js из League of Legends

предисловие

Первый обзор предыдущей статьиУчим холст по смайликам, Предварительное понимание основного использования холста и общего метода написания класса 2D-графики реализуется через случай. Эта проблема углубляется в холст, понимает библиотеку рисования холста и делает простойлол прогулочная игра (нажмите, чтобы играть). Если вам кажется, что блок столкновений немного уродлив, на самом деле, я сделал это специально, изучите его и измените на желаемую картинку, разве это не красиво.

что такое пикси

Сначала посмотрите объяснение на официальном веб-сайте: Создавайте красивый цифровой контент с помощью самого быстрого и гибкого средства визуализации 2D WebGL. Самая быстрая и гибкая библиотека 2D-рендеринга. Да, вы правильно прочитали, он просто раздут, а внутренняя реализация по умолчанию использует webgl.
pixi.js — это мощный движок холста, который поддерживает взаимодействие событий на ПК и мобильных устройствах и может выбирать метод рендеринга с webgl2 на canvas в зависимости от устройства пользователя, так что моей маме не нужно беспокоиться о том, что мы не понимаем wegbl, мы можем использовать pixi, чтобы насладиться аппаратным ускорением, предоставляемым webgl. В разработке он часто используется на страницах активности H5, мини-играх и других сценариях.

Основное использование

Сначала загрузите npm install pixi.js -S В настоящее время pixi достигла версии 5.3. После V5 я начал рефакторить его с помощью ts.Для тех, кто плохо разбирается в ts, если вы хотите увидеть исходный код, вы можете посмотреть версию V4.

1. Application

import { Application } from 'pixi.js'
const app = new Application({
  width: 800,
  height: 800,
  antialias: true,    // default: false 反锯齿
  transparent: false, // default: false 透明度
  resolution: 1       // default: 1 分辨率
})
document.body.appendChild(app.view)

Видно, что pixi создает приложение через класс Application и возвращает экземпляр приложения.В приложении app.view является элементом dom холста, и следующие операции над классом приложения также будут отображаться в холст app.view. . Друзья, которые разрабатывали мобильные терминалы, должны видеть, что атрибут разрешения используется для адаптации dpi при инициализации, а на стороне ПК обычно используется 1. После того, как приложение будет создано, оно также будет иметь два важных свойства: app.stage и app.render. app.stage — это нижний слой холста, и все слои должны быть добавлены на сцену для отображения.

2. Container

Класс контейнера используется для создания каждого нового слоя. Короче говоря, это наиболее распространенная концепция div в HTML. Упомянутый выше app.stage на самом деле является контейнером. Я думаю, вы поймете, когда увидите его здесь. Приложение. stage на самом деле является концепцией body в HTML.

// 在continer1中创建一个起点(0,0),宽高100*100的矩形
const container1 = new Container()
const rectangle = new Graphics()
rectangle.beginFill(0x66CCFF)
rectangle.drawRect(0,0,100, 100);
container1.position.set(100, 100)
container1.addChild(rectangle)
app.stage.addChild(container1)

// 在container2中创建文本, 通过container.x / y 或者container.position来控制坐标
container1.position.set(100, 100)
const style = new TextStyle({
 fontSize: 36,
 fill: "white",
 stroke: '#ff3300',
 strokeThickness: 4,
 dropShadow: true,
})
const message = new Text("你好 Pixi", style)
container2.position.set(300, 100)
container2.addChild(message)

3. Sprite

Спрайт — это объект, используемый для обработки изображений.

// 加载一张背景图
const bg = Sprite.from(images/lol-bg.jpg)
app.stage.addChild(bg)

4. Loader

Загрузчик, через загрузчик можно загружать пачки изображений за один раз

const IMAGES = [{
  name: '1',
  url: 'images/1.png'
}, {
  name: '2',
  url: 'images/2.png'
}, {
  name: '3',
  url: 'images/lol-bg.jpg'
}]
app.loader.add(IMAGES).load(() => {
	console.log('加载完成')
})

Сделайте небольшую игру League of Legends

Мечтаю вернуться к S8 и IG выиграть чемпионат, очень бы хотелось игру лол Я вспоминаю те дни, когда я скакал по полю боя, а теперь я кодовый инструмент. Я старый геймер лол.Хотя у меня больше, чем сердце, но я не достаточно силен.Я хочу оседлать лошадь и отправиться в путь супербога.Моя скорость рук уже давно не успевает,и рука скорость уже давно не успеваю.сейчас играю только изредка в несколько игр по выходным.,пока Ionian Gold 1. Я видел, как пользователь сети играл в небольшую игру с позиционированием Ясуо раньше. Я попробовал это по прихоти, но не смог найти никаких изображений для Ясуо. Мы заменили его уродливой волшебницей.

код инициализации

  1. Написать элемент конфигурации
// 新建一个confjg.js
export const WIDTH = 1000
export const HEIGHT = 600
export const IMAGES = [{
  name: 'player',
  url: 'images/1.png'
}, {
  name: 'background',
  url: 'images/2.png'
}, {
  name: 'starBackground',
  url: 'images/lol-bg.jpg'
}]

export const PLAYER_OPTIONS = {
  x: WIDTH / 2,
  y: HEIGHT / 2,
  scale: 0.3,
  width: 356,
  height: 220,
  speed: 3
}
  1. построить приложение
// 创建一个返回app实例的app.js文件
import { Application } from 'pixi.js'
import { WIDTH, HEIGHT } from './config'
export function appFactory() {
  const app = new Application({
    width: WIDTH,
    height: HEIGHT,
    antialias: true,    // default: false 反锯齿![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/acd47611a1484489b4245a8f48afdda3~tplv-k3u1fbpfcp-watermark.image)
    transparent: false, // default: false 透明度
    resolution: 1       // default: 1 分辨率
  })
  
  document.body.appendChild(app.view)
  app.view.oncontextmenu = (e) => {  e.preventDefault()}
  return app
}
  1. написать основной код
// 新建index.js 通过前面两步, 精简主代码, 所有的游戏逻辑都在加载完图片之后开始
import { WIDTH, HEIGHT, IMAGES } from './config'
import { appFactory } from './app'
const app = appFactory()
app.loader.add(IMAGES).load(setup)
function setup() {
	// todo 加载完图片后
    initScene()
    app.ticker.add(() => {
    	// todo 场景更新、动画和碰撞检测
    })
}
function initScene() {
	// todo 初始化3个场景(开始场景、游戏场景、结束场景)
    startScene = new Container()
    startScene.name = 'start'
    playScene = new Container()
    playScene.name = 'play'
    overScene = new Container()
    overScene.name = 'over'
    app.stage.addChild(startScene)
    app.stage.addChild(playScene)
    app.stage.addChild(overScene)
}
// 切换场景
function changeScene(sceneName) {
const scenes = [startScene, playScene, overScene ]
  scenes.forEach((scene) => {
    currentScene = sceneName
    if (sceneName === scene.name) {
      scene.visible = true
     }
    })
}

стартовая сцена

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

  1. Кнопка "Создать"
// 新建buttun.js 抽离出来精简index.js
import { Graphics, Text } from 'pixi.js'
export function getButton(text) {
  const button = new Graphics()
  button.lineStyle(2, 0x000, 0.3)
  button.beginFill(0xF5E817)
  button.drawPolygon([
    0, 0,
    180, 0,
    150, 48,
    0, 48,
  ])
  button.endFill();
  button.interactive = true;
  if(text) {
    const message = new Text(text)
    message.x = 28
    message.y = 12
    message.style = { fill: "black", fontSize: 24 }
    button.addChild(message)
    button.on('mouseover', () => {
      message.style.fill = 'white'
    })
    button.on('mouseout', () => {
      message.style.fill = 'black'
    })
  }
  return button
}

  1. Добавьте фоновое изображение и добавьте кнопку
  // 添加按钮
  startScene = new Container()
  startScene.name = 'start'
  const backgroundImage = new Sprite(app.loader.resources['starBackground'].texture)
  backgroundImage.name = 'background'
  const scareX = WIDTH / backgroundImage.width
  const scareY = HEIGHT / backgroundImage.height
  backgroundImage.scale.set(scareX, scareY)
  startScene.addChild(backgroundImage)
  // 添加按钮
  const startButton = getButton('开始游戏')
  startButton.position.set(24, 240)
  startScene.addChild(startButton)
  startButton.on('click', () => {
    changeScene('play')
  })
  const otherButton = getButton('其他功能')
  otherButton.position.set(24, 320)
  startScene.addChild(otherButton)
  app.stage.addChild(startScene)
  startScene.visible = true

3. Добавьте анимацию движения камеры Анимация движения камеры визуально представляет собой процесс перемещения кругов и медленного увеличения. Этот вид рисования должен быть разделен на 2 этапа, поэтому я подумал о том, чтобы напрямую изменить увеличение круга линзы, чтобы добиться двух комбинированных эффектов перемещения и увеличения. Если вы не можете понять это, вы должны быть в состоянии понять реализация следующего кода.

    // 圆的内半径
   const radius = 100;
   // 模糊量
   const blurSize = 32;
   const circle = new Graphics()
     .beginFill(0xFF0000)
     .drawCircle(radius + blurSize, radius + blurSize, radius)
     .endFill()
   circle.filters = [new filters.BlurFilter(blurSize)];
   const bounds = new Rectangle(0, 0, WIDTH, HEIGHT);
   const texture = app.renderer.generateTexture(circle, SCALE_MODES.NEAREST, 1, bounds);
   const focus = new Sprite(texture);
   app.stage.addChild(focus);
   const backgroundImage = startScene.getChildByName('background')
   backgroundImage.mask = focus
   let animateTimer = null
   const tween = new Tween(focus.scale).to({
     x: 5,
     y: 5,
   }, 1500).easing(Easing.Quadratic.In).onComplete((() => {
     if(animateTimer) {
       cancelAnimationFrame(animateTimer)
       animateTimer = null
     }
   })).start()

   function animate() {
     animateTimer = requestAnimationFrame(animate)
     tween.update()
   }
   requestAnimationFrame(animate)

Используйте tween, чтобы сделать tween-анимацию, и увеличьте маску и круг от 1x до 5x. Стартовая сцена готова.

игровая сцена

  1. фоновый макет
  playScene = new Container()
  playScene.name = 'play'
  const mapTexture = app.loader.resources['background'].texture
  const rectangle = new Rectangle(0, 1080, 1550, 900)
  mapTexture.frame = rectangle
  const map = new Sprite(mapTexture)
  map.name = 'map'
  const mapScareX = WIDTH / map.width
  const mapScareY = HEIGHT / map.height
  map.scale.set(mapScareX, mapScareY)
  playScene.addChild(map)
  1. написать класс волшебницы
// player.js
export class Player extends AnimatedSprite{

  constructor(frames, options) {
    super(frames)
    this.options = {}
    Object.assign(this.options, options, PLAYER_OPTIONS)
    this.radian = 0
    this.anchor.set(0.5, 0.5)
    this.targetX = this.options.x
    this.targetY = this.options.y
    this.position.set(this.options.x, this.options.y)
    this.animationSpeed = this.options.animationSpeed
    this.scale.set(this.options.scale, this.options.scale)
  }

  goto(x , y) {
    this.targetX = x
    this.targetY = y
    this.radian = Math.atan2((y - this.y), (x - this.x))
    this.rotation = this.radian
  }

  walk() {
    if(this.targetX === this.x && this.targetY === this.y) return
    const dx = this.x - this.targetX
    const dy = this.y - this.targetY
    const distance = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2))
    if(distance < this.options.speed) { // 距离小于一帧直接赋值
        this.x = this.targetX
        this.y = this.targetY
    } else {
      this.x = this.x + this.options.speed * Math.cos(this.radian)
      this.y = this.y + this.options.speed * Math.sin(this.radian)
    }
  }
  
  reset() {
    this.radian = 0
    this.x = this.options.x
    this.y = this.options.y
    this.targetX = this.options.x
    this.targetY = this.options.y
  }
}

Объясните функции goto и walk

  1. Когда мышь щелкает, чтобы нарисовать изображение, мы можем получить положение мыши Math.atan2(dx, dy) может получить отрезок, образованный между двумя точками, и угол между осью x и радианом.
  2. Скорость движения устройства разделена на компонент X, y-y-оси VX = скорость * COS (θ), vy = Speed ​​* Sin (θ)

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

export class Monster extends Graphics {
  constructor(options) {
    super()
    this.beginFill(0x9966FF)
    this.drawCircle(0,0, options.size || 32);
    this.endFill();
    this.x = options.x
    this.y = options.y
  }

  goto(x , y) {
    this.targetX = x
    this.targetY = y
    this.radian = Math.atan2((y - this.y), (x - this.x))
    this.rotation = this.radian
  }

  walk() {
    this.x = this.x + 3 * Math.cos(this.radian)
    this.y = this.y + 3 * Math.sin(this.radian)
  }
}
  1. Генерация случайных единиц столкновения
export function monsterFactory(scene, target ,number, time = 300) {
  const boundary = [
    [0, WIDTH, 0, 0],
    [0, 0, 0, HEIGHT],
    [WIDTH, WIDTH, 0, HEIGHT],
    [0, WIDTH, HEIGHT, HEIGHT]
  ]
  const timer = setInterval(() => {
    for(let i = 0; i < number; i++) {
      const randomRange = boundary[getRadom(number, 0)]
      const x = getRadom(randomRange[1], randomRange[0])
      const y = getRadom(randomRange[3], randomRange[2])
      const monster = new Monster({
        x,
        y
      })
      monster.goto(target.x, target.y)
      scene.addChild(monster)
    }
  }, time);
  return timer
}

Функция monsterFactory используется для генерации случайных единиц столкновения. Определяется граничный массив длины 4, единицы случайным образом генерируются из четырех границ, а время может использоваться для управления частотой генерации единиц и количеством единиц за раз.Здесь попробуйте увеличить входные параметры.

monsterFactory(playScene, yaoji, 5, 800) //每隔0.8秒产生5个

Невооруженным глазом можно, и сразу выскочило много юнитов столкновения.

конечная сцена

  overScene = new Container()
  overScene.name = 'over'
  const playButton = getButton('重新开始')
  playButton.position.set(24, 320)
  playButton.on('click', () => {
    changeScene('start')
  })
  overScene.addChild(playButton)
  const overText = new Text('加油,再来一次,你是下一个Faker')
  overText.x = 300
  overText.y = HEIGHT / 2
  overText.align = 'center'
  overText.style = { fill: "white", fontSize: 32 }
  overScene.visible = false
  overScene.addChild(overText)
  score = 0
  scoreText = new Text(`${score}`)
  scoreText.style = { fill: "red", fontSize: 32 }
  scoreText.position.set(WIDTH / 2, HEIGHT / 3)

Конечная сцена проще, чем предыдущий, и код использует предыдущую кнопку.

Обнаружение столкновений и оценка

  app.ticker.add(() => {
    if (currentScene === 'play') {
      for(let i = 0; i < playScene.children.length; i++) {
        let c = playScene.children[i]
        if (c.name !== 'yaoji' && c.name !== 'map') {
          if(hit(yaoji, c)) { // 碰撞检测
            changeScene('over')
            return
          }
        }
      }
      scoreText.text  = `${++score}`  // 更新得分
      yaoji.walk()
      playScene.children.forEach(c => {
        if (c.name !== 'yaoji' && c.name !== 'map') {
          c.walk()
        }
      })
    }
  })
  
function hit(obj1, obj2) {
  let isHit = false
  const dx = obj1.x - obj2.x
  const dy = obj1.y - obj2.y
  const combinedHalfWidths = (obj1.width + obj2.width) / 2
  const combinedHalfHeights = (obj1.height + obj2.height) / 2
  if (Math.abs(dx) < combinedHalfWidths) {
    if (Math.abs(dy) < combinedHalfHeights) {
      isHit = true;
    }
  }
  return isHit
}

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

Переход на следующий уровень — принцип работы бегущей строки

Сложность статьи сильно возросла по сравнению с прошлым разом, если вы это видели, давайте углубимся в пикси.Приведенный выше код является конструктором app.ticker.При создании приложения начинают тикать внутренние часы.Видно, что тик такой же, как анимация, которую мы обычно пишем с помощью requestAnimationFrame.Интересно, что цепочка функций обратного вызова поддерживается внутри.Многие люди говорят, что интервью построить ракету, структура данных не используется, но используется исходный код. почему не массивы? Интерфейс нужно только добавлять и удалять насильно, и нет необходимости обращаться к таблице ниже, цепочка лучше, и сложность добавления и удаления ниже.

может ты попробуешь

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

  1. Стартовая сцена действительна только с кнопкой запуска, которая может расширить функцию
  2. Частицы столкновения уродливы и могут быть заменены картинками, которые вам нравятся
  3. Обнаружение столкновений можно оптимизировать в соответствии с формой изображения, на которое вы меняете изображение.
  4. Я не делал никаких жестов мышью и не нашел понравившейся картинки -- --
  5. Увеличьте скорость загрузки изображений, я загрузил только 3 изображения, и я не чувствую особой необходимости (ленивый)

Кодовый адрес этой проблемы

наконец

Оба на основе холста 2d базового введения в игру закончены, пикси + твин + математика средней школы, формулы физики, для общего рынка удалось справиться с последствиями страницы события, и если Лига 2d все еще чувствует маловато, а так с той 3д частью прощай, каньон тоже призываешь.