Codesandbox. IO/s/tap – bird -… Visit the page to see the effect
Each step is coded separately and can be read against this code to make it easier to understand
In the early identification of PIXI
There are many articles on the web, so I won’t expand them here. If you don’t know about game engines, please read the following
H5 Scene small animation implementation PixiJs actual combat – Zhihu (zhihu.com)
API Manual PixiJS API Documentation
start
Material preparation
Our images are stored in public/assets, and the compiled relative program file is read in a path like./assets/bird.png
Step 1 Set the stage
All of the game’s visual actions are inside the canvas, and for the sake of coordinate conversion, let’s assume that our screen width is 750 and height 1334, meaning that a 750 x 1334 image can be spread across the entire game. So our canvas is going to look like
<canvas width="750" height="1334" />
Copy the code
In the real world, however, not all devices are 750 x 1334, so we might have to scale, such as x0.5 if the screen width is 375. We use the width and height properties of style for control
<canvas width="750" height="1334" style="width: 375px; height: 667px" />
<! -- width, height denotes the pixel size inside the canvas, corresponding to the corresponding pixel in the memory area -->
<! > < span style = "font-size: 14px! Important; color: RGB (51, 51, 51);
Copy the code
With the initialization method of pixi, so
// this.$refs.canvas is the corresponding canvas node
application = new PIXI.application({
width: 750.height: 1334.view: this.$refs.canvas,
backgroundColor: 0x0099ff.// Make it blue for easy observation
});
this.$refs.canvas.style.width = this.width + "px";
this.$refs.canvas.style.height = this.height + "px";
Copy the code
Step 2 Experiment by adding a background
Once the stage is created, we can try to add some view elements to the stage. PIXI was developed in an object-oriented manner, using different classes for different visual elements, such as
The name of the class | describe |
---|---|
Sprite | The elves class. General image rendering and so on |
Text | The text class. Mainly used for rendering text |
AnimatedSprite | Compared to sprites, more animation frame control, generally used to do frame by frame animation elements, such as walking people, flying birds |
Container | Container, mainly used for container wrapping |
For example, Container and Sprite inherit from DisplayObject, while AnimatedSprite inherit from Sprite. Therefore, many property methods and concepts are the same
In the game engine world, we use images (collections of pixels) as materials, and when we initialize a Sprite class, we need to load our background image, generate a material object, and use it for initialization
// Initialize the material and pull it from the static file
const bgTexture = Texture.from("./assets/bg.png");
// Create a Sprite object
const bg = new PIXI.Sprite(bgTexture);
// Configure dimensions, coordinates, etc
bg.width = 750;
bg.height = 1334;
bg.x = 0;
bg.y = 0;
// The stage we operate on is the stage in the app
application.stage.addChild(bg);
Copy the code
The generation of materials, in addition to images, can also
- By pulling from an existing material, you can cut an existing material
- You can use the drawing tool class directly to draw simple graphics, such as a circle or a triangle
- It can also be taken directly from the display object. For example, we can take an image of the entire game interface, generate a new material, and then store it as a picture and make it into a screenshot
For this code, you can look at tap-bird-CodesandBox
Step 3 Add flooring
Next we add a floor. We created a container and used two images of the floor end to end to make it a longer image for easy animation control
const ground = new Container();
const texture = Texture.from("./assets/ground.png");
// Add a floor
// Create two floors to form a single base plate for loop scrolling
const ground1 = new Sprite(texture);
ground1.x = 0;
const ground2 = new Sprite(texture);
ground2.x = ground1.width;
ground.height = ground1.height;
ground.width = 2 * ground1.width;
ground.addChild(ground1);
ground.addChild(ground2);
ground.y = 1334 - ground.height;
app.stage.addChild(ground);
Copy the code
Note that the floor may not appear, because the resource load is not timely, causing some exceptions in width and height access, resulting in the size and position of the element, you can switch the refresh effect several times, we will solve these problems in the fourth step
For this code, you can look at tap-bird-CodesandBox
Step 4 optimize code structure and lazy loading
So far, we’ve found some problems
- The resource loading delay may cause the interface to display abnormally
- In the future, we need to add a lot of class creation and logic code, which is not easy to maintain in one process
In view of the above problems, we make the following adjustments
The customApplication
Class instead ofPIXI.Application
This part of the work is to move the initialization logic into and out of the entry code
// In the vue file, introduce a custom class
import Application from './Application';
// Mounted
{
mounted() {
this.app = new Application({
width: 750.//this.width,
height: 1334.//this.height,
view: this.$refs.canvas,
backgroundColor: 0xffffff,})this.$refs.canvas.style.width = this.width + "px";
this.$refs.canvas.style.height = this.height + "px"; }},Copy the code
Write the Application class
import {
Application
} from "pixi.js";
// Inherited from pixi.Application, unpopulated with initialization logic
export default class game extends Application {
constructor(props) {
super(props);
// todo initializes logic}}Copy the code
useLoader
Resources are preloaded
Before the initialization stage, we use the Loader of PIXI to preload resources. After the loading is completed, we will create elements and other work, so the Application class needs to be changed
import {
Application,
Loader,
Sprite
} from "pixi.js";
// Inherited from pixi.Application, unpopulated with initialization logic
export default class game extends Application {
constructor(props) {
super(props);
// Resource loading allows cross-domain, avoiding image error
Loader.shared.add({ url: "./assets/bg.png".crossOrigin: true });
Loader.shared.add({ url: "./assets/ground.png".crossOrigin: true });
Loader.shared.add({ url: "./assets/bird.png".crossOrigin: true });
Loader.shared.add({ url: "./assets/holdback.png".crossOrigin: true });
Loader.shared.add({ url: "./assets/number_2.png".crossOrigin: true });
// Trigger init after loading. Use bind to prevent this pointer from changing
Loader.shared.onComplete.once(this.init.bind(this));
// Start loading
Loader.shared.load();
}
init(){
// Initialize the logic
// Add background
// The way the material is referenced has changed
const bg = new Sprite(Loader.shared.resources["./assets/bg.png"].texture);
bg.width = 750;
bg.height = 1334;
bg.zIndex = 0;
this.stage.addChild(bg); }}Copy the code
After this adjustment, the way materials are referenced has changed
/ / the original
const bg = new Sprite(Texture.from("./assets/bg.png"));
/ / new
const bg = new Sprite(Loader.shared.resources["./assets/bg.png"].texture);
Copy the code
Move the floor
Following the previous thought, we’ll wrap Ground as a separate class and add a new method to it, onUpdate, to update the floor roll position
import { Container, Sprite, Loader } from "pixi.js";
export default class Ground extends Container {
constructor(props) {
super(props);
// See the previous code
}
onUpdate() {
// Each scroll reduces the entire box to the left (negative) by 10 px, reset to 0 for more than one screen
this.x = (this.x - 10) % 750; }}Copy the code
We then call it in the Application with the tick to refresh
export default class game extends Application {
init() {
// Add new floor and position it
this.ground = new Ground();
this.ground.y = 1334 - this.ground.height;
this.ground.x = 0;
this.stage.addChild(this.ground);
// tick
this.onUpdate = this.onUpdate.bind(this);
this.ticker.add(this.onUpdate);
}
onUpdate() {
// Refresh the floor
this.ground.onUpdate(); }}Copy the code
Step 6 Add obstacles
An obstacle with an upper and lower tube
And our material file is
Remember the source of the material, we can split an image to produce two materials left and right. Combined with the previous use of Container, we pack the top and bottom tubes in one Container
import { Container, Sprite, Texture, Loader } from "pixi.js";
export default class Holdback extends Container {
constructor(offset) {
super(a);const HOLDBACK_TEXTURE =
Loader.shared.resources["./assets/holdback.png"].texture;
// Material clipping generates two pipe objects
this.top = new Sprite(
new Texture(HOLDBACK_TEXTURE, {
x: HOLDBACK_TEXTURE.width / 2.y: 0.width: HOLDBACK_TEXTURE.width / 2.height: HOLDBACK_TEXTURE.height
})
);
this.bottom = new Sprite(
new Texture(HOLDBACK_TEXTURE, {
x: 0.y: 0.width: HOLDBACK_TEXTURE.width / 2.height: HOLDBACK_TEXTURE.height
})
);
// Use the input parameter to locate the two pipes
// Anchor. Set is the center of the set object, with 1 meaning 100%
this.top.anchor.set(0.1);
this.top.y = offset;
this.bottom.y = this.top.y + 300;
this.addChild(this.top);
this.addChild(this.bottom); }}Copy the code
Then we added dynamic create and destroy logic to the main logic code
onUpdate(){
// This. Holdbacks is used to hold an array of obstacles that have been created
let temp = [];
for (let i = 0; i < this.holdbacks.length; i++) {
const holdback = this.holdbacks[i];
// Refresh the position
holdback.x = holdback.x - 10;
// Determine if it is completely out of the screen
if (holdback.x <= -holdback.width) {
// Delete from the stage
this.stage.removeChild(holdback);
} else {
// Put it in the cachetemp.push(holdback); }}// Save the undeleted items again and continue to judge in the next cycle
this.holdbacks = temp;
// Create obstacles
this.dist -= 10;
if (this.dist % 300= = =0) {
GetNextOffset generates a number that controls the location of the notch
const holdback = new Holdback(this.getNextOffset());
holdback.x = 750;
holdback.zIndex = 2;
// Insert into the middle of the stage
this.stage.addChild(holdback);
this.holdbacks.push(holdback);
this.dist = 0; }}Copy the code
The getNextOffset method is generated using sin based on time
getNextOffset() {
return (
Math.sin((((Date.now() - this.startTime) % 3000) / 3000) * 2 * Math.PI) *
100 +
350
);
}
Copy the code
Step 7 Draw layers
Now you can see that the floor is blocked because in Pixi, the render level is related to the insertion order, so we need to add a render level to them and sort them
We give the element zIndex when it is created and then sort it in the update phase
this.ground = new Ground();
this.zIndex = 1;
const holdback = new Holdback(this.getNextOffset());
holdback.zIndex = 0;
/ / sorting
this.stage.children.sort((a, b) = > a.zIndex - b.zIndex);
Copy the code
PIXI does not support this function, and the zIndex property is not used for this purpose
Add birds
- use
AnimatedSprite
Class to create birds - At any time the bird has a velocity vy, which starts at 0, which should be 0 every time it updates its position
this.y += vy
- The speed of the bird is not constant, it is affected by gravity, so we have to update the speed every time we refresh the position
this.vy += a
, a is constant and can be adjusted as needed - When you tap the screen again, the bird should get an upward speed,
this.y = -8
(the top is negative), because of the presence of the acceleration, the velocity will soon become downward, so the bird will eventually move downward
Step 9 Collision detection
- use
getBounds
Of the elementx
.y
.width
.height
, how to make cross judgment between the two elements, that is, whether the four points in the rectangular area of A are in the rectangular area of B - We will see that the bird’s visual range contains some transparent parts, and using this rectangle directly will be a little inaccurate. We can rewrite the bird class
getBounds
Method (remember to call the parent method first) and adjust the range to 1/2
getBounds(. args) {
const rect = AnimatedSprite.prototype.getBounds.call(this. args); rect.x = rect.x + rect.width /4;
rect.y = rect.y + rect.height / 4;
rect.width *= 0.5;
rect.height *= 0.5;
return rect;
}
Copy the code
Step 10 Scoreboard
The scoreboard is wrapped in Container, converts the score to a string, and then iterates through the sprites for element creation
Write in the last
If you have the need to private letter me, welcome to give advice, thank you for reading