I'm participating in the Mid Autumn Festival Creative submission competition , Details please see : Mid Autumn Festival Creative submission contest
The moon sinks into the blue sea and looks at the heavy building , Who put the light on to provoke sleepwalking .
——《 eight-line poem with seven characters to a line and a strict tonal pattern and rhyme scheme · Kongming latern 》
In this issue, we begin to use pixi.js Lightweight h5 The game engine , To develop a complete scene level animation of the mid autumn night sky . This is a practical case of temporary creation , Hope you enjoy it , There is a bright starry sky , There is a charming full moon , The fragrance of sweet scented osmanthus , There are also Kongming lamps that repose our good wishes ..
That's what we've done , Let's prepare four pictures first , Then the infrastructure will be built first .
<body>
<div id="app"></div>
<script type="module" src="./app.js"></script>
</body>
Copy code
* {
padding: 0;
margin: 0;
}
html,
body {
width: 100%;
height: 100vh;
position: relative;
overflow: hidden;
background-color: #999;
}
#app {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
Copy code
Because we cooperate this time vite Do the project construction, so use module Pattern , It is convenient to introduce the module in the back .
import * as PIXI from "pixi.js";
const imgs = {
"sweet": "assets/sweet.png",
"lamp": "assets/lamp.png",
"moon": "assets/moon.png",
"petal": "assets/petal.png"
}
class Application {
constructor() {
this.app = null;
this.stage = null;
this.textures = null;
this.w = 800;
this.h = 600;
this.dt = 0;
this.init();
}
init() {
// initialization
let el = document.getElementById("app")
this.app = new PIXI.Application({
resizeTo: el
});
this.w = this.app.view.width;
this.h = this.app.view.height;
this.stage = this.app.stage;
this.stage.sortableChildren = true;
el.appendChild(this.app.view);
this.loadTexture(imgs).then(data => {
this.textures = data;
window.addEventListener("resize", this.reset.bind(this))
this.render();
})
this.app.ticker.add(this.step.bind(this));
}
reset() {
// Reset
this.w = this.app.view.width;
this.h = this.app.view.height;
this.stage.children.length = 0;
this.render()
}
loadTexture(imgs) {// Load picture texture }
render() {
this._renderSky();
this._renderMoon();
this._renderLamp();
this._renderSweet();
this._renderPetal();
}
_renderLamp() {// Render Kongming lamp }
_renderMoon() {// Render the full moon }
_renderPetal() {// Render petals }
_renderSweet() {// Render osmanthus branches }
_renderSky() {// Render the starry sky }
step() {
// frame
this.dt++;
}
}
window.onload = new Application();
Copy code
What we do here is a full screen animation , therefore PIXI Directly adjust the width and height of all areas of the parent container for subsequent elements . also , What we expect is to load the texture, store the texture set, and then start rendering the elements . Next, write an image and load the generated texture loadTexture Function .
loadTexture(imgs) {
const textures = {};
return new Promise((reslove, reject) => {
let loader = this.app.loader;
for (const key in imgs) {
loader.add(key, imgs[key])
}
loader.load((info, resources) => {
for (const key in resources) {
let texture = PIXI.Texture.from(resources[key].data);
textures[key] = texture;
}
reslove(textures)
});
})
}
Copy code
We used pixi.js A self-contained loader , Load these pictures , After loading, we will turn it into texture one by one and store it in the object to facilitate its generation of sprites .
// full moon
_renderMoon() {
const moon = PIXI.Sprite.from(this.textures["moon"]);
moon.scale.set(this.h / moon.height * 0.7, this.h / moon.height * 0.7);
moon.position.set(this.w - moon.width * 0.7, -moon.height * 0.3)
this.stage.addChild(moon);
}
Copy code
// Starry sky
_renderSky() {
const sky = new PIXI.Container();
let color = this.createGradTexture();
for (let i = 0; i < this.w / 20; i++) {
const star = new PIXI.Sprite(color);
let scale = Math.random();
star.scale.set(scale, scale);
star.position.set(this.w * Math.random(), this.h * 0.7 * Math.random());
sky.addChild(star)
}
this.stage.addChild(sky)
}
createGradTexture() {
let canvas = document.createElement('canvas');
canvas.width = 8;
canvas.height = 8;
let context = canvas.getContext('2d');
let gradient = context.createRadialGradient(canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, canvas.width / 2);
gradient.addColorStop(0, 'rgba(255,255,255,1)');
gradient.addColorStop(0.3, 'rgba(255,255,255,1)');
gradient.addColorStop(0.45, 'rgba(0,0,128,1)');
gradient.addColorStop(1, 'rgba(0,0,0,1)');
context.fillStyle = gradient;
context.fillRect(0, 0, canvas.width, canvas.height);
return PIXI.Texture.from(canvas);
}
Copy code
The drawing of the moon will not be repeated , Is simply to generate the placement of the wizard .
Starry sky, we need to make a container first Container To collect stars , Then add this container to the scene . The number of stars is positively related to the screen size ,for Generate sprites one by one , But where did our elf texture come from , We expect the stars to be white hearts , There's a little blue light around , here , We can introduce another image to generate texture , perhaps , Just like me , stay createGradTexture Function use canvas api Draw a... With a circular gradient 8*8 Then generate a texture .
as for , The positions of the stars are randomly generated on the screen , Here I want only random to high 70% Quan man's words are a little contrary .
_renderSweet(num = 10) {
const sweet = this.textures["sweet"];
const points = []
const ropeLength = sweet.width / num;
for (let i = 0; i < num; i++) {
points.push(new PIXI.Point(i * ropeLength, 0));
}
const strip = new PIXI.SimpleRope(sweet, points);
const stripContainer = new PIXI.Container();
stripContainer.addChild(strip);
stripContainer.position.set(10, -20);
stripContainer.scale.set(.5, .5);
stripContainer.rotation = Math.PI / 180 * 40;
stripContainer.zIndex = 10;
this.stage.addChild(stripContainer);
this.app.ticker.add(() => {
for (let i = 0; i < points.length; i++) {
points[i].y = Math.sin((i * 0.5) + this.dt * 0.1) * 2;
points[i].x = i * ropeLength + Math.cos((i * 0.3) + this.dt * 0.1) * 1;
}
})
}
Copy code
We don't do frame animation or bone animation here , Just use a static diagram to complete , Mainly used pixi.js Of SimpleRope, Turn the sweet scented osmanthus branch into a rope , In fact, I made some slices , Then change the position of each point when rendering updates , The effect of breeze floating can be realized .
_renderPetal(num = 20) {
let petals = [...Array(num).values()];
petals = [].map.call(petals, petal => {
petal = PIXI.Sprite.from(this.textures["petal"]);
let scale = .1 + .2 * Math.random();
petal.scale.set(scale, scale);
petal.rotation = Math.PI / 180 * 360 * Math.random()
petal.position.set(this.w * Math.random(), this.h * Math.random());
petal.vy = Math.random() * 0.2 + 0.5;
petal.vx = Math.random() * 0.5 + 0.5;
petal.zIndex = 15 * Math.random();
this.stage.addChild(petal);
return petal;
})
this.app.ticker.add(() => {
petals.forEach(petal => {
petal.rotation += (0.025 * Math.random())
petal.alpha -= .0006;
petal.y += petal.vy;
petal.x += petal.vx;
if (petal.x > this.w + petal.width || petal.y > this.h + petal.height) {
petal.x = -this.w / 4 * Math.random()
petal.y = this.h * Math.random() - this.h / 2
petal.zIndex = 15 * Math.random();
petal.alpha = 1;
}
})
})
}
Copy code
Here our husband becomes 20 A petal wizard controls the displacement direction, size and other parameters , These parameters will be changed during rendering updates to achieve motion . however , When we update, we should pay attention to , Make boundary judgment , Once beyond the boundary , Let him reassign , Continuous reuse .
_renderLamp(num = 12) {
let lamps = [...Array(num).values()];
lamps = [].map.call(lamps, lamp => {
lamp = PIXI.Sprite.from(this.textures["lamp"]);
let scale = .15 + .35 * Math.random();
lamp.scale.set(scale, scale);
lamp.position.set(this.w * Math.random(), this.h * Math.random());
lamp.vy = Math.random() * 0.36 + 0.2;
lamp.vx = 0.12 * Math.random();
lamp.zIndex = scale;
lamp.interactive = true;
lamp.on('pointerdown', this._pointerDown.bind(this, lamp));
lamp.on('pointerover', this._pointerOver.bind(this, lamp));
lamp.on('pointerout', this._pointerOut.bind(this, lamp));
const blurFilter = new PIXI.filters.BlurFilter();
lamp.filters = [blurFilter];
blurFilter.blur = (0.5 - scale) * 10;
this.stage.addChild(lamp);
return lamp;
});
this.app.ticker.add(() => {
lamps.forEach(lamp => {
lamp.y -= lamp.vy;
lamp.x += Math.sin(this.dt * lamp.vx) * 0.2;
if (lamp.y < -lamp.height) {
lamp.y = this.h;
lamp.w = this.w * Math.random();
lamp.vy = Math.random() * 0.36 + 0.2;
lamp.vx = 0.12 * Math.random();
}
})
})
}
_pointerDown(lamp) {
lamp.vy += .8;
}
_pointerOver(lamp) {
lamp.filters[0].blur = 0;
}
_pointerOut(lamp) {
lamp.filters[0].blur = (0.5 - lamp.scale.x) * 10;
}
Copy code
This is similar to the implementation of petal falling , But the direction is always upward , Because the Kongming lamp can't keep still after flying, so use trigonometric function sin To simulate his lateral periodic change . It's worth mentioning that there are many other , We randomly generate its size , Reuse zIndex Set priority according to size , It must be big in front , The small one is behind , And then use pixi.js The built-in filter does fuzzy processing , In this way, the Kongming lamp in the distance will become blurred , More real .
Next , Introduce pixi.js Three events :
pointerdown: An event is triggered when the pointer device button is pressed on the display object .
pointerover: An event is triggered when the pointing device moves onto the display object .
pointerout: An event is triggered when the pointing device moves out of the display object .
To receive the event, we need to send Kong Mingdeng's interactive Property on .
After clicking, the Kongming light will accelerate , Let's just change vy value , Moving in and out of blur dissipation is also simple .
Although there is some workload , But the difficulty is not so high , It's actually over here , The online demo
We just used pixi.js The fur can build such a picture , I don't know what association you have after reading . Is to make a scene animation editor ? Or do you want to play rope games ? Or particle effects ? The essential purpose of our study is to explore , Criticize , Innovative ideas , So explore the world !
It's not easy to create , Your support is the driving force of my creation , Please comment on the collection with your praise , Three times with one click ~