Script c unity 2d physics methods. Security information portal. There are three levels of rigid body control

* Unity * is a very powerful, progressive engine with a lot of potential. It has many built-in functions (including the * NvidiaPhysX * physics engine), which we, the users, will not have to manually register. :)
In this short article I would like to discuss the physical capabilities of the engine. So, let's begin:

Rigidbody
=
= What is it? =
The * Rigidbody * function hides the Absolute Rigid Body (* ATT *). If explained roughly and clearly, then * ATT * in physics and mechanics is an ideal rigid body, which, under the influence of force, cannot change its properties, but can (under its influence) move in 3 dimensions (down, up, forward, etc.). i.e. in our XYZ axes), as well as rotate in 3 dimensions (again along the XYZ axes).

In * Unity *, as in other game engines (again, I call them "game" engines roughly), * Rigidbody * is used for various objects with which we can interact, pushing, kicking, etc. Such objects under our influence will further roll, move and collide with other objects under the influence of gravity.

What use can we find for this function? =
For example, to create a car, in addition to * Rigidbody *, we need 4 wheel collider"a and * code * (* script *) applying force to the wheels, depending on the keys pressed.

  • * Mass * - The mass of our object in kilograms... It is recommended not to set the mass values ​​100 times more or less than the masses of other * ATT *.
  • * Drag * - How much the body is subject to air resistance when it moves under the influence of forces. With a value of * 0 *, there is no resistance, and an infinite value will instantly stop our object.
  • Angular drag- How much the body is subject to air resistance when it rotates under the influence of forces. With a value of * 0 *, there is no resistance, and an infinite value will instantly stop the rotation of our object.
  • Use gravity- When turned on, the object becomes influenced by gravity.
  • Is Kinematic- When enabled, the object becomes unaffected by the physics engine and can only be changed by its * Transform * function. This can be useful for creating moving platforms, for example.
  • * Interpolate * - It is used only if the movements of your ATT seem strange or awkward to you, etc .:
  1. None: Interpolation is not applied
  2. Interpolate: Compared to the transformation of the previous frame (* frame *), the next one will be smoothed.
  3. Extrapolate: The transformation of the current frame is smoothed in comparison with the estimated (approximate) transformation of the next one.
  • Freeze rotation- Prohibits any rotation, both scripted and in collisions. However, rotation can be performed by the function // transform.Rotate ()
  • Collision detection- Used to prevent fast-moving objects from passing through other objects without finding Collision"s" (a special "grid" on objects, with which they collide with each other and with the player).
  1. Discrete: The default is for our object to "notice" all other objects that it might collide with.
  2. Continuous: Use Discrete Collision with dynamic collision objects (which have * ATT *), and Continuous collision for static MeshCollider"s (without * ATT *). Mode Continuous Dynamic uses Continuous collision for one specific * ATT *. The rest of the * ATTs * will use the _Discrete_ mode. (This will greatly affect the load on the physics engine, just leave _Discrete_ if there are no problems with colliding fast objects)
  3. Continuous Dynamic: Used for objects in _Continuous_ mode or Continuous Dynamic Collision. Continuous collision will also be used for static MeshCollider"s (without * ATT *). For all others, the _Discrete_ mode is used. Used for fast moving objects.

How can we use this feature? =
= Basic knowledge.
To use * ATT *, we need an already created game object (* GameObject *), by clicking on it, we go to the menu along the following path: Components - Physics - Rigidbody ... Everything, * ATT * added! :)
The object is now subject to gravity, forces can be applied to it using scripts, but in order for the object to behave exactly the way you want it, you must add * Collider * or * Joint *.

Code rules the world.
In the script, we will now manipulate our object using the functions AddForce () and AddTorque () .
Since I use * JavaScript * in * Unity *, my examples will be with it, links to other scripting examples (on C # or * Boo *) you will find below, in paragraph Additional information on ATT.

Rigidbody.AddForce

// Rigidbody.AddForce uses 2 types of formulas, as well as many other functions related to movement in space. // 1 type: function AddForce (force: Vector3, mode: ForceMode = ForceMode.Force): void // Force that throws the object up, relative to the global coordinate system. function FixedUpdate () (rigidbody.AddForce (Vector3.up * 10);) // Using Vector3, a built-in Unity function that is basically the same as the standard coordinate system. // Type 2: function AddForce (x: float, y: float, z: float, mode: ForceMode = ForceMode.Force): void // The same, but here the X-Y-Z coordinate system is used. function FixedUpdate () (rigidbody.AddForce (0, 10, 0);)

Rigidbody.AddTorque

// The function unwinds the object around the given axis. // 1 type: function AddTorque (torque: Vector3, mode: ForceMode = ForceMode.Force): void // Spins the ATT around the global Y axis function FixedUpdate () (rigidbody.AddTorque (Vector3.up * 10);) // Type 2: function AddTorque (x: float, y: float, z: float, mode: ForceMode = ForceMode.Force): void // Does the same thing, but again in a different measurement system. function FixedUpdate () (rigidbody.AddTorque (0, 10, 0);)

ATT interacts with objects.
For the correct operation of our * ATT * they need to be supplied Collider"ami (or Collision"amy as you please ^. ^).
Read more about colliders below.


Size matters!
Observe the dimensions of your object, because they are much more significant even the * ATT * masses. If your subject is not moving correctly, is hanging in the air, or does not collide, try adjusting its magnitude (not * ATT *, but the subject itself). When importing a model from the 3D editor, its dimensions are preserved, so be careful at the modeling stage and respect the dimensions of all models.

Additional information on ATT =
On this, I will probably finish describing * ATT * or * Rigidbody *. However, there are a couple of tips, especially for those who have looked here before :)

  1. The standard cube size in * Unity * is 1 meter, therefore, it is very convenient to check the size of your models by it. To create a cube, choose from the menu GameObject - Create Other - Cube
  2. The * Mass * relative exponent determines how two objects will interact with each other.
  3. * Mass * does not affect the speed of falling from a height, use * Drag * for this purpose.
  4. The higher the * Drag * values, the more the item weighs. standard values ​​vary from 0.001(solid piece of metal) to 10(feather).
  5. If you need to modify an object using both scripts and physics, add * ATT * to it with the * Kinematic * parameter.

You can view script examples of the effect of external forces on an object with the * ATT * function at the following links:
* AddForce *
* AddTorque *

To change the script example, click on the text with the name of the programming language!

Colliders
=
= What is it? =
In the previous section, we examined how the * Rigidbody * works and mentioned the so-called * colliders *. * Collider * for us is an auxiliary object in the form of a mesh of a simple primitive or, conversely, complex shape, which is located around our model or part of the model and interacts with other objects, if they are also surrounded by colliders.
To visually explain to the connoisseurs of the * Warcraft 3 * world editor, imagine a model we imported, to which we did not assign path textures in the dudad editor - this will be our object; and the role of colliders will be played by blockers of the path around the model. Naturally, this is a rather crude comparison, because in * Unity * they are much more functional. Well, let's take a closer look.

Types of colliders. =
Colliders are added via the menu Component - Physics ... There are several types:

  • Box collider- in the form of a cube.
  • Sphere collider- in the form of a sphere.
  • Capsule collider- in the form of a capsule.
  • Mesh collider- automatically creates a collider according to the shape of the mesh of the object, cannot collide with other colliders of the same type. Mostly used for static objects, such as a race track environment.
  • Wheel collider- used for wheels, a very useful thing.
  • Compound collider- combinations of primitives that together act as one. To create such a complex collider, you need to add child objects to our base collider, and bind to them by primitive. Thus, for example, it is very convenient to make simple colliders for machines.


Customizable characteristics =
In principle, all colliders are similar to each other, they are simply used for objects of different shapes, but they have several different parameters.

  • * Cube *

* * Material * - Shows how the collider interacts with other objects, while assigning a physical material, for example, metal, ice, etc.
* Is Trigger- If the parameter is enabled, then the object is affected by the script, not the physics.
* * Size * - Size of the collider along the X-Y-Z axes.
* * Center * - Position of the collider, relative to the local coordinates of the object.

  • *Sphere*

* * Radius * - The radius of the sphere, replaces the parameter * Size *.
* The rest of the parameters are unchanged.

  • * Capsule * (parameters replace size)

* * Radius * - The thickness of the capsule.
* * Height * - Height of the cylindrical part of the collider (without rounded bases).
* * Direction * - The direction of the collider, relative to the local coordinates of the object.


  • Mesh collider(parameters replace size)

* * Mesh * - Select the desired mesh to create the collider.
* Smooth Sphere Collisions - Enabling this function smoothes the collider surface. It should be used on smooth surfaces, for example, sloping terrain without excessive angularity, on which spheres should roll.
* * Convex * - When enabled, allows our collider to collide with others of the same. Convex Mesh Collider"s are limited to 255 triangles.

  • Wheel collider(parameters replace size)

* * Radius * - The radius of the wheel.
* Suspension Distance- Maximum distance to increase the wheel suspension. The suspension always increases down the local * Y * axis.
* Suspension spring- The suspension tries to reach the specified point using various forces.

  1. Spring: Attempts to reach the specified point (position). The higher the parameter, the faster it is achieved.
  2. Damper: Softens, slows down the speed of the suspension. The higher the value, the slower the shock moves.
  3. Target Position: The total "path" that the harness can "travel". * 0 * means fully extended shock, and * 1 * means fully compressed. The default value is 0, which corresponds to a normal car suspension.

* * Mass * - Wheel mass.
* Forward / Sideways Friction - Friction parameters for simple wheel rolling and when rolling sideways (this happens in skids or when drifting).

Writing an Arkanoid in Unity. Ball and platform mechanics

So, we continue the series of articles on writing a simple game in Unity - a classic arkanoid. We will only use 2D tools provided by the engine. In each of the articles we will touch upon one aspect of game writing, and in this one we will set in motion the ball and the platform controlled by the player.

Here is a list of all the articles:

  1. Ball and platform mechanics.

Where are we staying?

In the previous lesson, we set up a project, transferred resources into it and created the first simple scene. If you haven't read it, we strongly recommend that you correct this defect.

Result preview

Platform movement

We already have the platform - we created it in the last lesson. It remains to teach her to move, and only to the left or to the right, i.e. on the X axis. To do this, we need to write a script ( Script).

Scripts are pieces of code that are responsible for a specific task. Unity can work with scripts written in three programming languages: Boo, JavaScript, and C #. We'll use the latter, but you can try your hand at other languages ​​as well.

So, to create a script, go to the tab Project, we will find there the folder of the same name Scripts and right-click on it. Let's choose Create -> C # Script... A new file will appear called NewBehaviourScript. Rename it to PlayerScript for convenience. In the tab Inspector you can see the contents of the script.

Open the script by double clicking. The MonoDevelop development environment will start, which you can subsequently change to any editor convenient for you. Here's what you'll see:

Using UnityEngine; using System.Collections; public class NewBehaviourScript: MonoBehaviour (// use this method to initialize void Start () () // Update is called when every frame of the game is rendered void Update () ())

All scripts in Unity have two methods by default:

  • Start (): Used to initialize variables or parameters that we need in the code.
  • Update (): called every frame of the game, needed to update the state of the game.

In order to move the platform, we need two types of information: position and speed.

Thus, you need to create two variables to store this information:

public float playerVelocity;
private Vector3 playerPosition;

Note that one variable is publicly declared and the other is private. Why is this done? The fact is that Unity allows you to edit the values ​​of public variables without going into the MonoDevelop editor, without having to change the code. This feature is very useful in those cases. when it is necessary to correct a value on the fly. Platform speed is one such value, which is why we announced it publicly.

Save the script in the MonoDevelop editor and go to the Unity editor. Now we have a script and we need to assign it to some object, in our case, the platform. Select our platform in the tab Hierarchy and in the window Inspector add a component by clicking on the button Add Component.

Adding our script to a component can be done in another way. Drag our script to the button area Add Component... In the tab Inspector you should see something like this:

Notice that the script component now has a field Player Velocity which can be changed immediately. This is made possible by publicly declaring the variable. Set the parameter to 0.3 and go to the MonoDevelop editor.

Now we need to know the position of the platform: playerPosition. In order to initialize a variable, refer to the script object in the Start () method:

// use this method to initialize void Start () (// get the starting position of the platform playerPosition = gameObject.transform.position;)

Great, we have determined the starting position of the platform, and now you can move it. Since we need the platform to move only along the X axis, we can use the GetAxis method of the Input class. We will pass the Horizontal string to this function, and it will return 1 if the "right" key was pressed, and -1 - "left". By multiplying the resulting number by the speed and adding this value to the player's current position, we get the movement.

We will also add a check for exiting the application by pressing the Esc key.

Here's what we should end up with:

Using UnityEngine; using System.Collections; public class PlayerScript: MonoBehaviour (public float playerVelocity; private Vector3 playerPosition; // use this method to initialize void Start () (// get the starting position of the platform playerPosition = gameObject.transform.position;) // Update is called when each frame of the game is drawn void Update () (// horizontal movement playerPosition.x + = Input.GetAxis ("Horizontal") * playerVelocity; // exit from the game if (Input.GetKeyDown (KeyCode.Escape)) (Application.Quit ();) / / update the platform position transform.position = playerPosition;))

Save the script and return to the Unity editor. Click the button Play and try to move the platform using the left and right buttons.

Defining the play area

You have most likely noticed that the platform can move outside the playing field. The fact is that we do not have any checks for going beyond any boundaries.

Let's add another public variable to our existing script and call it boundary.

This variable will store the maximum coordinate of the platform along the X axis. Since we are going to build the levels in a symmetric form around the point with coordinates (0, 0, 0), the absolute value of the boundary variable will be the same for the positive part of the X axis, and for the negative one.

Now let's add a couple of conditions. Let's do it quite simply: if the position we calculated is greater than the boundary or less than -boundary, then we simply set a new position along the X axis equal to the value of the boundary variable. Thus, we guarantee that the platform does not leave our borders and never leaves the play area. Here is the code:

Using UnityEngine; using System.Collections; public class PlayerScript: MonoBehaviour (public float playerVelocity; private Vector3 playerPosition; // use this method to initialize void Start () (// get the starting position of the platform playerPosition = gameObject.transform.position;) // Update is called when each frame of the game is drawn void Update () (// horizontal movement playerPosition.x + = Input.GetAxis ("Horizontal") * playerVelocity; // exit from the game if (Input.GetKeyDown (KeyCode.Escape)) (Application.Quit ();) / / update the position of the platform transform.position = playerPosition; // check if it is out of bounds if (playerPosition.x< -boundary) { transform.position = new Vector3 (-boundary, playerPosition.y, playerPosition.z); } if (playerPosition.x >boundary) (transform.position = new Vector3 (boundary, playerPosition.y, playerPosition.z);)))

Now go back to the editor and, switching to the game, find the optimal value for the boundary variable. In our case, the number 5.46 came up. Open up Inspector and reset the platform X position to 0, and the parameter Boundary set it according to the value you found.

Click the button Play and make sure you did everything right. The platform should only move within the playing field.

Turning on physics

To make the collisions more realistic, we will use a physics simulation. In this article we will add physical properties to the ball, platform, and field boundaries. Since we are writing a 2D game, we will be using 2D colliders. A collider is a separate type of component that allows an object to respond to the colliders of other objects.

In the window Hierarchy select our platform, go to Inspector and click on the button Add Component... In the window that appears, type collider. As you can see, there are a lot of options. Each collider has specific properties corresponding to related objects - rectangles, circles, etc.

Since our platform is rectangular, we will use Box Collider 2D... Select it, and the component will automatically determine the platform dimensions: you will not need to set them manually, Unity will do it for you.

Do the same for 3 borders ( Hierarchy -> Inspector -> Add Component -> Box Collider 2D).

With the ball a little bit differently: it has a round shape. Let's select a ball and add a component for it Circle collider 2D.

In fact, the circle and rectangle collider are very similar, except that instead of the parameter Size, which determines the width and length, in the circle is used Radius... We think that explanations here are superfluous.

Elastic collision

In order for our ball to bounce off blocks, walls and platforms, we need to set the surface (material) for the physical component added earlier. Everything is already there in Unity, we just need to add the necessary material.

Open the window Project and inside the folder Asset create a new folder called Physics. Click on the newly created folder with the right mouse button and select Create -> Physics2D Material... Name it BallPhysicsMaterial.

Every surface in Unity has two parameters: friction (friction) and elasticity (bounciness)... You can read more about the physics engine and a number of physical parameters. If you need an absolutely elastic body, then you should set friction to 0, and elasticity to 1.

Now we have the finished material, but it has nothing to do with the ball yet. Select the ball object in the tab Hierarchy and in the window Inspector you will see a field Material component Circle collider 2D... Drag the newly created material here.

Adding a component Rigid body

In order for our ball to move under the control of physics, we must add one more component to it: Rigid Body 2D... Select the ball object in the window Hierarchy and add the above component - although it has several parameters, we are only interested in one: Gravity scale... Since our ball will move only due to bounces, we will set this parameter to 0 - this way we guarantee that gravity will not react to the object. Everything else can be left unchanged.

Bead behavior

Let's create a separate script for the ball (again using C # as the programming language) and call it BallScript. Link the generated script to an object ( Hierarchy -> Inspector -> Add Component).

Before we start writing the script, let's define the behavior of the balloon:

  1. The ball has two states: inactive (when it is on the platform at the beginning of the game) and active (when it is in motion).
  2. The ball will only become active once.
  3. When the ball becomes active, we apply force to it so that it starts moving.
  4. If the ball leaves the playing field, it is deactivated and placed on the platform.

Based on this information, let's create the ballIsActive, ballPosition and ballInitialForce global variables:

private bool ballIsActive;
private Vector3 ballPosition;
private Vector2 ballInitialForce;

Now that we have a set of variables, we need to prepare the object. In the Start () method, we must:

  • create a force to be applied to the ball;
  • put the ball in an inactive state;
  • remember the position of the ball.

Here's how you can do it:

Void Start () (// create force ballInitialForce = new Vector2 (100.0f, 300.0f); // deactivate ballIsActive = false; // remember position ballPosition = transform.position;)

As you may have noticed, the force applied is not strictly vertical, but inclined to the right - the ball will move diagonally.

Void Update () (// check for pressing the space if (Input.GetButtonDown ("Jump") == true) ())

The next step is to check the state of the ball, since we only need to set the force if the ball is in an inactive state:

Void Update () (// checking if the space is pressed if (Input.GetButtonDown ("Jump") == true) (// checking the state if (! BallIsActive) ()))

If we assume that we are at the beginning of the game, then we must apply force to the ball and set it to an active state:

Void Update () (// check if the spacebar was pressed if (Input.GetButtonDown ("Jump") == true) (// check the state if (! BallIsActive) (// apply the force rigidbody2D.AddForce (ballInitialForce); // set active state ballIsActive =! ballIsActive;)))

If you now turn on the game, then by pressing the spacebar, the ball will actually start moving. However, you may notice that the ball in the inactive state does not behave quite correctly: if we move the platform, then the ball should move with it, but in fact it remains in the same position. Stop playing, let's fix this.

In the Update method, we must check the state of the ball, and if it is inactive, we need to set the position of the ball along the X axis the same as that of the platform.

The solution is simple enough, but how do we get the coordinate of a completely different object? Elementary - we will create a variable of the GameObject type and save the reference to the platform object:

public GameObject playerObject;

Let's go back to the Update () method:

Void Update () (// check if the spacebar was pressed if (Input.GetButtonDown ("Jump") == true) (// check the state if (! BallIsActive) (// apply the force rigidbody2D.AddForce (ballInitialForce); // set active state ballIsActive =! ballIsActive;) if (! ballIsActive && playerObject! = null) (// set the new position of the ball ballPosition.x = playerObject.transform.position.x; // set the position of the ball transform.position = ballPosition;)) )

Save the script and return to the Unity editor. You may have noticed that the playerObject variable is declared, used, but not initialized anywhere. Yes, that is right. To initialize it, go to the tab Hierarchy, find the ball and in the window Inspector find the component Ball script... This component has a parameter Player Object, currently empty:

Find in the tab Hierarchy our platform and drag it to the field Player Object... Start the game by pressing the button Play and make sure everything works.

Resetting the game

If at this stage you start the game and lose (so that the ball falls out of the field), then nothing will return to normal. But in fact, the game state should be reset. Let's fix this.

It is very easy to catch this state: the ball will be active, and its position along the Y-axis is negative. If so, then we transfer the ball to an inactive state and put it on the platform:

If (ballIsActive && transform.position.y< -6) { ballIsActive = !ballIsActive; ballPosition.x = playerObject.transform.position.x; ballPosition.y = -4.2f; transform.position = ballPosition; }

But this is not the end. After saving the script and starting the game, you will notice that each time you restart the level, the ball gains more and more strength. Why is this happening? The point is that we do not clear the forces applied to the ball. To fix this, we can use the IsKinematic parameter, turning it off before adding force and turning it on after falling.

Here is the final Update () method:

Public class BallScript: MonoBehaviour (private bool ballIsActive; private Vector3 ballPosition; private Vector2 ballInitialForce; // GameObject public GameObject playerObject; // use this method to initialize void Start () (// create force ballInitialForce = new Vector2 (100.0f, 300.0 f); // deactivate ballIsActive = false; // remember the position ballPosition = transform.position;) void Update () (// check if the spacebar was pressed if (Input.GetButtonDown ("Jump") == true) ( // check the state if (! ballIsActive) (// reset all forces rigidbody2D.isKinematic = false; // apply the force rigidbody2D.AddForce (ballInitialForce); // set the active state ballIsActive =! ballIsActive;) if (! ballIsActive && playerObject! = null) (// set the new position of the ball ballPosition.x = playerObject.transform.position.x; // set the position of the ball transform.position = ballPosition;) // check if the ball falls if (ballIsActive && tra nsform.position.y< -6) { ballIsActive = !ballIsActive; ballPosition.x = playerObject.transform.position.x; ballPosition.y = -4.2f; transform.position = ballPosition; rigidbody2D.isKinematic = true; } } }

And now that's exactly it. Start the game and check if everything works as expected.

In the next part

So the second article has come to an end. You have now learned how to work with scripts, colliders, and the keyboard. Next time we'll talk about the mechanics of game blocks.

Views: 734


My strange creative path has taken me into game development. Thanks to an excellent student program from an IT company, whose name consists of one Greek small letter, cooperating with our university, it was possible to assemble a team, generate documentation and establish an Agile game development under the supervision of a high-quality QA engineer (hello Anna!)

Without much thought, Unity was chosen as the engine. This is a wonderful engine that can really quickly and easily make a very bad game that, in their right mind, no one will ever play. To create a good game, you still have to shovel through the documentation, delve into some of the features and gain development experience.

Our game used the physics engine in a way it didn't expect, which caused a lot of performance issues on mobile platforms. This article, using our game as an example, describes my struggle with the physics engine and all those features of its work that were noticed on the way to a viable beta version.

The game

A few words about how it was done.
Made with Blender and a couple of python scripts. At the time of shooting, there were 16 squares in the corner of the screen, the color of which encoded 32 bits of a floating point number - the rotation of the phone at a given time. R, G - data, B - parity. 0 - 0, 255 - 1. The video shot on the computer was split into frames using ffmpeg, each frame of the render was assigned a decrypted corner. This format allowed us to survive any compression in the shooting process and overcame the fact that all programs have slightly different ideas about the passage of time. In reality, the game is played in the same way as on the render.


The plane flies through an endless and unpredictable cave, in which there are bonuses, all sorts of coins and enemies in which you can shoot homing missiles. Crashed into a wall - immediately lost.
A distinctive feature of the game is that the level is nailed to the horizon and the control in it is gyroscopic, and, moreover, absolute. I tilted the phone 45 degrees - the airplane flew at an angle of 45 degrees. You need to make a dead loop - you have to twist the tablet. No sensitivity, just hardcore.
Let's highlight two main and obvious problems for a developer:
Problem 1: Infinity
Unity stores and processes the coordinates of objects in the form of ordinary 32-bit floats, with an accuracy of somewhere up to 6 decimal places. The problem is that the game is endless, and if we fly long enough, all sorts of crazy bugs will start, up to teleportation through walls. There are several approaches to solving this problem:
  • Ignoring. In Minecraft, for example, rounding errors only made the game more interesting by spawning.

  • Teleportation to (0; 0; 0) when the plane is too far from the origin.

  • Changing the starting point. It is not the plane that moves, but the level around it.
  • In our case, the only valid option is the third one, which was implemented. About the implementation - a little later.
    The first, ignoring, is absolutely unacceptable. Creating a robot that can play our game forever is an interesting (and very simple) task that someone will solve. And ordinary Korean players should not be underestimated - the airplane is fast, the level is generated unpredictably. And if you fly and fly before passing through the walls, then much more accurate shooting will obviously start to fail after 5 minutes of flight.
    The second - teleportation of the player and the whole world - brings mobile devices to their knees, in some cases - for about half a second. This is very noticeable, and therefore unacceptable. But this is a perfectly acceptable option for unpretentious endless PC games.

    Problem 2: level generation

    There are several basic approaches to building endless runners:
  • Using ready-made level segments that dock at random. This is done, for example, in Subway Surfers. It's easy to implement, but the player quickly gets used to it and knows what to prepare for, which is boring.

  • A level is just a straight line on which obstacles are randomly placed. This is done in Joypack Joyride and Temple Run. In our case, this would greatly limit the number of maneuvers.

  • Everything is randomly generated. The most difficult, unpredictable and interesting option for the player.
  • Of course, we chose the most difficult option. At its heart is a very complex state machine that performs random transitions over them. But within the framework of this article, it is not the mechanism that is interesting, but the process of generating the level and its organization, taking into account the chosen starting point.

    Level structure

    We are flying in a cave, it has a floor and a ceiling - a couple of blocks, elementary building units. The blocks are combined into segments that seamlessly fit together. The segments, as a whole, revolve around the aircraft and move along its velocity vector, creating the illusion of flight. If the segment leaves the field of view of the camera, it is cleared of blocks, docked to the last segment of the level and filled with new blocks, according to the generator's instructions. The aggregate of such segments is the level.
    Experienced Unity developers could justifiably grimace at the scope of work and all the possible pitfalls. But in words everything is simple, and I had no development experience ...

    Basic Laws of Physics in Unity

    In a month of development, experimenting and reading the documentation, I have identified three basic laws of physics in Unity. They can be violated, but the price for violation is performance. The engine will in no way warn you about a mistake, and without a profiler, you may never know about them. Failure to comply with these laws can slow down your game. in tens once. As I understand it, violation of any law leads to the fact that the physics engine marks the offending collider as incorrect and recreates it on the object, with the subsequent recalculation of physics:
    1. Colliders must not move, rotate, turn on / off, and resize.
    Once you've added a collider to an object, forget about any impact on it or the objects that contain it. A normal collider is a purely static object. A tree, for example, can have one collider. If the tree can fall on the player, the tree will fall along with the performance. If this tree grows from a magic nutrient cloud, which does not have a collider, but can move, this will be accompanied by a drop in productivity.
    2. If the object moves or rotates - it must be a rigid body, i.e. have a Rigidbody component.
    This is written in the documentation, yes. Which you don't have to read carefully to start making a game, because Unity is very simple and intuitive.
    Rigidbody changes the relationship of the physics engine to the object. External forces begin to act on it, it can have linear and angular velocities, and most importantly, a rigid body can move and rotate by means of a physics engine, without causing a complete recalculation of physics.
    There are two types of solids - regular and kinematic. Ordinary bodies interact with each other and with ordinary colliders - one body cannot pass through another. Kinematic bodies follow simplified simulation rules - they are not affected by any external forces, including gravity. They can freely go through anything.
    If you don’t mind putting objects under the control of a physics engine, use ordinary rigid bodies. For example, if you need to beautifully roll stones off a cliff. If your scripts or animators control the object directly, use kinematic bodies, so you don't have to constantly struggle with the engine and random object collisions. For example, if you have an animated character or a guided missile that explodes on contact with something.
    3. If the object is a rigid body, it must move and rotate through the methods of a rigid body.
    Forget about calling Transform "on an object immediately after adding a collider to it. From now on, Transform is your enemy and a performance killer. Before you write transform.position = ... or transform.eulerAngles = ..., say" I now absolutely clearly understand what I am doing, I am satisfied with the brakes that will be caused by this line. ”Do not forget about hierarchical connections: if you suddenly move an object containing solid bodies, the physics will be recalculated.
    There are three levels of rigid body control:
    - The highest and, therefore, natural, level - through forces. These are the AddForce and AddTorque methods. The physics engine will take into account body weight and correctly calculate the resulting speed. All interactions of bodies occur at this level.
    - Medium - speed change. These are velocity and angularVelocity properties. On their basis, the forces affecting bodies during their interaction are calculated, as well as, obviously, their positions at the next moment in time. If a rigid body has a very low speed, it "falls asleep" to save resources.
    - The lowest level is directly the coordinates of the object and its orientation in space. These are the MovePosition and MoveRotation methods. At the next iteration of the physics calculation (this is important, since each subsequent method call within one frame replaces the call of the previous one), they teleport the object to a new position, after which it lives as before. This level is used in our game, and only it, because it provides complete control over the object.

    What's left behind? Object on / off and scale. I don't know if there is a way to resize the object without confusing the engine. Quite possibly not. Turning off the object is painless, and turning it on ... yes, it causes a recalculation of physics, in the vicinity of the turned on object. Therefore, try not to include too many objects at the same time, stretch this process over time so that the user does not notice.

    There is a law that does not affect performance, but does affect performance: a solid cannot be part of a solid. The parent object will dominate, so the child will either stand still in relation to the parent, or behave in an unpredictable and incorrect manner.

    There is another non-physics feature of Unity that is worth mentioning: dynamically creating and destroying objects via Instantiate / Destroy methods is INSANELY slow. I'm afraid to even imagine what happens under the hood during the creation of the object. If you need to create and delete something dynamically - use factories and fill them with the necessary objects during game load. Instantiate should be called as a last resort - if the factory suddenly ran out of free objects, and forget about Destroy forever - everything created should be reused.

    Application of laws in practice

    (this section contains the line of reasoning when creating a game and its features)

    The level should obviously rotate and move.
    Let's make our life easier forever by placing the level's axis of rotation - the airplane - at the origin. Now we can calculate the distance from a point to it by calculating the length of the coordinate vector of the point. A trifle, but nice.
    Moving objects together is easy through the object hierarchy in Unity because children are part of the parent. For example, the described level structure is logically implemented as follows:
    - Axis of rotation
    - - Level
    - - - Segment 1
    - - - - Block 1 (Collider)
    - - - - ...
    - - - - Block N
    - - - Segment 2 ...
    - - - Segment 3 ...
    - - - Segment 4 ...
    (You can even do without the level object)

    The script on the axis receives data from the gyroscope and sets the appropriate angle to it ... And it breaks many rules at once, because the rotation will be transmitted through the hierarchy to the colliders, which will drive the physics engine crazy. You will have to make the axis a rigid body and rotate it through the appropriate method. But what about level movement? Obviously, the axis of rotation and the object of the level will not move, each segment must be moved individually, otherwise we are faced with the problem of infinity. This means that the segments must be rigid bodies. But we already have a rigid body higher in the hierarchy and a rigid body cannot be part of a rigid body. A logical and elegant hierarchy is not suitable, you have to do everything by hand - both rotation and movement, without using an object for the axis of rotation. Be prepared for this if you have unique gameplay features.

    If you had to move the segments directly and so, then they would have to be rotated. The main difficulty is that the Unity physics engine does not have a method to "rotate an object around an arbitrary point" (Transform does, but don't be tempted). There is only "to rotate around its center." This is logical, because rotation around an arbitrary axis is both rotation and movement, and these are two different operations. But it can be imitated. First, we rotate the segment around its axis, then we rotate the coordinates of its “own axis” around the plane. Due to the fact that the plane is at the origin, we don't even have to remember school geometry and go to Wikipedia, Unity already has everything. It is enough to convert the angle of rotation to a quaternion and multiply it by the coordinates of the point. By the way, I found out about this right at the time of writing the article, before that the rotation matrix was used.

    We have enemies who push the plane against the wall, hoping to kill. There is a shield that pushes the plane away from the walls, helping to survive. This is implemented trivially - there is an offset vector, which is added to the coordinates of each segment every frame and reset after that. Anyone who wants to kick the airplane, through a special method, can leave the vector of his kick, which will be added to this displacement vector.

    Ultimately, the actual coordinates of the segment, each frame, are calculated by the level's motion control center something like this:
    Vector3 position = segment.CachedRigidbody.position; Vector3 deltaPos = Time.deltaTime * Vector3.left * settings.Speed; segment.truePosition = Quaternion.Euler (0, 0, deltaAngle) * (position + deltaPos + movementOffset);
    After all the calculations and crutches necessary for the precise alignment of the segments to work during regeneration, segment.truePosition is sent to the MovePosition method of the segment's rigid body.

    conclusions

    How fast does it all work? On the old flagships - Nexus 5 and LG G2 - the game flies at 60 FPS, with a barely noticeable drawdown when turning on new colliders during segment generation (this is inevitable and does not work around) and worms pushing out of the ground (you can create some kind of hell, to get around this, but now there is a deliberate violation of the third law). 40 stable FPS is given by any device with a gyroscope that we come across. Without knowledge and consideration of all the laws, the performance was, to say the least, unsatisfactory and the phones overheated. So much so that I thought to write my own simple specialized engine for 2D physics. Fortunately, physics in Unity turned out to be flexible enough that all the problems could be bypassed and create a unique game, it was enough only a couple of weeks of experimentation.

    Now that you know all the main pitfalls of the Unity physics engine, you can quickly clone our game, destroying the dreams, lives and faith of three poor students in humanity. I hope this article will save you a lot of time in the future and will help you find not entirely obvious violations of the laws of productive physics in your projects.

    Read the documentation and experiment, even if you use simple and intuitive tools.

    Related articles: