предисловие
Первый обзор предыдущей статьиУчим холст по смайликам, Предварительное понимание основного использования холста и общего метода написания класса 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. Я видел, как пользователь сети играл в небольшую игру с позиционированием Ясуо раньше. Я попробовал это по прихоти, но не смог найти никаких изображений для Ясуо. Мы заменили его уродливой волшебницей.
код инициализации
- Написать элемент конфигурации
// 新建一个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
}
- построить приложение
// 创建一个返回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
}
- написать основной код
// 新建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 кнопок, фонового изображения и анимации линзы.
- Кнопка "Создать"
// 新建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
}
- Добавьте фоновое изображение и добавьте кнопку
// 添加按钮
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. Стартовая сцена готова.
игровая сцена
- фоновый макет
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)
- написать класс волшебницы
// 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
- Когда мышь щелкает, чтобы нарисовать изображение, мы можем получить положение мыши Math.atan2(dx, dy) может получить отрезок, образованный между двумя точками, и угол между осью x и радианом.
- Скорость движения устройства разделена на компонент 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)
}
}
- Генерация случайных единиц столкновения
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.Интересно, что цепочка функций обратного вызова поддерживается внутри.Многие люди говорят, что интервью построить ракету, структура данных не используется, но используется исходный код. почему не массивы? Интерфейс нужно только добавлять и удалять насильно, и нет необходимости обращаться к таблице ниже, цепочка лучше, и сложность добавления и удаления ниже.
может ты попробуешь
Введение кода на этом закончено, проблем по делу еще много, если вам интересно, вы можете оптимизировать его самостоятельно.
- Стартовая сцена действительна только с кнопкой запуска, которая может расширить функцию
- Частицы столкновения уродливы и могут быть заменены картинками, которые вам нравятся
- Обнаружение столкновений можно оптимизировать в соответствии с формой изображения, на которое вы меняете изображение.
- Я не делал никаких жестов мышью и не нашел понравившейся картинки -- --
- Увеличьте скорость загрузки изображений, я загрузил только 3 изображения, и я не чувствую особой необходимости (ленивый)
наконец
Оба на основе холста 2d базового введения в игру закончены, пикси + твин + математика средней школы, формулы физики, для общего рынка удалось справиться с последствиями страницы события, и если Лига 2d все еще чувствует маловато, а так с той 3д частью прощай, каньон тоже призываешь.