preface

It’s been two months since the first article, which introduced you to the basics of what a physics engine is and what it needs to look like to move forward. On this basis, we launched the second chapter, exploring the learning path of physics engine. In our daily developments, the nature is to use less than very complex physical system, most of the game is based on a rigid body, and a certain adaptation under the game scene, finally simulate the motion of objects in our regular understanding, make us feel the displacement, deformation looks for granted, comply with the law. One of the most famous mobile games is Angry Birds. So how do we achieve the angry Birds effect? Let’s explore it step by step.

Particle movement in the game

First we see such a scene, in reality, a normal bullet speed is about 300 m/s, close to the speed of sound, if put it in the game, still follow the normal speed, then shoot out of bullets will just disappear in a flash, the shooter was completely unable to observe ballistic game, Horizontal version of the clearance type of the game has become only see the muzzle flame, but can not see the barrage of horror games.

That as “bullet” bird is also the same, if it is really according to the speed of flight in the air to calculate, then we can only see a lithe figure flash by, and then like a bullet through the building to fly away. This is obviously not what we want.

So how do you solve this problem? Obviously, we should reduce the speed of high speed objects in the game. In general, depending on the size of the map, you can limit the speed to 5-25m/s. So the velocity problem is solved, but when an object’s velocity drops from 300 to 25, its energy, its collision behavior, makes a big difference. When a slow-moving bird bumps into a wall, it might just fall off in silence, and the wall says, “What just tickled me?” . So how do you solve this problem?

First of all, we know how kinetic energy relates to velocity and mass: E=12mv2E=\frac{1}{2}mv^2E=21mv2, so when the speed decreases, we need to increase the mass. The proportion of the increase in mass is equal to the proportion of the decrease in speed. So when a 5g mass bullet travels at 300m/s and its acceleration drops to 25m/s, its mass should increase 144 times to 720g. This solved the problem of low energy as a result of low speed. By this time our little bird had changed from an ordinary chicken to a strong muscular bird, capable of knocking wild boar into flight with just a single movement.

But there was a new problem, the speed was reduced and the parabola was deformed. It could have flown farther, but because of its low muzzle velocity, it fell to the ground soon, and our muscled bird became a big weight again. So how can this problem be solved? That is to restore the parabola to its original shape.

When an object is thrown sideways or horizontally, its initial velocity determines the shape of the parabola. The height and vertical velocity determine the duration of gravity, and the horizontal velocity determines the horizontal displacement. As the speed decreases, both the duration of gravity and the horizontal displacement change, resulting in a very different trajectory than before. We know the horizontal displacement: x=v horizontal tx= v_{horizontal}tx=v horizontal T, and the vertical displacement: Y =v vertical t−12gt2y = v_{vertical}t – \frac{1}{2}gt^2y=v vertical t−21gt2, where t is replaced, the parabolic equation can be obtained, Y = v v x – 12 gv level 2 vertical x2y = \ frac {v_ vertical} {} {v_ level of {}} – x \ frac {1} {2} \ frac {g} {v_ level of {}} ^ 2 x ^ 2 = y v v vertical x – 21 v level 2 single, Where v vertical v horizontal \frac{v_{vertical}}{v_{horizontal}}v horizontal v vertical is determined by the Angle of the object, is a fixed value, Gv level 2\frac{g}{v_{level}^2}v level 2g, From this we know that the transformation formula of the acceleration of gravity is gb=gnormal δ s2g_b = \frac{g_{normal}}{\Delta s^2} GB = δ s2gnormal. But is this the case? No, because when we reduce the speed, our scene is actually shrinking, the distance from 300 meters is shortened to 25 meters in the same time, so y and X need to have the same scale to get the same shape parabola. Then we can conclude that the real g-force conversion formula should be GB =gnormal δ sg_b = \frac{g_{normal}}{\Delta s} GB = δ sgnormal.

Resultant forces in the game

Power accumulator

Those who have been exposed to simple mechanics know that when multiple forces act on an object, we can process the combination of complex multiple forces into forces in several directions that are convenient for us to calculate through the resultant force and component force.

First of all, let’s make it clear that the net force is the superposition of multiple forces at the vector level, so we need to use vector addition and subtraction. The most commonly used is the parallelogram rule, and the parallelogram rule is very simple in the processing of coordinate axes, is to add the coordinates of two vectors. But the force itself is not necessarily a constant force. Air resistance, for example, may increase with increasing speed; Buoyancy increases as the volume enters the water. So we need to calculate the net force all the time, and in a game, every frame.

We can do this by creating a class dedicated to the resultant force, called a power accumulator. The concept of accumulator is widely used in object-oriented programming and functional programming, and there is also a corresponding operation method in JS — array.reduce. It can add up the elements of an array directly, and the sum doesn’t just mean sum, it can be anything. Then things are easy, we just need to sum up the forces in an array, and give a cumulative function. Let’s take a two-dimensional plane as an example:

class ForceAccum(a){
    constructor () { // Initialize the array of forces
        this.forceArray = []
    }

    addForce(force) { // Add force to array
        this.forceArray.push(force)
    }
    
    clearForceAccum () { // Clear the array for the next calculation
        this.forceArray = []
    }
    
    forceReducer (prevForce, currentForce) { // Calculate the resultant force
        const x = prevForce.x + currentForce.x
        const y = prevForce.y + currentForce.y
        return {x, y}
    }
    
    getForce () { // Get resultant force
        const force = this.forceArray.reduce(forceReducer)
        return force
    }
}

const forceAccum = new ForceAccum() // Create a force accumulatorForrceAccum. AddForce (gravity)// Increase gravityForrceAccum. AddForce (resistance)// Add resistanceForrceAccum. AddForce (thrust)// Increase thrust
const force = forrceAccum.getForce() // Calculate the resultant force
forrceAccum.clearForceAccum() // Clear the array for the next calculation
Copy the code

So that’s the simplest force accumulator and its application, adding force to the system, and finally getting the resultant force. Once we have the net force, we can use The Niu’s Law to get the acceleration, and then integrate the acceleration to get the velocity, and then integrate the velocity to get the position. Then we repeat the above for each frame.

What kind of power do we need in Angry Birds? It can be seen that all we need is the thrust from the spring of a slingshot and its own gravity, so we just need to add these two forces to our force accumulator to calculate the force that our muscular bird is receiving and the fraction of the force in the horizontal and vertical directions.

In our actual motion, there is only one force of gravity at work (regardless of air resistance), and thrust is eventually converted into the initial speed of flight in the elastic deformation of the slingshot. So if you want to simplify the calculation, you can directly convert the thrust of the spring into the initial velocity. And of course you can calculate it from impulse and momentum.

So how do we get each of these forces in code and calculate them? And that requires something else — a force generator.

Power generator

The force generator, as its name suggests, is a device for creativity. Because when we move, there are all kinds of forces, some of which are constant, like gravity when the mass is constant; Some forces vary according to the scene, such as air resistance as the speed changes constantly; Some forces vary depending on what the player does, such as thrust, spring, etc.

In fact, we can register corresponding force generators for these forces according to their characteristics, so that they can be better managed. The principle of the force generator is very simple, we through a class to create. However, each force has different characteristics, so we need to deal with different forces. There are two different methods here. One is to write all the forces that need to be included in a class, and use the corresponding method when creating forces. The other is to pass the methods and parameters that generate the force into a class and return the required force, or inject the force directly into a force accumulator. The latter flexibility will allow us to better control our system in complex mechanical systems.

class ForceRegister () { constructor (forceAccum,func, Param) {this.forceaccum = forceAccum this.func = func this.param = param this.force = null} createForce () {// Return the required force This.force = this.func(this.param) return this.force} addForce () {this.force (); forrceAccum.addForce(this.force) } }Copy the code

The func, param in the above code is the method and argument we need to generate the force. Gravity, for example, requires only the input of the mass of the object (or the inverse of the mass) and the acceleration of gravity to obtain the corresponding force — {x:0, y: mg}. Resistance is also in the same way, A resistance equation for FD = 12 rho v2 CD \ displaystyle F_ {D} \, = \ {\ tfrac {1} {2}} \ \ rho \, v ^ {2} \, C_ {D} \, AFD = 21 rho v2CDA. FD=av2F_{D} =av ^2FD=av2,a is the parameter under a certain condition. So the parameters of our resistance generator are a and velocity V, and then the direction is the opposite of the velocity of motion.

With force generators and force accumulators, we can easily manage mechanics in games. But in the game each frame requires a lot of calculation and refresh, the performance requirements must be relatively high. So there are various ways to optimize.

For example, we can remove air resistance, water resistance and so on to save complicated calculation, or we can only give a fixed value for the calculation. For gravity, which is more important, we can build in the acceleration of gravity directly into the entire physical system, rather than incorporating gravity into every calculation of every object.

In fact, we understand that we only need to set the acceleration of gravity in the system, and set the vector of initial velocity according to the user’s operation, and then we can quickly complete the parabolic movement of a bird.

conclusion

With simple mechanics and the right code, we can create a super-simple angry Birds world that conforms to the laws of mechanics. However, this is far from enough. In a game, in addition to the simple superposition and displacement of force, there are torque, collision, rotation, angular velocity and so on. Only by adding these, can we calculate the collision and score, and reasonably represent the force, rotation and movement of the object after being hit. Look forward to the next chapter in our physics Engine series