1.14 Learning Unity game development from 0-physics engine

In the previous article, we talked about how to dynamically create objects, and how to use the Prefab mechanism to reuse the objects and logic we made. In this article, we will explain how to use these functions, combined with the physics engine that comes with Unity, to achieve a simple Effects for FPS games.

physical components

First of all, we need to understand how to use the physics engine that comes with Unity. In the previous series of articles, we actually have a psychological methodology. Most of the functions of Unity are realized by the components hanging on the object. Then the physics engine is related. function is no exception.

We have been operating the Cube before, so there is still a Box Collider component that has not been mentioned. In fact, this is one of the components related to the physics engine:

Collider, as the name suggests, means colliding body. Obviously, if an object needs to participate in physical calculations, then the object generally needs to have a certain shape and size, so as to provide reasonable physical effects.

Then the Box Collider is actually very obvious. This is a Box-shaped collider. How do we uncheck the Mesh Renderer component, then we can see the green wireframe in the Scene scene. This is actually the editor helping us visualize this Collider shape:

In addition to the shape of Box, we can also use Sphere Collider, Capsule Collider, etc. You can directly search for Collider in Add Component to quickly find all available built-in Collider components, pay attention to the 2D suffix It is specially used for 2D games, we are doing 3D games now, and 2D components should not be used.

We still use Box as a demonstration now, so the size of the Box here is the size of the physical calculation that our Cube participates in. Here you may ask, since the Collider component itself has size setting parameters, can we make it Is the size involved in the physical calculation inconsistent with the size seen in the actual rendering?

Of course it is possible. If we slightly adjust the size of Collider, XYZ is +1, and re-check the Mesh Renderer component, then we can see that the green wireframe will appear outside the rendering effect (orange wireframe), like What it looks like wrapped:

This also shows that physical calculation and rendering are completely separated. It is not what you see the object renders. The physical calculation is based on this visual effect, but is calculated by an independent size outline.

Of course, if you want the physical calculation to be forcibly bound to your rendering effect, then Unity also provides a Mesh Collider. As the name suggests, this Collider is bound to the Mesh used for rendering, and will directly fit the shape of the Mesh to create a Collider to participate in physical calculation:

We delete the Box Collider (click the three points on the right, select Remove Component), add the Mesh Collider, and Unity sets the Mesh in the current Mesh Filter component to the Mesh Collider component by default, as shown in the red box in the figure. Of course, you can also set other Mesh as the shape of the Collider. Similarly, if we uncheck the Mesh Renderer component, you can see that although the green wireframe is similar to the Box Collider in outline, it is obviously composed of triangles, that is to say This Collider is composed of the same triangles as Mesh.

Of course, the Mesh Collider will not give you the option to resize. If you want to resize, you need to adjust the Transform size (Scale) of the GameObject itself, which means that the Collider is directly bound to the rendering effect.

Then you may ask, since Mesh Collider is so good, why is Box Collider given by default?

Obviously, the more complex the shape of the Collider, the greater the calculation performance consumption. If we can approximate the shape we are currently rendering with a simple shape, then the performance consumption of the physics engine calculation can be reduced, and the physical effect will not be affected. Too much unreal. This is actually a trade-off between performance and effect that is often done in game development. That is, it is good that the difference is not bad. The game can only be played if it can run smoothly. No matter how accurate the calculation is, it is useless if the frame rate is too low to play. .

After talking for a long time, we haven't explained how to use this Collider. In fact, it is very simple. As long as the object contains the Collider component, any component hung on this object will trigger a specific function when the Collider collides:

That is, the function in the red box:

It is clearer by looking at the function name. Three are a group. For example, OnCollisionXXX represents the three states of Enter, Stay, and Exit. The trigger timing of these three states is also relatively simple:

  1. When a Collider collides with another Collider for the first time, OnCollisionEnter is triggered
  2. The essence of the collision between two Colliders is that the shapes intersect. If it is a rigid body, it will generally bounce off after the collision, but the speed of the bounce may not be so fast, or it may continue to intersect due to settings or code logic, then after OnCollisionEnter is triggered Every physical frame will check whether it is still intersecting, and if so, OnCollisionStay will be triggered
  3. When the intersection is no longer there, OnCollisionExit will be triggered once. If the intersection is triggered again, then continue to run the process from 1

This state flow should still be easy to understand, and OnTriggerXXX will be discussed later.

So let's try this function now, because there must be two objects colliding, so we need to have two Cubes.

The shortcut key Ctrl+D in Unity can quickly copy one of the currently selected objects. We press Ctrl+D on the existing Cube in the scene to get a Cube (1), and its position, shape, size and components are the same as the copied one. Cube is exactly the same.

Select this Cube (1) and let's operate the Scene view a little bit, press and hold one of the blue arrows and drag to move the position of the Cube, but don't move too far, and keep the intersecting state with the Cube:

In this way we ensure that the two Colliders are in a collision state.

Ok, let's start writing code now, and create a new component for handling collision callbacks:

using UnityEngine;

public class CollisionHandler : MonoBehaviour
{
    void OnCollisionEnter(Collision collision)
    {
        Debug.Log("OnCollisionEnter from " + collision.gameObject.name);
    }
}

Then put this component on any of our Cubes, here I put it in Cube(1):

Then let's run the game and see, eh? Why didn't you see the relevant log output to the Console window?

Let's take a look at the description of the official document:

Notes: Collision events are only sent if one of the colliders also has a non-kinematic rigidbody attached. Collision events will be sent to disabled MonoBehaviours, to allow enabling Behaviours in response to collisions.

Here is a note that tells us that the Collision event will only be sent when at least one of the two Colliders that collide has a rigidbody.

So what is a rigid body? Here comes the second physical component: RigidBody

Search RigidBody directly in Add Component, you can find the component with the same name, and add it to any of our Cubes:

You can see that the parameters that can be configured in this component are:

  • Quality (Mass)
  • Whether to use gravity (Use Gravity)

Wait for the parameters, so it seems that this component is endowed with the material properties of Collider physics. After all, the name is also called a rigid body. Does that mean that this is a material that will not deform?

But the actual situation is not the case. The official document only shows that the RigidBody component only determines whether the object is affected by the physics engine, and does not represent the physical material properties of the object. What can be configured in the RigidBody component is also about the object being affected by the physics engine. After the engine influences, the effect of movement and rotation . What actually configures the physical material is that there is a Physical Material in Collider, of course we will not expand on this here.

All in all, it can be simply understood that RigidBody is just a component used to control whether an object participates in physical calculations.

Then after we add this component, run the game again to see:

explain:

  1. Cube(1) has a RigidBody component, and if Use Gravity is checked, it will be affected by gravity, then the object will change its position according to the built-in gravity acceleration when it starts running
  2. Cube(1) and Cube collide, and one of them has a RigidBody component, so the Collision message will be sent to both parties involved in the collision, and we have a script in Cube(1) that receives OnCollisionEnter and prints the log. You can see that there is Log output, the other side of the collision is another Cube, if we also add the CollisionHandler component to the Cube, then the name in the output log should be Cube(1)
  3. Since Cube(1) has a RigidBody component, it will also participate in real physical simulation. You can see that in addition to falling under gravity, it will also bounce off after colliding with another Cube (you can see the moment the game starts teleported to the right)

So now we can understand that if you want an object to participate in physical calculations, you need two conditions for this object

  1. Has a Collider component, which determines the size of the shape when participating in physical calculations
  2. Add the RigidBody component to the object that needs to be calculated by the physics engine to change its position. If you don’t need to change its position, don’t add this component. You only need the Collider component.

The function of changing the position of the object by the physical effect is automatically completed by the built-in physics engine of Unity, and does not need to be processed by itself.

If we want to deal with collision-related logic, we can write components and receive OnCollisionXXX callbacks. Of course, it should be noted that this callback will only be triggered if at least one of the two colliding objects has a RigidBody component.

FPS game trial

After we have a preliminary understanding of how to use the physics engine, we can consider making FPS games, but in fact it is a function of firing bullets and hitting walls.

In order to make this function, we need to think about what to do:

  1. First of all, it is necessary to make a bullet affected by the physics engine. Of course, it does not mean simulating acceleration, air resistance, etc., mainly to make the effect that it will stop when it hits the wall.
  2. Make another wall, bring a Collider but it won't move by itself
  3. Finally, make a logic for firing bullets

Then the first step, we take a Cube as a bullet, but the size is a bit big, we change it to a smaller point, for example, the Scale is set to (0.2,0.2,0.2), and the Cube(1) we directly configured can be directly Renamed to Bullet, deleted some irrelevant components, probably like this:

Then we make another wall. The wall is actually just a cube with a scale changed to become a long strip. We directly change the cube, because the cube already has a BoxCollider, which is directly enough.

Then move to the right side we are used to. Of course, it doesn’t matter which side you use. Drag the blue arrow to move it, and then adjust the perspective of the Scene to look at the Wall:

Selected is our Bullet

Well, the remaining step is that we need to launch the bullet. Obviously we can directly write a script to hang on the Bullet:

using UnityEngine;

public class AddVelocity : MonoBehaviour
{
    public Vector3 initialVelocity; // 初始速度

    void Start()
    {
        Rigidbody rb = GetComponent<Rigidbody>();
        if (rb != null)
        {
            rb.velocity = initialVelocity;
        }
    }
}

This script is also relatively simple to understand. It is to give the RigidBody component a speed value when starting. This is the first concept that needs to be understood when using the physics engine. If we want the object to be simulated by the physics engine, we should not directly modify the position information of the object. Instead, give some physical information, such as providing a speed, or providing a concept such as force.

We hang this component on the Bullet, and then match it with the speed. Note that the speed here is actually a vector, that is to say, the speed has a direction. We want the bullet to fly out along the blue arrow, and the blue is the Z axis (you can see The icon in the upper right corner of the scene quickly finds which axis the color corresponds to), obviously we need to configure (0,0, speed), let’s configure a (0,0,1), run it and see:

Seeing that our bullet fell before it touched the wall, it was obviously because the speed was too slow and the acceleration of gravity was so great that it fell before it reached the position. Then we directly modify the speed to speed up by 8 times:

It looks much better, it got stuck in the middle because starting the game would freeze a little bit, and then miss a short part of the initial object movement.

In this way, we have already made a "bullet" shooting the wall and bouncing off the effect.

Improve the experience of FPS games

But it is obviously not very cool, FPS games should support continuous shooting, and the hail of bullets is one of the refreshing points of this type of game.

So what do we need to do to achieve this effect?

  1. Need to receive user input, such as the left mouse button, and when pressed, a bullet will be fired at regular intervals
  2. Since there must be more than one bullet in the scene at the same time, we need to dynamically create bullets
  3. If possible, I hope to shoot with the mouse to control the crosshair like a real FPS game

First of all, the first requirement, we need to handle user input, or as before, create a new component script to handle mouse press events, each press triggers a fire logic, and if the press continues, every 0.1s to trigger a firing logic.

using UnityEngine;

public class FireController : MonoBehaviour
{
    private bool isMouseDown = false;
    private float lastFireTime = 0f;
    public float fireInterval = 0.1f;

    void Update()
    {
        if (Input.GetButton("Fire1"))
        {
            if (!isMouseDown)
            {
                isMouseDown = true;
                lastFireTime = Time.time;
                Fire();
            }
            else if (Time.time - lastFireTime > fireInterval)
            {
                lastFireTime = Time.time;
                Fire();
            }
        }
        else
        {
            isMouseDown = false;
        }
    }

    void Fire()
    {
        // 在这里实现每次触发的逻辑
        Debug.Log("Fire!");
    }
}

Considering that this trigger logic does not belong to Bullet or Wall, then we need to allocate a new GameObject to carry this logic, so we create a new Empty GameObject called FireController, and directly hang this component:

Then let's run to see if the Fire log will be printed when the left button is pressed and held down?

Then there is the second requirement. Earlier we also learned how to dynamically create a GameObject, so now we need to dynamically create a pre-made GameObject. In fact, we need to make good use of the Prefab function. We first drag the Bullet that has been created in the scene to the Project window to form a Prefab, and then delete the original Bullet in the scene. Since this Bullet is dragged from the scene, it has certain location information. It is all set to 0:

In this way, we already have a Bullet waiting to be dynamically created in our Project window.

Or find the FireController we created in the first step. We need to dynamically create objects in the Fire function, so we must first obtain this Prefab. Obviously we need to modify the code to support the assignment of this Prefab:

using UnityEngine;

public class FireController : MonoBehaviour
{
    private bool isMouseDown = false;
    private float lastFireTime = 0f;
    public float fireInterval = 0.1f;
    public GameObject bullet;

    void Update()
    {
        if (Input.GetButton("Fire1"))
        {
            if (!isMouseDown)
            {
                isMouseDown = true;
                lastFireTime = Time.time;
                Fire();
            }
            else if (Time.time - lastFireTime > fireInterval)
            {
                lastFireTime = Time.time;
                Fire();
            }
        }
        else
        {
            isMouseDown = false;
        }
    }

    void Fire()
    {
        // 在这里实现每次触发的逻辑

        // 创建新的子弹,每次都是从模板bullet复制一个出来
        GameObject newBullet = Object.Instantiate(bullet);
    }
}

We added a new public GameObject member called bullet, which is used to assign to the editor which Prefab the Bullet we need, and then create a Prefab instance through Object.Instantiate every time Fire is used, which is actually similar copy.

Then we can see that there will be a place on the FireController panel to assign GameObject to us:

This is very logical, we drag the Bullet's Prefab to assign values. After the assignment is complete, let's run to see:

1.14 Learning Unity game development from 0-physics engine

looks good

explain:

  1. After the mouse is clicked, FireController handles the logic of the left mouse button press and long press in Update, and triggers the Fire function
  2. The Fire function gets the Prefab we assigned through the member variable bullet, and creates a new instance through Object.Instantiate. Each instance is equivalent to a copy of this Prefab, but the data logic of each other is independent. You can see that each instance in the Hierarchy window on the left Each creation will add an object, the position of the created instance defaults to the origin, of course you can specify it yourself
  3. Each instance has its own AddVelocity script. The Start function is equivalent to running once before the Update function is executed for the first time after each instance is created. The Start function gives the RigidBody of the GameObject a Z-axis speed.
  4. Each instance will fly towards the wall according to the calculation of the physics engine
  5. hits a wall, bounces, and will fall due to gravity

In fact, we have initially realized the continuous shooting function, but as a programmer, I must have realized immediately that a bullet object will be created every time it is shot, but I don’t see where it will be destroyed. Wouldn’t it be a memory explosion sooner or later if you keep shooting like this?

That's right, we lack the logic of destroying the GameObject here, so we need to make up for it. Here we can simply do it. The bullet disappears 5 seconds after it is created:

using UnityEngine;

public class AddVelocity : MonoBehaviour
{
    public Vector3 initialVelocity; // 初始速度
    public float lifeTime = 5.0f;
    private float lifeStartTime;

    void Start()
    {
        Rigidbody rb = GetComponent<Rigidbody>();
        if (rb != null)
        {
            rb.velocity = initialVelocity;
        }

        lifeStartTime = Time.time;
    }

    void Update()
    {
        if (Time.time - lifeStartTime > lifeTime)
        {
            Destroy(gameObject);
        }
    }
}

To save trouble, we directly write it in the AddVelocity script, but it should be noted that the Destroy input must be the gameObject, not this, because this represents the AddVelocity class, that is, the AddVelocity component is destroyed, not the entire GameObject.

next chapter

OK, the second requirement has also been successfully completed, so the third requirement is that we need to use the mouse to control the viewing angle, and then there is a crosshair that allows us to aim, but draw a crosshair on the screen, this crosshair actually belongs to the UI The part, that is, the content that is simply displayed on the screen, is different from 3D objects, and the development of UI will be different, but every game basically needs to do UI, so this is also a very important part of game development.

So in the next chapter, we will explain how to use the built-in functions to create a UI interface in a Unity game, and combine the content of this chapter to create a crosshair.

Guess you like

Origin blog.csdn.net/z175269158/article/details/130135269