“This article has participated in the good article call order activity, click to see: back end, big front end double track submission, 20,000 yuan prize pool for you to challenge!”

preface

In game development, we often use a lot of “collision detection”, more complex scenarios we may use some collision detection library. Such as MatterJS, P2 and other physics engines. There are many methods of collision detection, specific scene specific analysis, the more useful is SAT theorem, ray detection, etc. Next, we will start with a simple game called “throw pot” and introduce how “boundary detection” and “ray projection detection” are implemented.

What is a game of pot throwing?

It is a kind of throwing game in which arrows are thrown into the pot, which is both a kind of etiquette and a game. “Throwing the pot can heal the mind, cultivate one’s morality, serve the country and observe people”

Rules of the game:

Long press the mouse, adjust the Angle, the arrow accurately into the pot

Let’s take a look at how the game works:

Next, let’s formally introduce how to develop this little game.

First, environment construction

PixiJs is used as the game engine. First, let’s set up a simple environment to build a TS development environment using WebPack.

const path = require('path');
module.exports = {
    entry: './src/main.ts'.module: {
        rules: [{test: /\.tsx? $/,
                use: 'ts-loader'.exclude: /node_modules/}},resolve: {
        extensions: ['.tsx'.'.ts'.'.js']},output: {
        filename: 'output.js'.path: __dirname,
        libraryTarget: 'umd',}};Copy the code

Next we introduce the PIxiJS engine:

//pixi
yarn add --save pixi.js
/ / install typings
yarn add -g typings
// Generate code hint.d.ts
typings install --global --save-dev dt~pixi.js
Copy the code

A simple development environment is set up. Let’s write the entry file main.ts.

import { IndexScene } from "./core/IndexScene";
export class Main {
    / * * * / home page
    private indexScene: IndexScene;
    constructor(canvas: HTMLCanvasElement) {
        const width = document.body.clientWidth * (window.devicePixelRatio || 1);
        const height = document.body.clientHeight * (window.devicePixelRatio || 1);
        const app = new PIXI.Application(width, height, {
            view: canvas,
            backgroundColor: 0x1099bb
        });
        this.indexScene = app.stage.addChild(newIndexScene(width, height)); }}Copy the code

IndexScene is our main scene, “What you write is what you see.” We won’t go much into setting up the environment here, so let’s get started on today’s main content.

Second, game implementation

1. Game interface

First of all, build the game interface, an arrow lost, a pitcher, function panel (speed, Angle, storage force display).

/ / bucket bottom
this.barrel = this.addChild(Sprite.from(".. /.. /assets/barrel.png")); ./ / the arrow
this.arrow = this.addChild(newArrow()); ./ / barrel surface
this.barrel = this.addChild(Sprite.from(".. /.. /assets/barrelMask.png")); ./ / text
let style = {
    fontFamily: "Arial".fontSize: 66.fill: "white".stroke: '#f64066'.strokeThickness: 4};// Speed display text
const speedTxt = this.addChild(new Text("Speed: 0", style)); .// Initial Angle display
const rotationTxt = this.addChild(new Text("Initial Angle: 0", style)); .Copy the code

This leads to the following game scene, which is so simple to build that I won’t go into detail.

Let the arrow lose movement

We thought about how to develop this game:

  • The arrow follows the mouse
  • The arrows were fired with varying degrees of intensity
  • The arrow moves in a parabola
  • The arrows went into the pot
  • The arrow fell out of the field

First, we need to make the arrow follow our mouse, listen for the mouse event using the piXI event, and calculate the arrow Angle. Calculate the Angle (radian) between the line segment from the arrow position to the mouse position and the positive X-axis with math.atan2 (dy.dx). So our arrows can move with the mouse.

this.on(MOUSE_EVENT.onMouseMove, this.onMouseMove, this);
// Move the mouse
private onMouseMove(e){
    const dx = e.data.global.x - this._width * 0.15;
    const dy = 800 - e.data.global.y;
    this.arrowRotation = -GUtils.getAngle(dy, dx);
    this.arrow.rotation = this.arrowRotation * Math.PI / 180; }...// GUtils class Angle calculation
static getAngle(dy: number, dx: number): number {
    const angle = Math.atan2(dy, dx) / Math.PI * 180;
    return angle;
}
Copy the code

Next, we need to give the arrow a catapult. We have calculated the Angle of the arrow above, and given a force, we can get the components of the force in the x and y directions, where the force is the change in displacement at each distance. This. Force += 0.3 * rate

Xu li / * * * /
private force: number = 0;
/** the most powerful */
private maxForce: number = 15;
Xu li / * * * /
private addForce(rate: number = 1) {
    if (!this.isUp) return;
    this.force += 0.3 * rate;
    if (this.force > this.maxForce) {
        this.force = this.maxForce;
    }
    this.forceProgressMask.y = 594 - 394 * this.force / this.maxForce;
}
Copy the code

Finally, how do we eject the arrow?

We need to set up a game loop with 60 frames per second and 16.7ms per frame to draw. Here we use pixiJS’s Ticker control.

app.ticker.add(() = > {
    this.indexScene.update(app.ticker.FPS);
});
Copy the code

Execute the game loop.

update(fps) {
    if (this.paused) return;
    let deltaTime: number = 0;
    const now = +new Date(a);if (this.lastTime) {
        deltaTime = now - this.lastTime;
    } else {
        deltaTime = 1000 / fps;
    }
    let rate: number = deltaTime / (1000 / fps);
    this.addForce(rate);
    this.updateArrowSpeed(rate);
    this.updateArrowPosition(rate);
    this.lastTime = now;
}
Copy the code

During each frame update, we need to update the arrow’s force, speed, position and rotation in real time.

So how do you calculate that?

Every frame we increment the arrow, and the vertical direction is affected by gravity. Define a force of gravity in the vertical direction.

/ * * * / gravity
const G: number = 0.2;
Copy the code

Next, calculate the real time position of the arrow.

/ / displacement
this.arrow.x += horizontalVelocity * rate;
this.arrow.y -= verticalVelocity* rate;
/ / by gravity
this.arrow.verticalVelocity -= G * rate;
Copy the code

Here is the full method. Rate is the coefficient to do frame rate synchronization. Can avoid frame loss resulting in lag.

/** Update arrow position */
private updateArrowPosition(rate: number = 1) {
    if (this.isArrowMove) {
        const { horizontalVelocity, verticalVelocity } = this.arrow;
        this.arrowRotation = -GUtils.getAngle(verticalVelocity * rate, horizontalVelocity * rate);
        this.arrow.x += horizontalVelocity * rate;
        this.arrow.y -= verticalVelocity * rate;
        this.arrow.rotation = this.arrowRotation * Math.PI / 180; }}/** Update arrow force */
private updateArrowSpeed(rate: number = 1) {
    if (this.isArrowMove) {
        this.arrow.verticalVelocity -= G * rate; }}Copy the code

After the above steps, our arrow is ready to be ejected, and it is moving smoothly in a parabola. Finally, we need to determine whether the arrow is in the pot and whether the arrow is in the throwable zone.

3. Ray projection detection

1. Basic Mathematics

Before we get into collision detection, let’s do a quick math review.

How do you determine a line?

“A line in a plane is a figure represented by a two-factor equation in a rectangular plane coordinate system.”

So we just use the slope-intercept form of the line expression.

//k is the slope and b is the y-intercept
y = kx + b
Copy the code

And we also know that k is 0 when the line is horizontal relative to the X-axis; When the line is perpendicular to the X-axis, k is infinity.

How do you tell if two lines intersect?

We now have two lines that intersect at one point (x, y);

  • The line A y = K ₁x + b₁
  • Line B, y is equal to K ₂x plus B ₂

By reasoning we can get:

₁x + b₁ = k₂x + b₂

Exchange yields:

X = (B ₂ – B ₁)/(K ₁ – K ₂)

So that’s all the math you need.

2. Practical application

So what do we do with this formula?

When the arrow is moving, we can get the velocity component to determine a straight line, and the spout of the pot can be viewed as a straight line parallel to the X-axis. When the two lines intersect, can we think that the arrow hit the target? Of course, the line is infinitely long. When judging, we can also detect whether the intersection point is within the range of the throwing pot by the “boundary detection” method. In this way, our collision detection will be more accurate.

We can look at the diagram:

When the intersection (x, y) is in the middle of x1 and X2, and the intersection (x, y) is within the bucket’s target range, the arrow can be dropped.

How do I write that in code?

Here, we will add two offsetW and offsetH, because the barrel image has edge offset, so we need to deal with the offset slightly to make the detection more accurate.

/* Check whether the bucket is entered */
public static isArrowEnterArea(arrow: Arrow, barrel: Sprite) {
    const offsetW = 5,offsetH = 10;/ / migration
    const { x, y, width, verticalVelocity, horizontalVelocity, rotation } = arrow;
    const { x: _bx, y: _by, width: _bw, height: _bh } = barrel;
    let _x = x + width / 2 * Math.cos(rotation);
    let _y = y + width / 2 * Math.sin(rotation);
    let k1 = horizontalVelocity / verticalVelocity;
    let b1 = _y - k1 * _x;
    // Calculate the intersection point y
    let interSectionX = (_by - b1) / k1;
    return (
        interSectionX > _bx + offsetW &&
        interSectionX <= _bx + _bw - offsetW &&
        _x > _bx + offsetW &&
        _x <= _bx + _bw - offsetW &&
        _y > _by &&
        _y <= _by +  offsetH
    );
}
Copy the code

In this way, we can test whether the arrow can be thrown into the pot. Our arrows don’t always go in, and we need to check to see if they go over the edge. This is where the “boundary detection” method comes in. Determine whether the arrow is inside the rectangle.

/* Detect arrow hit frame */
public static isArrowOutBorder(arrow: Arrow, area: Graphics) {
    const { x, y, width, rotation } = arrow;
    const { x: _ox, y: _oy, width: _w, height: _h } = area;
    let _x = x + width / 2 * Math.cos(rotation);
    let _y = y + width / 2 * Math.sin(rotation);
    return (
        _x > _ox + _w ||
        _x < _ox ||
        _y > _oy + _h ||
        _y < _oy
    );
}
Copy the code

Similarly, we add collision detection to the game loop to determine if the game is over.

if (Collide.isArrowOutBorder(this.arrow, this.area)) {
    // alert(" hit the border ")
    this.paused = true;
}

if (Collide.isArrowEnterArea(this.arrow, this.barrel)) {
    // alert(" alert ")
    this.paused = true;
    // Insert reset
    this.enterBarrel();
}
Copy the code

Here, our “pot” little game is finished. Different business scenarios require different collision detection.

3. Advantages and disadvantages

What are the benefits of ray projection detection?

In fast moving objects, it is difficult for us to judge that a point is in the position of the target, so it is difficult to accurately judge, and the judgment of line intersection will be more applicable and accurate. We can use to shoot, ring and other small games.

What are the drawbacks of raycast detection?

Small scope of application. Irregular pattern detection is not powerful

Afterword.

Originally, I wanted to write the SAT theorem, but I didn’t think of a simple and fun example, so I decided to arrange a raycast detection first. In fact, there are many things in the game collision detection, 2D has convex polygon separation axis detection (SAT), pixel detection, 3D has ray detection (mostly used in shooting games) and so on.

Likewise, is this all there is to it

In fact, there are two pot ears, very small import, how do we judge it? More needs to be judged in practice. The following will be updated in other fun small games and game development small knowledge.

This article is shallow, please don’t hesitate your comments and likes ~ note: this article is the author’s painstaking work, reprint must declare