[Unity] Ultimate Mobile Guide - Notes [Understanding moving to the grappling hook, and then to the realization of the snake]

Article directory

[Unity] Ultimate Mobile Guide - Notes (from moving, grappling hooks to snakes)

Reminder

link address:

https://www.bilibili.com/video/BV15L411y7a3

https://www.youtube.com/watch?v=OHJS44fIDCU

Full project |: https://github.com/MemoryLeakHub/MovementUnity

Comprehension :

Why do I open the project, there is nothing! (¬д¬.)

When working on this project, because the video is machine-translated, it is very uncomfortable to watch. Every time a bug occurs, I have to guess its operation in unity. I guess I am dismantling it wrong, or it is the problem of the project itself, but fortunately, I After reading a lot of documents + the help of ChatGpt, these problems have been solved.

content

1. Transform mobile operation

Suppose we have set a game object and set up the venue.

After that, the necessary components have been added, the initial position has been set Position, as well as gravity and sprites, etc., and the codes involved in this chapter are all in the BoxMovement.csscript operation, now let us follow the camera lens in unity to learn together in unity There are operations about moving.

details as follows:

Please add a picture description

【1】transform.positionChange position

transform.positionIndicates the position of the current object, including the three-dimensional coordinate axis. Therefore, the operation of the script below is actually to modify the position where the X coordinate axis appears. Since we are currently operating in a 2D project, we can directly modify the first two values. , rather than the tube Zaxis.

void Update(){
    
    
    float nowX = 22.5f;
    transform.position = new Vector3(10, transform.position.y);
}

transform.position.yIt means that the value of the current y-axis will only change with the force of the current game object ( because I added a rigid body component ), which is different from the value of the X-axis that has been modified.

【2】transform.TranslatePanning

transform.Translate(X, 0, 0);Indicates that only the movement on the X axis is modified, and X is the specified movement amount.

void Update(){
    
    
    float X = 0.01f;
    transform.Translate(X, 0, 0);
}

It is operated by moving 0.01 units per second, that is transform.Translate(X/1.0f, 0, 0)( only for the convenience of understanding ), seconds are different from frames, and frames are the number of rendering frames per second. For example, if I want the game to run at 60FPS, it means rendering 60 per second. frame.

Please add a picture description

【3】transform.position +=Operation similar to translation

Move the current object along the X-axis direction, but this movement is Time.deltaTimerelated to time, +=then add the position of the current object to the right vector, thus changing the position of the object.

public float X = 0.1f;
void Update(){
    
    
    transform.position += new Vector3(X * Time.deltaTime, 0);
}

That script means we're currently moving 0.1 units per frame, so you'll notice it's transform.Translate(X, 0, 0);moving slower than before.

Please add a picture description

【4】Orientation vector

It is shorthand for [3], it Vector3.rightis a unit vector, which represents the positive direction of the X axis, that is (1, 0, 0), which will make the position of the object under each frame move to the right, and the speed of movement is controlled by X, And the movement speed will be consistent under different frame rates.

public float X = 0.1f;
void Update(){
    
    
    transform.position += Vector3.right * X * Time.deltaTime;
}

The direction vectors involved in movement in Unity are mainly unit vectors, representing various directions in three-dimensional space.

direction vector vector value describe
Vector3.forward (0, 0, 1) The unit vector of the front of the object (positive direction of the Z axis)
Vector3.back (0, 0, -1) Unit vector for the back of the object (negative direction of the Z axis)
Vector3.right (1, 0, 0) Unit vector to the right of the object (positive X-axis direction)
Vector3.left (-1, 0, 0) Unit vector to the left of the object (negative direction of the X-axis)
Vector3.up (0, 1, 0) Unit vector above the object (positive Y-axis direction)
Vector3.down (0, -1, 0) Unit vector below the object (negative Y direction)
Vector3(1.0f, 1.0f, 1.0f).normalized (1, 1, 1).normalized unit vector in the direction on the diagonal

Note that these vectors usually need to be normalized to ensure they have length 1 so that they are unit vectors.

[5] Stop at the specified position (difference between unrotated and rotated game objects under different operations)

According to the previous knowledge, it can be realized by slightly improving the content of [2], [3], and [4], which is the StopXfinal X-axis position we want to stop.

public float X = 0.1f;
void Update(){
    
    
    float StopX = 22.0f;
    if(transform.position.x >= StopX) return;
    transform.position += Vector3.right * X * Time.deltaTime;//[4]
    //transform.position += new Vector3(X * Time.deltaTime);//[3]
    //transform.Translate(Vector3.right * X * Time.deltaTime);//[2]
}

Conclusion (in the case of GameObjects without Rigidbody components):

If you are moving the rotated game object, [3]和[4]the move operation will not affect the result of its rotation, it will still move to the specified X-axis and Y-axis position in the end.

However [2], the translation operation of the X-axis will consider the problem that it has rotated, and the position of the Y-axis will change when it reaches the end point, which is [3]和[4]different from the result of the operation, while the value of the X-axis remains unchanged.

This is the difference between translating and changing position.

2. Moving distance, direction and destination

[1] Find the distance between two objects

The distance between them can be calculated by simply subtracting the position of the vector from both sides.

public class BoxMovement : MonoBehaviour
{
    
    
    public float X = 0.1f;
    private GameObject redBox;

    private void Start()
    {
    
    
        // 移除GameObject的类型声明
        redBox = GameObject.Find("redBox");
    }

    void Update()
    {
    
    
        if (redBox != null) // 检查redBox是否被正确找到
        {
    
    
            Vector3 heading = redBox.transform.position - transform.position;
            var distance = heading.magnitude;
            Debug.Log(distance);//显示距离
            transform.position += Vector3.right * X * Time.deltaTime;
        }
        else
        {
    
    
            Debug.LogError("redBox not found!"); // 如果找不到redBox,记录错误消息
        }
    }
}

①Two ways to get game objects

  • Use GameObject.Find("GameObject");, note that the private variable means that it has been redeclared internally, which will result in the inability to Update()access the variable . Therefore, according to the above script, add a check to determine whether the game object is empty, otherwise unity will throw an exception.
  • Public reference or serialization operations.

Vector3.magnitudeWhat is it?

A property used to calculate the length (modulus) of a three-dimensional vector, that is, the mathematical formula ( x 2 + y 2 + z 2 ) \sqrt(x^2+y^2+z^2)( x2+y2+z2)

[2] Find the direction between two objects

In [4] in No. 1 , we talked about the usage of vectors. Through the transformation of [1]** in **No. 2, we can obtain the vectors of two game objects, and then normalize them to get the relationship between the two objects. direction.

Vector3 heading = redBox.transform.position - transform.position;
var distance = heading.magnitude;
var direction = heading.normalized;
transform.Translate(direction * X * Time.deltaTime);

In the future, if you need to change the axis position redBoxof the final destination Y, you only need to modify heading.ythe value, depending on your needs.

The above X value can also be modified to become speed speed = distancetotaltime speed=\frac{distance}{totaltime}speed=t o t a lt im edistance, to control the object to reach the destination at a specific speed, that is, we hope to modify the totaltime \text{totaltime}totaltime moves to the destination within a specific time.

【3】Use to MoveTowardsmove to the destination

Use Vector2.MoveTowardsthe function to gradually move the object from the current position to targetthe position. The moving distance of each frame stepis determined by to achieve the effect of smooth movement. At the same time, there is no need to judge whether the condition of reaching the destination is reached.

var speed = 2;
var step = speed * Time.deltaTime;
var target = redBox.transform.position;
transform.position = Vector2.MoveTowards(transform.position, target, step);

In the video, the author uses to calculate the distance between Vector3.Distancetwo types of points. Vector3It is used as follows:

float distance = Vector3.Distance(pointA, pointB);

Therefore, it is added later to stop the timer written by the author. The unit of distance is usually consistent with the unit of your game scene, such as meters or centimeters, depending on your game settings.

if(Vector3.Distance(transform.position,target)<0.001f) return;

three,Lerp

The box has rigid body 2D ( bodyTypedynamic, gravity 0), collision body 2D, sprite Rendercomponents

redBox has rigid body 2D ( bodyTypestatic, gravity 0), collision body 2D, sprite Rendercomponents

【1】Use Mathf Lerpto move

Returns by Mathf.Lerp( linear interpolation ) a new value between the start value and the target value at the given time timeElapsed/totalTime( interpolation factort ) .this.transform.position.xtarget.xx

	public GameObject redBox;
    private float timeElapsed = 0.0f;

    private void Update()
    {
    
    
        timeElapsed += Time.deltaTime;
        var target = redBox.transform.position;
        var totalTime = 1.5f;
        var time = timeElapsed / totalTime;
        var boxStartPosition = this.transform.position;
        this.transform.position = Vector3.Lerp(boxStartPosition, target, time);

        if (this.transform.position.x >= redBox.transform.position.x)
        {
    
    
            return;
        }
    }

Mathf.LerpHas the following uses:

  1. Smooth movement: Mathf.Lerp to smoothly move objects or cameras from one position to another, instead of jumping instantaneously.

  2. Color Gradient: Mathf.Lerp Interpolates between two colors to create a color gradient effect.

  3. Animation: In animation, Mathf.Lerpcan be used to interpolate values ​​between keyframes to create smooth animation effects such as movement, rotation, and scaling.

  4. Transition effects: Used to create transition effects, such as fade effects or transition effects between images.

Note : The interpolation factor tis usually between 0 and 1, indicating the degree of interpolation from the start value to the end value. For example, t0.5 means to take the middle value between the start value and the end value.

The meaning of time that has passed can be var timeElapsed+= Time.deltaTime;better expressed with .

【2】LerpEase in

Use Vector3.Lerpthe function to EaseIngradually move the position of the current game object to according to the return value of the easing function target. LerpThe function calculates the intermediate value between the two positions through interpolation. According to the increase of time, the position of the game object will gradually approach the target position, which has an easing effect.

private float EaseIn(float k)is a custom easing function that uses a simple easing function k * k * kthat implements a slow start effect where objects start moving slowly and then gradually speed up.

private void Update(){
    
    
    timeElapsed += Time.deltaTime;
    var target = redBox.transform.position;
    var totalTime = 1.5f;
    var time = timeElapsed / totalTime;
    var boxStartPosition = this.transform.position;
    this.transform.position = Vector3.Lerp(boxStartPosition, target, EaseIn(time));

    if (this.transform.position.x >= redBox.transform.position.x){
    
    
            return;
    }
}

private float EaseIn(float k){
    
    
    return k * k * k;
}

【3】LerpEase out

This function accepts a time parameter k, the value is in the range [0, 1], indicating the proportion of the elapsed time to the total time. The function returns an easing-calculated value that kincreases rapidly as the input time approaches 1 to achieve a slow ending effect.

private float EaseOut(float k){
    
    
    return 1f+((k -= 1f)*k*k);
}

Priority split :

  • k -= 1f: First, ksubtract 1 from to map the time range from [0, 1] to [-1, 0].
  • k * k * k, and then find the cube.
  • 1f + ...: Finally, add the result of the above calculation to 1 to map the value range back to [0, 1], and make the kvalue increase rapidly when the input time is close to 1, thus achieving the effect of slow ending.

【4】LerpBounce

tLet’s see how I wrote it. The following is a demonstration of different degrees of rebound effects caused by different stages of simulation. Also consider the value of the interpolation factor in Lerp. This is really too difficult to adjust, and it is difficult to see in the X-axis direction. As a result, in the direction of the Y axis, because of the influence of gravity, there will be a slight continuous rebound, and then finally stabilize to a fixed position.

The following effect is still timethe case of fixed variable value. If you consider timeElapsedthe continuous increase, then the final boxobject can be fixed to redBoxthe position ( in short, you must adjust the size of the time yourself, otherwise you will not see the effect )

Please add a picture description

private float BounceIn (float k) {
    
    
        return 1f - BounceOut(1f - k);
    }
    private float BounceOut (float k) {
    
    			
        if (k < (1f/2.75f)) {
    
    
            return 7.5625f*k*k;				
        }
        else if (k < (2f/2.75f)) {
    
    
            return 7.5625f*(k -= (1.5f/2.75f))*k + 0.75f;
        }
        else if (k < (2.5f/2.75f)) {
    
    
            return 7.5625f *(k -= (2.25f/2.75f))*k + 0.9375f;
        }
        else {
    
    
            return 7.5625f*(k -= (2.625f/2.75f))*k + 0.984375f;
        }
    }
    private float BounceInOut (float k) {
    
    
        if (k < 0.5f) return BounceIn(k*2f)*0.5f;
        return BounceOut(k*2f - 1f)*0.5f + 0.5f;
    }

4. RigidbodyControl mobile operation

【1】How to use the keyboard to move

Use Input.GetAxisto get player input values ​​on the horizontal and vertical axes. Typically, this corresponds to the directional keys (left and right and up and down arrows) on a keyboard or the joystick input of a gamepad.

Vector2 movementDirection = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
transform.Translate(movementDirection * 2 *Time.deltaTime);

[2] Use AddForcethe mobile rigid body

The movement of the object in an arbitrary direction is realized through the rigid body component.

Note : If the movement cannot be achieved, check whether the rigid body component needs to add physical materials.

public class BoxMovement : MonoBehaviour
{
    
    
    private Rigidbody2D rb;

    private void Start()
    {
    
    
        rb = GetComponent<Rigidbody2D>();
    }

    void Update()
    {
    
    
        var speed = 10f;
        Vector2 movementDirection = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
        rb.AddForce(movementDirection * speed);
    }
}

【3】Use AddForceJump

Detects if the player has pressed the "Up Arrow" key, and if so, applies an upward momentum to the object to make it jump.

ForceMode2D.Impulseis an enumeration value representing the way to apply the momentum. Here, using Impulsemode, means to apply a momentary impulse, that is, to increase speed momentarily.

if (Input.GetKeyDown(KeyCode.UpArrow)){
    
    
        var amount = 6f;
        rb.AddForce(Vector2.up * amount, ForceMode2D.Impulse);
}

【4】Constant speed movement

rb.velocityIndicates the speed of the object, that is, it moves at a constant speed. The following operation is different from AddForcehaving an applied force, and the object will change speed immediately, without physical simulation effects.

var speed = 10f;
Vector2 movementDirection = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
rb.velocity = movementDirection * speed;

[5] Use to MovePositionmove the rigid body

MovePositionIt will change the position directly, so the effect of movement is achieved by the increment of the current frame, but it changes very smoothly, directly to a certain position, without seeing a gradient effect, which is different from it AddForce.

var speed = 10f;
Vector2 movementDirection = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
rb.MovePosition(
(Vector2)transform.position +
(movementDirection * speed * Time.deltaTime));
the difference MovePosition AddForce
way of moving Modify the position, which is non-physical ( other aspects do not change ) Apply force to change speed and position
Applicable scene Kinematic platform movement, camera movement, and more Object explosion effects, character acceleration, etc.
smoothness high, no speed gradient low, simulated inertia

5. Kinematics platform

[1] Unity operation

Add kinematics to our red platformBodyType

Please add a picture description

You can quote the start position and end position by yourself, and you can also set the value yourself

Please add a picture description

Applicable scope of different body types

  1. Dynamic (dynamic rigid body) :

    • bodyTypeDynamicWhen set to , the rigidity will be simulated by the physics engine, including gravity, collision, force and velocity, etc.

    • Dynamic rigid bodies respond freely to external forces, such as gravity, applied forces, etc., and are simulated according to physical rules, which can produce realistic dynamic behavior in the physical world.

    • Suitable for objects that require physical simulation, such as characters, bullets, spheres, etc. These objects can be affected by external forces and collide with other objects.

  2. Kinematic (kinematic rigid body) :

    • bodyTypeKinematicWhen set to , the rigidbody is not affected by the forces of the physics engine ( character objects cannot be pushed ), but can still collide with other objects.

    • Kinematic rigid bodies are usually manually controlled by the developer and can be moved by scripts, but are not affected by the forces and gravity of the physics engine.

    • Suitable for objects that require precise control of position and movement, such as platforms, doors, elevators, etc. They can interact with other objects, but are not disturbed by external forces.

  3. Static (static rigid body) :

    • bodyTypeStaticWhen set to , the rigidbody is considered static, will not be affected by any external forces, and will not move.

    • Static rigid bodies are usually used for fixed objects such as walls, floors, buildings, etc. They don't respond to physics engine simulations, don't participate in collision response, and just exist as static environment elements.

【2】TransformWriting:

Mathf.PingPng(t,length);To realize the loop effect, the t parameter represents a value that changes with time, and the length represents the cycle period.

public class redPlatform : MonoBehaviour
{
    
    
    public Transform startPosition;
    public Transform endPosition;
    public float speed = 3f;

    private void Update()
    {
    
    
        float time = Mathf.PingPong(Time.time * speed, 1);
        Vector3 position = Vector3.Lerp(startPosition.position, endPosition.position, time);
        transform.position = position;
    }
}

【3】Writing of rigid body:

[2] above has the same effect as [3] here.

Please add a picture description

public class redPlatform : MonoBehaviour
{
    
    
    public Transform startPosition;
    public Transform endPosition;
    public float speed = 3f;
    private Rigidbody2D rb;

    private void Start()
    {
    
    
        rb = GetComponent<Rigidbody2D>();
    }
    private void Update()
    {
    
    
        float time = Mathf.PingPong(Time.time * speed, 1);
        Vector3 position = Vector3.Lerp(startPosition.position, endPosition.position, time);
        rb.MovePosition(position);
    }
}

Summary: Rigidbodythe transformdifference between

  1. Rigidbodymove :
  • Physical simulation: When using a rigid body to move, the object will be simulated by the physics engine, including gravity, collision and other forces, and it will also interact with the surrounding environment with these physical properties.

  • Collision detection: The rigid body automatically detects and responds to collisions to avoid objects passing through other objects.

  • Physics engine: Rigid body movement is controlled by a physics engine, which is usually used in situations that require physical interaction and simulation, such as character control, objects pushed by force, etc.

  1. Transformmove :
  • Non-physical simulation: Directly changing the position of the Transform is a non-physical way of moving, and the object will immediately reach the new position without being affected by the physics engine.
    • No Collision Detection: Transform movement does not automatically detect and respond to collisions, which means objects can pass through other objects, potentially causing unnatural behavior.
  • Simple control: Transform movement is easier to control and can be used in some simple situations, such as UI element movement, camera following, initial position setting of objects, etc.

To prevent confusion :

MovePositionAnd velocity, they are similar to Transformthe movement of components, but also have physical properties.

Anecdote :

The essence of human beings is pretentiousness and plagiarism. I often hear a group of bigwigs say that the physical effects that come with Unity are too bad! It's better to make the wheel yourself, and then some of them will directly use Azure's open source code to study whether there are physical properties.

( ゚Д゚)b (・ω・)b (o^-')b (* ̄▽ ̄)d

Six, quaternions

Summary of this chapter :

They are all talking about the different ways of axial vector and Euler angle to achieve the goal of rotation.

[1] Use quaternion Euler rotation

The principle of 2D rotation (realization of Euler angle, changing the Z axis of the local coordinate axis)

Quaternion.Eulermethod to create a degreesquaternion that rotates degrees around the Z axis. Vector3.forwardRepresents the Z-axis direction in the world coordinate system, so Vector3.forward * degreesit represents the rotation around the Z-axis by a specified angle.

transform.rotation = ...: Assign the calculated quaternion to the rotation property of the object

void Update(){
    
    
    var degrees = 30;
    transform.rotation = Quaternion.Euler(Vector3.forward * degrees);
}

The effect is as follows:

Please add a picture description

Simple understanding of quaternions :

Quaternion is a mathematical concept that extends complex numbers to three-dimensional space, used to represent rotation, and can avoid the Gimbal Lock problem (Gimbal Lock)

It consists of a scalar (real part) and a three-dimensional vector (imaginary part), which can be expressed as q = s + xi + yj + zk, where sis the scalar part, x, y, zare the imaginary parts.

For more content, please see this article Quaternion - Euler Angle - Universal Lock - Zhihu (zhihu.com)

【2】Use LookRotationRotate to view moving objects

vectorToTargetCalculates redBoxthe position vector from the current object to

rotateVectorToTargetThe vector is rotated vectorToTargetto make it rotate 90 degrees ( that is, fine-tuning, which will make the value of transform different from the value without fine-tuning, but in the end, because the LookRotation function is used, the effect seen is the same ).

Quaternion.LookRotation(Vector3.forward, rotateVectorToTarget)Creates a quaternion that orients the current object's Xaxes rotateVectorToTargetto orient the object at redBoxthe position.

Summary : boxtowards redBoxthe position of another object, but rotated 90 degrees when facing.

public class BoxMovement : MonoBehaviour
{
    
    
    public GameObject redBox;

    void Update()
    {
    
    
        Vector3 vectorToTarget = redBox.transform.position - transform.position;
        Vector3 rotateVectorToTarget = Quaternion.Euler(0, 0, 90) * vectorToTarget;
        transform.rotation = Quaternion.LookRotation(Vector3.forward, rotateVectorToTarget);
    }
}

The effect is as follows:

Please add a picture description

To repeat the principle again, in this 2D scene, our global coordinate axis will not change in any way, but the local coordinate axis will be changed , that is, Transformthe Zaxis value will change, thereby simulating the effect of an object rotation, as shown in the figure below The global display is displayed ( if it is not displayed, click the game object and Wpress the button )

Please add a picture description

To understand more principles, please read the following two articles:

Understanding Quaternion.LookRotation()_quaternion.lookrotation()_kenyr's Blog - CSDN Blog

https://devpress.csdn.net/game/6462fb0b6618ef1144e308d8.html

【3】Use FromToRotationRotate to view moving objects

The effect I see is consistent with the above [2] example, and the change of the local coordinate axis is also similar. At first, I couldn’t understand why unity had to do it multiple times. What is the difference between them? I took a look at the documentation comments of VS later.

void Update(){
    
    
     Vector3 vectorToTarget = redBox.transform.position - transform.position;
     transform.rotation = Quaternion.FromToRotation(Vector3.right, vectorToTarget);
}

According to the official Unity documentation, Quaternion.FromToRotationand Quaternion.LookRotationare both used to set a quaternion to rotate an object so that it faces the target direction, but their purposes are slightly different:

  1. Quaternion.FromToRotation

    • Quaternion.FromToRotationUsed to create a quaternion that rotates a vector from one direction to another.
    • You need to provide two vectors as parameters, the original direction and the target direction.
    • This function does not take into account the current orientation of the object , it only calculates the rotation needed to rotate from one orientation to another. This means it is often used to orient an object in a certain direction regardless of the current rotation.
  2. Quaternion.LookRotation

    • Quaternion.LookRotationUsed to create a quaternion that points the front of the object (usually the Z axis) in the target direction.
    • You need to provide two parameters: one to specify the "up" direction of the object (usually the Y axis), and the other to specify the target direction (for 2D projects, we don't need to change the Y axis )
    • This function takes into account the object's current rotation to ensure that the "above" of the object is consistent with the specified direction, while the front is pointing in the target direction. This is typically used to orient an object towards some goal, but maintain the object's current "up" orientation.

Conclusion :

In the display of unity, the change effects of their local coordinate axes are the same. They all rotate the Z axis and make the Xaxis point to the moving target. The difference between the two different functions is that the way to change the Z axis rotation is different , so You will find transform.rotationthat the Z-axis values ​​​​are completely inconsistent.

[4] Use quaternion AngleAxisrotation

The rotation is realized by the axial vector, which is different from the Euler angle, and the method is different, and the result is consistent with [1].

void Update(){
    
    
    var degree = 30;
    transform.rotation = Quaternion.AngleAxis(degree, Vector3.forward);
}

[5] RotateAroundRotate around another object

Transform.RotateAroundis a method in Unity that performs rotation around a specified point.

  • point: The center point of rotation, that is, the coordinates of the point around which the object rotates.

  • axis: Rotation axis, specify which axis the object rotates around.

  • angle: The angle of rotation, in degrees, indicates the angle at which the object will rotate around the rotation axis.

void Update(){
    
    
    var rotationSpeed = 30;
    transform.RotateAround(blueBox.transform.position,Vector3.forward, rotationSpeed * Time.deltaTime);
}

The effect is as follows ( 3D observation, will not force a certain axis to point to another object ):

Please add a picture description

RotateAroundclockwise rotation

transform.RotateAround(blueBox.transform.position,Vector3.back, rotationSpeed * Time.deltaTime);

learn by analogy

If you want to achieve boxunilateral orientation blueBox, please refer to [2] to fine-tune in advance.

Question ①: So how do two rotating game objects look at each other?

Tip: how to use Vector3.backthese LookRotationtwo

Question ②: How to make a small solar system?

In fact, isn't it just that many different planets revolve around the same sun, with different speeds and distances?

7. Camera( Replicating a simple cinemachineplug-in )

【1】The camera follows the player

In fact, it is to modify the position attribute of the camera, and pay attention to the Z value, don't write 0 just to watch the video.

public class Box : MonoBehaviour
{
    
    
    private Rigidbody2D boxPhysicsRb;

    private Vector2 movementDirection;
    public GameObject camera;

    private void Start()
    {
    
    
        boxPhysicsRb = GetComponent<Rigidbody2D>();
    }

    private void Update()
    {
    
    
        movementDirection = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
        var speed = 10f;
        boxPhysicsRb.MovePosition(
            (Vector2)this.transform.position +
            (movementDirection * speed * Time.deltaTime)
        );
        camera.transform.position = new Vector3(
                this.transform.position.x,
                this.transform.position.y, -4);
    }
}

[2] Use SmoothDampto smoothly follow the player with the camera

Vector3.SmoothDampis a function in Unity to smoothly move a vector from one position to another. It is often used in scenes such as camera following, smooth moving objects, etc., to avoid abrupt movement effects. The following is Vector3.SmoothDampthe basic usage and parameter explanation of :

Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 currentVelocity, float smoothTime, float maxSpeed = Mathf.Infinity, float deltaTime = Time.deltaTime);
  • current: The current position, that is, where to start moving.

  • target: Target position, that is, the position to move to.

  • currentVelocity: Reference parameter, used to store the current speed. Usually, you need to define a variable externally to store the velocity, and then pass it to SmoothDampthe function so that the function updates the velocity every frame.

  • smoothTime: Smoothing time, representing the time required currentto move from to . targetSmaller values ​​result in faster smoothing, larger values ​​result in slower smoothing.

  • maxSpeed(Optional): A maximum speed limit to make sure you don't move too fast. By default it is set to positive infinity, this can be adjusted as desired.

  • deltaTime(Optional): Time interval for each frame. Usually, you can use Time.deltaTimet as this parameter, so that the smoothing effect is independent of the frame rate.

public class Box : MonoBehaviour
{
    
    
    private Rigidbody2D boxPhysicsRb;

    private Vector2 movementDirection;
    public GameObject camera;

    private void Start()
    {
    
    
        boxPhysicsRb = GetComponent<Rigidbody2D>();
    }

    private void Update()
    {
    
    
        movementDirection = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
        var speed = 10f;
        boxPhysicsRb.MovePosition(
            (Vector2)this.transform.position +
            (movementDirection * speed * Time.deltaTime)
        );
        
    }
    void LateUpdate()
    {
    
    
        Vector3 velocity = Vector3.zero;
        //获取摄像机位置
        Vector3 targetPosition = new Vector3(boxPhysicsRb.transform.position.x, boxPhysicsRb.transform.position.y, camera.transform.position.z);
        camera.transform.position = Vector3.SmoothDamp(camera.transform.position, targetPosition, ref velocity, 0.06f);
    }
}

Lerpit is also fine

camera.transform.position = Vector3.Lerp(camera.transform.position, targetPosition, 10f * Time.deltaTime);

[3] Set the camera boundary

①Move the camera after moving to a certain distance :

Use Vector3.Distance(摄像机位置,刚体位置), and then the conditional judgment is enough.

Disadvantages: Some fine-grained boundary issues are troublesome to deal with

void LateUpdate()
    {
    
    
        Vector3 targetPosition = new Vector3(boxPhysicsRb.transform.position.x, boxPhysicsRb.transform.position.y, camera.transform.position.z);
        Vector3 velocity = Vector3.zero;
        var distance = Vector3.Distance(
            camera.transform.position,
            targetPosition);
        if (distance > 2f)
        {
    
    
            camera.transform.position = Vector3.SmoothDamp(
                camera.transform.position,
                targetPosition, ref velocity, 0.06f);
        }
    }

② Use to Matf.Clampset the camera boundary (the camera will not move after moving out of the range)

Mathf.Clampis a commonly used mathematical function used to constrain a value within a specified range. What it does is ensure that a value does not fall outside the range between the min and max values. Specifically, Mathf.Clampaccepts three parameters:

  1. The first parameter is the value to limit.

  2. The second parameter is the minimum value of the range.

  3. The third parameter is the maximum value of the range.

void LateUpdate()
    {
    
    
        Vector3 velocity = Vector3.zero;
        Vector3 bounds = new Vector3(
                Mathf.Clamp(boxPhysicsRb.gameObject.transform.position.x, -4f, 4f),
                Mathf.Clamp(boxPhysicsRb.gameObject.transform.position.y, -4f, 4f),
                camera.transform.position.z
            );
        camera.transform.position = Vector3.SmoothDamp(
            camera.transform.position, bounds, ref velocity, 0.06f);
    }

Note : According to Mathf.Clampthe meaning, if it is out of range, the camera will be ignored, which is different from the meaning of ①, so in the actual game, it is necessary to set an additional boundary collision body, which is very troublesome

8. Bullet launch and ballistic trajectory ray prompt

【1】Simple bullet launch

Rotate character with keyboard

public float rotationSpeed = 90f;
void Update(){
    
    
    float horizontalInput = Input.GetAxis("Horizontal");
    transform.Rotate(Vector3.back * rotationSpeed * Time.deltaTime * horizontalInput);
}

Instantiateis a function in Unity that is used to create a new GameObjectcopy of the game object ( ) while the game is running (instantiated at runtime).

public static Object Instantiate(Object original, Vector3 position, Quaternion rotation);
  • original: The original object to instantiate. Usually a prefab (Prefab), but also other instantiable objects, such as audio clips, materials, etc.

  • position: The location of the new instance. That is, where to place the new instance in the scene.

  • rotation: The rotation of the new instance. The orientation to rotate the new instance to.

  • InstantiateThe function will return a Objectreference of type.

steps :

Start()Create a new bullet prefab with a rigid body component and the life cycle in the prefab script to achieve the initial velocity of the rigid body,

public float speed = 10f;
public Rigidbody2D rb;
void Start(){
    
    
     rb.velocity = transform.right * speed;
}

Then just create a new object in the box. You need to determine shotGothe position where the bullet is generated. In addition, pay attention to the difference between the dynamics and dynamics of the rigid body.

Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);

The effect is as follows:

Please add a picture description

[2] Ballistic trajectory using physics simulation script

var bullet = Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);
bullet.GetComponent<Bullet>().Shoot(force);

Then improve it a little bit and turn it into a bullet script, which can be called by public functions.

box.cs

var bullet = Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);
var bs = bullet.GetComponent<BulletScript>();
bs.Test(10);

bulletScript.cs

public class BulletScript : MonoBehaviour
{
    
    
    public float speed = 10f;
    public Rigidbody2D rb;
    void Start()
    {
    
    
         //rb.velocity = transform.right * speed;
    }

    public void Test(float speed)
    {
    
    
        Debug.Log("Test"+speed);
        rb.velocity = transform.right * speed;
    }
}

Then boxadd LineRendercomponents to it, so that a ballistic ray can be drawn ( it is indeed possible to write the following video, but the bullet cannot be fired )

有问题的代码

public class Box : MonoBehaviour
{
    
    
    public Rigidbody2D rb;
    public float rotationSpeed = 90f;
    public GameObject bulletPref;
    public Transform shootGo;
    public LineRenderer boxLineRenderer;

    private void Start()
    {
    
    
        boxLineRenderer.positionCount = 0;
    }

    void Update()
    {
    
    
        float horizontalInput = Input.GetAxis("Horizontal");
        transform.Rotate(Vector3.back * rotationSpeed * Time.deltaTime * horizontalInput);
        var bullet = Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);
        var bs = bullet.GetComponent<BulletScript>();
        bs.Test(10);
        DrawTrail(bullet);
    }

    private void DrawTrail(GameObject bullet)
    {
    
    
        Vector3[] points = new Vector3[50];
        boxLineRenderer.positionCount = points.Length;
        float accumulatedTime = 0f;
        Physics2D.simulationMode = SimulationMode2D.Script;
        for (int i = 0; i < points.Length; i++)
        {
    
    
            accumulatedTime += Time.fixedDeltaTime;
            Physics2D.Simulate(accumulatedTime);
            points[i] = bullet.transform.position;
        }
        Physics2D.simulationMode = SimulationMode2D.FixedUpdate;
        boxLineRenderer.SetPositions(points);
        Destroy(bullet.gameObject);
    }
}

Because Physics2D.simulationMode = SimulationMode2D.Scriptit will conflict with our bullet firing logic, the problem becomes that we can only write physics simulation by ourselves .

Just kidding, in fact, just change the logic so that the code logic of physics simulation and bullet launch does not appear in the same local scope ( Problem: If our logic is too complex and does not control the number of bullets that appear, the performance will suffer Very poor , it depends on whether you need the requirements of [3] in the future).

private float previousBoxRotation = -1f;
void Update(){
    
    
    //...
    if (previousBoxRotation != this.transform.rotation.eulerAngles.z){
    
    
            DrawTrail(bullet);
            previousBoxRotation = this.transform.rotation.eulerAngles.z;
    }
}

[3] Press the button to control the bullet launch (Debug process)

We 有问题的代码improved on the basis of the above, the ray is displayed before pressing the space bar, and the bullet is thrown when the space bar is released, which is also in line with the logic of our game .

public class Box : MonoBehaviour
{
    
    
    public Rigidbody2D rb;
    public float rotationSpeed = 90f;
    public GameObject bulletPref;
    public Transform shootGo;
    public LineRenderer boxLineRenderer;

    private void Start()
    {
    
    
        boxLineRenderer.positionCount = 0;
    }

    void Update()
    {
    
    
        float horizontalInput = Input.GetAxis("Horizontal");
        transform.Rotate(Vector3.back * rotationSpeed * Time.deltaTime * horizontalInput);
        FireBullet();
        
    }

    private void FireBullet()
    {
    
    
        GameObject bullet = null;
        if (Input.GetKeyDown(KeyCode.Space))
        {
    
    
            bullet = Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);
            DrawTrail(bullet);
        }
        else if(Input.GetKeyUp(KeyCode.Space))
        {
    
    
            bullet = Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);
            bullet.GetComponent<BulletScript>().Test(5f);
        }
    }

    private void DrawTrail(GameObject bullet)
    {
    
    
        Vector3[] points = new Vector3[50];
        float accumulatedTime = 0f;
        Physics2D.simulationMode = SimulationMode2D.Script;

        for (int i = 0; i < points.Length; i++)
        {
    
    
            accumulatedTime += Time.fixedDeltaTime;
            Physics2D.Simulate(accumulatedTime);
            points[i] = bullet.transform.position;
        }

        Physics2D.simulationMode = SimulationMode2D.FixedUpdate;
        boxLineRenderer.positionCount = points.Length;
        boxLineRenderer.SetPositions(points);
        Destroy(bullet.gameObject);
    }
}

The effect is as follows:

Please add a picture description

Unexpectedly, this is because the properties of the ray were not changed.

public class Box : MonoBehaviour
{
    
    
    public Rigidbody2D rb;
    public float rotationSpeed = 90f;
    public GameObject bulletPref;
    public Transform shootGo;
    public LineRenderer boxLineRenderer;

    private bool isDrawingTrajectory = false;
    private GameObject bullet = null;

    void Update()
    {
    
    
        float horizontalInput = Input.GetAxis("Horizontal");
        transform.Rotate(Vector3.back * rotationSpeed * Time.deltaTime * horizontalInput);
        FireLogic();
        
    }

    private void FireLogic()
    {
    
    
        if (Input.GetKeyDown(KeyCode.Space))
        {
    
    
            StartDrawingTrajectory();
        }
        else if(Input.GetKeyUp(KeyCode.Space))
        {
    
    
            LaunchBullet();
        }

        if (isDrawingTrajectory)
        {
    
    
            bullet = Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);
            DrawTrail(bullet);
        }
    }

    private void StartDrawingTrajectory()
    {
    
    
        isDrawingTrajectory = true;
        boxLineRenderer.enabled = true;
        boxLineRenderer.positionCount = 1;
        boxLineRenderer.SetPosition(0, shootGo.position);
    }

    private void LaunchBullet()
    {
    
    
        isDrawingTrajectory = false;
        boxLineRenderer.enabled = false;
        bullet = Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);
        bullet.GetComponent<BulletScript>().Test(5f);
    }

    private void DrawTrail(GameObject bullet)
    {
    
    
        Vector3[] points = new Vector3[50];
        float accumulatedTime = 0f;
        Physics2D.simulationMode = SimulationMode2D.Script;

        for (int i = 0; i < points.Length; i++)
        {
    
    
            accumulatedTime += Time.fixedDeltaTime;
            Physics2D.Simulate(accumulatedTime);
            points[i] = bullet.transform.position;
        }

        Physics2D.simulationMode = SimulationMode2D.FixedUpdate;
        boxLineRenderer.positionCount = points.Length;
        boxLineRenderer.SetPositions(points);
        Destroy(bullet.gameObject);
    }
}

The effect is as follows:

Tired, destroy it.

Please add a picture description

The following is the final effect ( write the physical effect yourself, you can also see the need to modify )

public class Box : MonoBehaviour
{
    
    
    // 存储子弹的预制体(Prefab)
    public GameObject bulletPrefab;
    // 存储发射点的Transform对象
    public Transform shootGo;
    // 用于绘制轨迹的线渲染器
    public LineRenderer boxLineRenderer;

    // 游戏对象的旋转速度
    public float rotationSpeed = 90f;
    // 子弹发射的力量
    public float launchForce = 10f;
    // 绘制轨迹的持续时间
    public float timeToDrawTrajectory = 2f;
    // 子弹的生存时间
    public float bulletLifeTime = 3f;

    private bool isDrawingTrajectory = false;

    private void Update()
    {
    
    
        // 检测并处理旋转
        CheckRotate();
        // 处理子弹发射逻辑和轨迹绘制
        FireWithTrajectory();
    }

    private void CheckRotate()
    {
    
    
        // 获取水平输入(通常是键盘上的左右箭头或A/D键)
        float horizontalInput = Input.GetAxis("Horizontal");
        // 根据输入旋转游戏对象
        transform.Rotate(Vector3.back * rotationSpeed * Time.deltaTime * horizontalInput);
    }

    private void FireWithTrajectory()
    {
    
    
        // 计算从发射点到鼠标位置的方向向量
        Vector2 dir = (Camera.main.ScreenToWorldPoint(Input.mousePosition) - shootGo.position).normalized;

        if (Input.GetMouseButtonDown(0))
        {
    
    
            // 当鼠标左键按下时,开始绘制轨迹
            StartDrawingTrajectory();
        }
        else if (Input.GetMouseButtonUp(0))
        {
    
    
            // 当鼠标左键释放时,发射子弹
            LaunchBullet(dir);
        }

        if (isDrawingTrajectory)
        {
    
    
            // 如果正在绘制轨迹,继续绘制
            DrawTrajectory(dir);
        }
    }

    private void StartDrawingTrajectory()
    {
    
    
        // 开始绘制轨迹
        isDrawingTrajectory = true;
        // 启用线渲染器
        boxLineRenderer.enabled = true;
        // 设置线渲染器的起始点
        boxLineRenderer.positionCount = 1;
        boxLineRenderer.SetPosition(0, shootGo.position);
    }

    private void DrawTrajectory(Vector2 dir)
    {
    
    
        // 绘制子弹的轨迹
        Vector2 currentPosition = shootGo.position;
        Vector2 currentVelocity = dir * launchForce;
        float elapsedTime = 0f;

        int pointCount = 50; // 要绘制的点的数量

        Vector3[] trajectoryPoints = new Vector3[pointCount];
        trajectoryPoints[0] = currentPosition;

        for (int i = 1; i < pointCount; i++)
        {
    
    
            float timeStep = timeToDrawTrajectory / pointCount;
            elapsedTime += timeStep;
            currentPosition += currentVelocity * timeStep;
            // 应用重力
            currentVelocity += Physics2D.gravity * timeStep;
            trajectoryPoints[i] = currentPosition;
        }

        // 设置线渲染器的点
        boxLineRenderer.positionCount = pointCount;
        boxLineRenderer.SetPositions(trajectoryPoints);
    }

    private void LaunchBullet(Vector2 dir)
    {
    
    
        // 发射子弹
        isDrawingTrajectory = false;
        // 关闭线渲染器
        boxLineRenderer.enabled = false;

        // 获取子弹的方向
        Vector2 direction = dir;
        // 实例化子弹对象
        GameObject bullet = Instantiate(bulletPrefab, shootGo.position, Quaternion.identity);
        // 获取子弹上的BulletScript组件
        var bs = bullet.GetComponent<BulletScript>();
        // 调用子弹脚本的Test方法,传递方向和发射力
        bs.Test(direction, launchForce);

        // 启动协程,在一定时间后销毁子弹,模拟子弹的生命周期
        StartCoroutine(DestroyBulletAfterTime(bullet, bulletLifeTime));
    }

    private IEnumerator DestroyBulletAfterTime(GameObject bullet, float lifetime)
    {
    
    
        // 等待一定时间后销毁子弹
        yield return new WaitForSeconds(lifetime);
        Destroy(bullet);
    }
}

The effect is as follows ( finally succeeded ):

Please add a picture description

[4] Projectile trajectories with independent physical scenes (making bullets like magazines)

Let's continue with part of [2] and ignore the part with bugs for the time being.

CreatePhysicsScene_Trajectory() method :

  • Used to create a new physics scene to simulate bullet trajectories.
  • Create a new scene called "PhysicsTrajectorySimulation" and set its physics mode to LocalPhysicsMode.Physics2D.
  • Get the 2D physics scene reference for the new scene.
  • Iterate over physicsSceneObjectseach Transform object in the list, instantiate the corresponding GameObject, and move it to the new scene.
  • If the label of the game object ( referring to the ground reference ) is "StopBullet", its Collider2D component is added to stopBulletCollidersthe list. This is used to ensure that in the physics simulation, especially when the bullet interacts with the ground, it can be recognized. Ground objects with the "StopBullet" tag to implement special collision or physics behavior.
public class Box : MonoBehaviour
{
    
    
    public Rigidbody2D rb;
    public float rotationSpeed = 90f;
    public GameObject bulletPref;
    public Transform shootGo;
    public LineRenderer boxLineRenderer;
    public List<Transform> physicsSceneObjects = new();
    public GameObject physicsGround;

    private float previousBoxRotation = -1f;
    private Scene sceneSimulation;
    private PhysicsScene2D physicsScene;
    private List<Collider2D> stopBulletColliders = new();

    private void Start()
    {
    
    
        boxLineRenderer.positionCount = 0;
        physicsSceneObjects.Add(physicsGround.transform);
        CreatePhysicsScene_Trajectory();
    }

    void Update()
    {
    
    
        float horizontalInput = Input.GetAxis("Horizontal");
        transform.Rotate(Vector3.back * rotationSpeed * Time.deltaTime * horizontalInput);
        var bullet = Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);
        var bs = bullet.GetComponent<BulletScript>();
        bs.Test(10);
        if (previousBoxRotation != this.transform.rotation.eulerAngles.z)
        {
    
    
            DrawTrail(bullet);
            previousBoxRotation = this.transform.rotation.eulerAngles.z;
        }
    }

    private void DrawTrail(GameObject bullet)
    {
    
    
        Vector3[] points = new Vector3[50];
        boxLineRenderer.positionCount = points.Length;
        float accumulatedTime = 0f;
        Physics2D.simulationMode = SimulationMode2D.Script;
        for (int i = 0; i < points.Length; i++)
        {
    
    
            accumulatedTime += Time.fixedDeltaTime;
            Physics2D.Simulate(accumulatedTime);
            points[i] = bullet.transform.position;
        }
        Physics2D.simulationMode = SimulationMode2D.FixedUpdate;
        boxLineRenderer.SetPositions(points);
        Destroy(bullet.gameObject);
    }

    private void CreatePhysicsScene_Trajectory()
    {
    
    
        sceneSimulation = SceneManager.CreateScene("PhysicsTrajectorySimulation",
        new CreateSceneParameters(LocalPhysicsMode.Physics2D));
        physicsScene = sceneSimulation.GetPhysicsScene2D();
        foreach (Transform obj in physicsSceneObjects)
        {
    
    
            var physicsObject = Instantiate(obj.gameObject, obj.position, obj.rotation);
            if (physicsObject.tag == "StopBullet")
            {
    
    
                stopBulletColliders.Add(physicsObject.GetComponent<Collider2D>());
            }
            SceneManager.MoveGameObjectToScene(physicsObject, sceneSimulation);
        }
    }
}

[5] Add elasticity to the object (turn into a ruthless tennis ball transmitter)

It is to create a new 2D physical material and add it to our bullet so that it can bounce.

Please add a picture description

Remember that the material needs to set the elastic range [0,1], and you can see that our track has changed.

Please add a picture description

[6] Projectile trajectory with dots: (03:56)

I don't understand the principle of this piece, skip it for now

Implementation steps :

Get the two files from the code link , DotLineMaterial.matand DottedLineShader.shaderthen add the corresponding Shaderimplementation to the material, and then modify the material properties in the components boxof our game object .lineRender

【7】The projectile trajectory stop line at the object

The identification ballistic ray cannot be reflected in the case of Tag="StopBullet", other Tags can be reflected

public class Box : MonoBehaviour
{
    
    
    // 刚体组件,用于控制物体的物理行为
    public Rigidbody2D rb;
    // 游戏对象的旋转速度
    public float rotationSpeed = 90f;
    // 子弹的预制体
    public GameObject bulletPref;
    // 子弹发射点的Transform
    public Transform shootGo;
    // 用于绘制轨迹的线渲染器
    public LineRenderer boxLineRenderer;
    // 存储物理场景中的对象
    public List<Transform> physicsSceneObjects = new List<Transform>();
    // 物理地面的游戏对象
    public GameObject physicsGround;

    // 上一次游戏对象的旋转角度
    private float previousBoxRotation = -1f;
    // 场景模拟
    private Scene sceneSimulation;
    // 物理场景
    private PhysicsScene2D physicsScene;
    // 用于停止子弹的碰撞体列表
    private List<Collider2D> stopBulletColliders = new List<Collider2D>();

    private void Start()
    {
    
    
        // 初始化线渲染器的位置点数量
        boxLineRenderer.positionCount = 0;
        // 将物理地面添加到物理场景对象列表中
        physicsSceneObjects.Add(physicsGround.transform);
        // 创建用于轨迹模拟的物理场景
        CreatePhysicsScene_Trajectory();
    }

    void Update()
    {
    
    
        // 获取水平输入来旋转游戏对象
        float horizontalInput = Input.GetAxis("Horizontal");
        transform.Rotate(Vector3.back * rotationSpeed * Time.deltaTime * horizontalInput);
        
        // 实例化子弹
        var bullet = Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);
        // 将子弹对象移到模拟场景中
        SceneManager.MoveGameObjectToScene(bullet.gameObject, sceneSimulation);
        var bs = bullet.GetComponent<BulletScript>();
        bs.Test(10);

        // 当游戏对象旋转时,绘制轨迹
        if (previousBoxRotation != this.transform.rotation.eulerAngles.z)
        {
    
    
            DrawTrail(bullet);
            previousBoxRotation = this.transform.rotation.eulerAngles.z;
        }
    }

    private void DrawTrail(GameObject bullet)
    {
    
    
        var bulletCollider2D = bullet.GetComponent<Collider2D>();
        Vector3[] points = new Vector3[50];
        var pointsBeforeCollision = 0;
        for (int i = 0; i < points.Length; i++)
        {
    
    
            // 模拟物理场景
            physicsScene.Simulate(Time.fixedDeltaTime);
            // 如果子弹与停止碰撞体相撞,退出循环
            if (isBulletToichingStopCollider(bulletCollider2D))
            {
    
    
                break;
            }
            pointsBeforeCollision++;
            points[i] = bullet.transform.position;
        }
        // 设置线渲染器的位置点
        boxLineRenderer.positionCount = pointsBeforeCollision;
        boxLineRenderer.SetPositions(points);

        // 销毁子弹对象
        Destroy(bullet.gameObject);
    }

    private bool isBulletToichingStopCollider(Collider2D bulletCollider2D)
    {
    
     
        var pos = bulletCollider2D.gameObject.transform.position;
        foreach (Collider2D collider in stopBulletColliders)
        {
    
    
            var distance = (pos - collider.transform.position).magnitude;
            // 如果子弹与停止碰撞体相撞,返回true
            if (collider.IsTouching(bulletCollider2D))
            {
    
    
                return true;
            }
        }
        // 子弹未与停止碰撞体相撞,返回false
        return false;
    }

    private void CreatePhysicsScene_Trajectory()
    {
    
    
        // 创建用于轨迹模拟的场景
        sceneSimulation = SceneManager.CreateScene("PhysicsTrajectorySimulation", new CreateSceneParameters(LocalPhysicsMode.Physics2D));
        physicsScene = sceneSimulation.GetPhysicsScene2D();
        foreach (Transform obj in physicsSceneObjects)
        {
    
    
            // 实例化物理场景中的物体
            var physicsObject = Instantiate(obj.gameObject, obj.position, obj.rotation);
            if (physicsObject.tag == "StopBullet")
            {
    
    
                // 如果物体标签是"StopBullet",则将其碰撞体添加到停止碰撞体列表中
                stopBulletColliders.Add(physicsObject.GetComponent<Collider2D>());
            }
            // 将物体移到模拟场景中
            SceneManager.MoveGameObjectToScene(physicsObject, sceneSimulation);
        }
    }
}

[8] Add rendering to the bullet

In fact, it is to add the following components to the prefab of the bullet, change the width and color of the ray, and make the effect of the bullet more cool.

Please add a picture description

[9] Ballistic length and projectile speed

In fact, it is to press the space bar to charge, to increase the length of the trajectory, and to change the charging speed of the bullet at the same time.

public class Box : MonoBehaviour
{
    
    
    public Rigidbody2D rb;
    public float rotationSpeed = 90f;
    public GameObject bulletPref;
    public Transform shootGo;
    public LineRenderer boxLineRenderer;
    public List<Transform> physicsSceneObjects = new();
    public GameObject physicsGround;
    public float bulletForce = 10f;

    private float previousBoxRotation = -1f;
    private Scene sceneSimulation;
    private PhysicsScene2D physicsScene;
    private List<Collider2D> stopBulletColliders = new();
    private float speedLerp = 0f;
    private float speedTimeElapsed = 0.0f;
    private bool isHoldingSpace = false;

    private void Start()
    {
    
    
        boxLineRenderer.positionCount = 0;
        physicsSceneObjects.Add(physicsGround.transform);
        CreatePhysicsScene_Trajectory();
    }

    void Update()
    {
    
    

        if (Input.GetKeyDown(KeyCode.Space))
        {
    
    
            speedTimeElapsed = 0;
            isHoldingSpace = true;
        }
        if (Input.GetKeyUp(KeyCode.Space))
        {
    
    
            isHoldingSpace = false;
        }

        if (isHoldingSpace)
        {
    
    
            speedTimeElapsed += Time.deltaTime;
        }

        float horizontalInput = Input.GetAxis("Horizontal");
        var totalTime = 2f;
        speedLerp = Mathf.Lerp(0, bulletForce, speedTimeElapsed / totalTime);
        transform.Rotate(Vector3.back * rotationSpeed * Time.deltaTime * horizontalInput);
        var bullet = Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);
        SceneManager.MoveGameObjectToScene(bullet.gameObject, sceneSimulation);
        var bs = bullet.GetComponent<BulletScript>();
        bs.Test(speedLerp);
        ShowTrajectoryBouncyPhysicsScene(bullet);
        
    }

    public void ShowTrajectoryBouncyPhysicsScene(GameObject bullet)
    {
    
    
        Vector3[] points = new Vector3[50];
        boxLineRenderer.positionCount = points.Length;
        for (int i = 0; i < points.Length; i++)
        {
    
    
            physicsScene.Simulate(Time.fixedDeltaTime);
            points[i] = bullet.transform.position;
        }
        boxLineRenderer.SetPositions(points);

        Destroy(bullet.gameObject);
    }

    private void CreatePhysicsScene_Trajectory()
    {
    
    
        sceneSimulation = SceneManager.CreateScene("PhysicsTrajectorySimulation",
        new CreateSceneParameters(LocalPhysicsMode.Physics2D));
        physicsScene = sceneSimulation.GetPhysicsScene2D();
        foreach (Transform obj in physicsSceneObjects)
        {
    
    
            var physicsObject = Instantiate(obj.gameObject, obj.position, obj.rotation);
            if (physicsObject.tag == "StopBullet")
            {
    
    
                stopBulletColliders.Add(physicsObject.GetComponent<Collider2D>());
            }
            SceneManager.MoveGameObjectToScene(physicsObject, sceneSimulation);
        }
    }
}

The effect is as follows:

Please add a picture description

【10】Change and length speed with mouse

It feels super weird! I dismantled it, right?

public class Box : MonoBehaviour
{
    
    
    public Rigidbody2D rb;
    public float rotationSpeed = 90f;
    public GameObject bulletPref;
    public Transform shootGo;
    public LineRenderer boxLineRenderer;
    public List<Transform> physicsSceneObjects = new();
    public GameObject physicsGround;
    public float bulletForce = 10f;

    private float previousBoxRotation = -1f;
    private Scene sceneSimulation;
    private PhysicsScene2D physicsScene;
    private List<Collider2D> stopBulletColliders = new();
    private bool dragging = false;

    private Vector3 mouseStartDragPosition;
    private Vector3 mouseCurrentDragPosition;

    private void Start()
    {
    
    
        boxLineRenderer.positionCount = 0;
        physicsSceneObjects.Add(physicsGround.transform);
        CreatePhysicsScene_Trajectory();
    }

    void Update()
    {
    
    
        if (Input.GetMouseButtonDown(0))
        {
    
    
            mouseStartDragPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            mouseCurrentDragPosition = mouseStartDragPosition;
            dragging = true;
        }
        else if (Input.GetMouseButtonUp(0))
        {
    
    
            dragging = false;
        }

        float horizontalInput = Input.GetAxis("Horizontal");
        transform.Rotate(Vector3.back * rotationSpeed * Time.deltaTime * horizontalInput);

        if (dragging)
        {
    
    
            var rotationSpeed = 20f;
            var mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            mouseCurrentDragPosition = mousePosition;
            float yAxis = mousePosition.y * rotationSpeed;
            transform.Rotate(Vector3.forward * yAxis * Time.deltaTime);
        }
        if (previousBoxRotation != transform.rotation.eulerAngles.z)
        {
    
    
            var maxDrag = 2f;
            var maxSpeed = 10f;
            var drag = Mathf.Clamp(mouseStartDragPosition.x - mouseCurrentDragPosition.x, 0, maxDrag);
            var currentSpeed = drag / maxDrag * maxSpeed;
            ShowTrajectoryBouncyCollisionPhysicsScene(currentSpeed);
            previousBoxRotation = transform.rotation.eulerAngles.z;      
        }
    }

    public void ShowTrajectoryBouncyCollisionPhysicsScene(float force)
    {
    
    
        var bullet = Instantiate(bulletPref, shootGo.transform.position, rb.gameObject.transform.rotation);
        SceneManager.MoveGameObjectToScene(bullet.gameObject, sceneSimulation);
        bullet.GetComponent<BulletScript>().Test(force);

        var bulletCollider2D = bullet.GetComponent<Collider2D>();

        Vector3[] points = new Vector3[50];
        var pointsBeforeCollision = 0;
        for (int i = 0; i < points.Length; i++)
        {
    
    
            physicsScene.Simulate(Time.fixedDeltaTime);
            if (isBulletToichingStopCollider(bulletCollider2D))
            {
    
    
                break;
            }
            pointsBeforeCollision++;
            points[i] = bullet.transform.position;
        }
        boxLineRenderer.positionCount = pointsBeforeCollision;
        boxLineRenderer.SetPositions(points);

        Destroy(bullet.gameObject);
    }

    private bool isBulletToichingStopCollider(Collider2D bulletCollider2D)
    {
    
    
        var pos = bulletCollider2D.gameObject.transform.position;
        foreach (Collider2D collider in stopBulletColliders)
        {
    
    
            var distance = (pos - collider.transform.position).magnitude;
            if (collider.IsTouching(bulletCollider2D))
            {
    
    
                return true;
            }
        }
        return false;
    }

    private void CreatePhysicsScene_Trajectory()
    {
    
    
        sceneSimulation = SceneManager.CreateScene("PhysicsTrajectorySimulation",
        new CreateSceneParameters(LocalPhysicsMode.Physics2D));
        physicsScene = sceneSimulation.GetPhysicsScene2D();
        foreach (Transform obj in physicsSceneObjects)
        {
    
    
            var physicsObject = Instantiate(obj.gameObject, obj.position, obj.rotation);
            if (physicsObject.tag == "StopBullet")
            {
    
    
                stopBulletColliders.Add(physicsObject.GetComponent<Collider2D>());
            }
            SceneManager.MoveGameObjectToScene(physicsObject, sceneSimulation);
        }
    }
}

The effect is as follows:

To be honest, the effect is terrible, at least for PC, the feel is very poor.

Please add a picture description

9. Magnets/grappling hooks

【1】Injection magnet

public class Box : MonoBehaviour
{
    
    
    public Transform shootPosition;
    public float hookDistance = 20f;
    public LayerMask grabMask;
    public GameObject physicsHook;

    private Rigidbody2D physicsHookRb;
    private Vector2 movementDirection;
    private RaycastHit2D raycastHit2D;
    private bool shoot = false;
    private float hookTimeElapsed = 0.0f;
    private Rigidbody2D rb;

    private void Start()
    {
    
    
        rb = this.GetComponent<Rigidbody2D>();
        physicsHookRb = physicsHook.GetComponent<Rigidbody2D>();
    }

    void Update()
    {
    
    
        movementDirection = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));

        if (Input.GetKeyDown(KeyCode.Space))
        {
    
    
            hookTimeElapsed = 0;
            Example_Shoot_Hook();
        }
        hookTimeElapsed += Time.deltaTime;
    }

    void FixedUpdate()
    {
    
    
        var shootDistance = Vector3.Distance(shootPosition.transform.position, raycastHit2D.point);
        var hookSpeed = 2;
        var totalTime = shootDistance / hookSpeed;
        var time = hookTimeElapsed / totalTime;
        if (shoot && raycastHit2D.collider != null)
        {
    
    
            var push = Vector3.Lerp(shootPosition.transform.position, raycastHit2D.point, time);
            physicsHookRb.MovePosition(push);
        }
        MovePhysicsBox(2);
    }

    private void MovePhysicsBox(float speed)
    {
    
    
        rb.velocity = movementDirection * speed;
    }

    private void Example_Shoot_Hook()
    {
    
    
        RaycastHit2D hit = Physics2D.Raycast(shootPosition.transform.position, Vector3.right, hookDistance, grabMask);
        if (hit.collider != null)
        {
    
    
            shoot = true;
            raycastHit2D = hit;
        }
        else
        {
    
    
            shoot = false;
            physicsHook.transform.localPosition = new Vector2(0, 0);
        }
    }
}

redBoxAdding the name Grabto us Layerwill ensure that it can be grabbed later. The details are as follows. Add the corresponding rigid body and collision body by yourself.

Please add a picture description

The effect is as follows:

Please add a picture description

【2】Retrieve the magnet

//...
private bool toggleOnClick = false;
private float mouseTimeElapsed = 0.0f;

void Update(){
    
    
     if (Input.GetMouseButtonDown(0)){
    
    
        toggleOnClick = !toggleOnClick;
        mouseTimeElapsed = 0;
     }
     if (toggleOnClick){
    
    
        mouseTimeElapsed += Time.deltaTime;
     }
    //...
}

void FixedUpdate(){
    
    
    //...
    if (toggleOnClick)
        {
    
    
            var pullTime = mouseTimeElapsed / totalTime;
            var pull = Vector3.Lerp(physicsHookRb.transform.position, shootPosition.transform.position, pullTime);
            physicsHookRb.MovePosition(pull);
            shoot = false;
        }
    MovePhysicsBox(2);
}

The effect is as follows:

The space bar launches, and then the mouse button returns to the launch point for the first time, and the space bar can be launched again after pressing the second time. It should be noted that I let it be boxdynamic, redBoxstatic, and blueBoxdynamic.

Please add a picture description

[3] Use a magnet to pull another object

public class Box : MonoBehaviour
{
    
    
    // 存储射击位置的游戏对象
    public GameObject shootPosition;
    // 钩子的最大伸展距离
    public float hookDistance = 20f;
    
    // 用于射击和抓取物体的层级掩码
    public LayerMask grabMask;
    // 物理钩子的游戏对象
    public GameObject physicsHook;

    // 用于存储玩家移动方向的向量
    private Vector2 movementDirection;
    // 用于射击的射线命中信息
    private RaycastHit2D raycastHit2D;
    // 标志是否进行射击
    private bool shoot = false;
    // 钩子射出后的时间累计
    private float hookTimeElapsed = 0.0f;
    // 鼠标点击状态的标志
    private bool toggleOnClick = false;
    // 鼠标按下后的时间累计
    private float mouseTimeElapsed = 0.0f;

    // 物理钩子的固定关节组件
    private FixedJoint2D physicsHookFixedJoint;
    // 物理钩子的碰撞器组件
    private Collider2D physicsHookCollider;
    // 射击位置的碰撞器组件
    private Collider2D shootPositionCollider;
    // 标志是否完成了物体拉取
    private bool finishedPull = false;
    // 玩家自身的刚体组件
    private Rigidbody2D rb;
    // 物理钩子的刚体组件
    private Rigidbody2D physicsHookRb;

    private void Start()
    {
    
    
        // 获取玩家自身的刚体组件
        rb = this.GetComponent<Rigidbody2D>();
        // 获取物理钩子的刚体组件
        physicsHookRb = physicsHook.GetComponent<Rigidbody2D>();
        // 获取物理钩子的碰撞器组件
        physicsHookCollider = physicsHook.GetComponent<Collider2D>();
        // 获取射击位置的碰撞器组件
        shootPositionCollider = shootPosition.GetComponent<Collider2D>();
        // 获取物理钩子的固定关节组件
        physicsHookFixedJoint = physicsHook.GetComponent<FixedJoint2D>();
    }

    void Update()
    {
    
    
        // 获取玩家的输入以确定移动方向
        movementDirection = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));

        if (Input.GetMouseButtonDown(0))
        {
    
    
            // 切换鼠标点击状态并重置时间
            toggleOnClick = !toggleOnClick;
            mouseTimeElapsed = 0;
            finishedPull = false;
        }
        if (toggleOnClick)
        {
    
    
            // 鼠标点击状态下,累计时间
            mouseTimeElapsed += Time.deltaTime;
        }

        if (Input.GetKeyDown(KeyCode.Space))
        {
    
    
            // 重置钩子时间并射出钩子
            hookTimeElapsed = 0;
            Example_Shoot_Hook_2();
        }
        hookTimeElapsed += Time.deltaTime;
    }

    void FixedUpdate()
    {
    
    
        // 计算钩子的速度和总时间
        var shootDistance = Vector3.Distance(shootPosition.transform.position, raycastHit2D.point);
        var hookSpeed = 20;
        var totalTime = shootDistance / hookSpeed;
        var time = hookTimeElapsed / totalTime;

        if (shoot && raycastHit2D.collider != null)
        {
    
    
            // 将钩子向命中点推进
            var push = Vector3.Lerp(shootPosition.transform.position, raycastHit2D.point, time);
            physicsHookRb.MovePosition(push);
        }

        if (raycastHit2D.collider != null
        && physicsHookCollider.IsTouching(shootPositionCollider)
        && physicsHookCollider.IsTouching(raycastHit2D.collider))
        {
    
    
            // 钩子与物体碰撞,标志物体已被拉取
            finishedPull = true;
        }

        if (raycastHit2D.collider != null &&
            !physicsHookCollider.IsTouching(shootPositionCollider) &&
            physicsHookCollider.IsTouching(raycastHit2D.collider) &&
            !finishedPull &&
            !toggleOnClick)
        {
    
    
            // 如果钩子与物体碰撞,但尚未完成拉取且不在点击状态下,固定物体
            var redBox = raycastHit2D.collider.gameObject.GetComponent<Rigidbody2D>();
            physicsHookFixedJoint.connectedBody = redBox;         
        }

        if (toggleOnClick && !finishedPull)
        {
    
    
            // 如果在点击状态下且尚未完成拉取,将物理钩子收回
            var pullTime = mouseTimeElapsed / totalTime;
            var pull = Vector3.Lerp(physicsHookRb.transform.position, shootPosition.transform.position, pullTime);
            physicsHookRb.MovePosition(pull);
            shoot = false;
        }
        // 移动物理盒子
        MovePhysicsBox(2);
    }

    private void MovePhysicsBox(float speed)
    {
    
    
        // 移动玩家物体
        rb.velocity = movementDirection * speed;
    }

    private void Example_Shoot_Hook_2()
    {
    
    
        // 射出钩子,检测是否命中可抓取物体
        RaycastHit2D hit = Physics2D.Raycast(shootPosition.transform.position, Vector3.right, hookDistance, grabMask);
        if (hit.collider != null)
        {
    
    
            // 标志已射出钩子,并存储射线命中信息
            shoot = true;
            raycastHit2D = hit;
            // 断开物理钩子的连接
            physicsHookFixedJoint.connectedBody = null;
        }
        else
        {
    
    
            // 如果未命中物体,标志未射出钩子,并将物理钩子重置
            shoot = false;
            physicsHook.transform.localPosition = new Vector2(0, 0);
        }
    }
}

The key here is to ensure that the reference does not make mistakes, bodyTypedo not set mistakes, the details are as follows, and redBoxthe rigid body is selected as dynamic:

Please add a picture description

The effect is as follows :

When we shoot out the blueBox in the space

Please add a picture description

TIP : If the rigid body of the game object BodyTypeis dynamic, after adding this component Fixed Join 2D, it cannot be moved, because it will force it to become static.

[4] Create a grapple

In fact, it is to add a rope on the basis of [3]. The method is to make the lineRender simulate a rope. If the color cannot be adjusted, just choose to add a material and then modify it.

Please add a picture description

public class Hook : MonoBehaviour
{
    
    
    public Transform shootPosition;
    public LineRenderer lineRenderer;
    
    void Update()
    {
    
    
        lineRenderer.SetPosition(0, shootPosition.transform.position);
        lineRenderer.SetPosition(1, transform.position);
    }
}

10. Eating snakes

[1] Pick up objects within the range

box.cs

public class Box : MonoBehaviour
{
    
    
    private Vector2 movementDirection;
    public LayerMask collectMask;
    void Update()
    {
    
    
        movementDirection = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
    }

    void FixedUpdate()
    {
    
    
        var rotationSpeed = 150f;
        var moveSpeed = 1.5f;
        this.transform.Translate(Vector2.right * moveSpeed * Time.fixedDeltaTime, Space.Self);
        this.transform.Rotate(Vector3.forward * - movementDirection.x * rotationSpeed * Time.fixedDeltaTime);

        var radius = 3f;
        Collider2D[] hitColliders = Physics2D.OverlapCircleAll((Vector2)this.transform.position, radius, collectMask);

        Debug.Log(hitColliders);

        foreach (var hitCollider in hitColliders)
        {
    
    
            var collect = hitCollider.gameObject.GetComponent<Collect>();
            if (!collect.isCollecting)
            {
    
    
                collect.StartCollecting(this.transform);
            }
        }
    }

    private void OnDrawGizmos()
    {
    
    
        Gizmos.color = Color.yellow;
        Gizmos.DrawWireSphere((Vector2)this.transform.position, 1.5f);
    }
}

collect.cs

We need to mount the script on the collected object, and then let it have a collision body component, and add the object to be boxrecognized to the collected game object .CollectLayer

Please add a picture description

public class Collect : MonoBehaviour
{
    
    
    private bool isCollected;
    private float timeElapsed = 0;
    public bool isCollecting = false;
    private Transform character;

    void Update()
    {
    
    
        timeElapsed += Time.deltaTime;

        if (isCollecting)
        {
    
    
            var speed = 2; // seconds
            var step = speed * Time.deltaTime;
            var target = character.transform.position;
            //实现一个被吃的效果就是移动+销毁
            gameObject.transform.position = Vector2.MoveTowards(gameObject.transform.position, target, step);
            if (Vector3.Distance(gameObject.transform.position, target) < 0.001f)
            {
    
    
                isCollecting = false;
                Destroy(gameObject);
            }
        }
    }

    public void StartCollecting(Transform target)
    {
    
    
        timeElapsed = 0;
        isCollecting = true;
        character = target;
    }
}

The effect is as follows:

Please add a picture description

【2】Follow the object, snake-like movement

screenplay

box.cs

using UnityEngine;

public class Box : MonoBehaviour
{
    
    
    private Vector2 movementDirection;

    public GameObject followPartPref;
    public GameObject bodyParts;

    private void Start()
    {
    
    
    	//实例体的父类也是需要被实例化的,不然会报错,不要按视频的写法来
        var bodyPartsInit = Instantiate(bodyParts);
        GameObject part1 = CreateFollowPart(this.transform,bodyPartsInit);
        GameObject part2 = CreateFollowPart(part1.transform, bodyPartsInit);
    }

    void Update()
    {
    
    
        movementDirection = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
    }

    void FixedUpdate()
    {
    
    
        var rotationSpeed = 150f;
        var moveSpeed = 1.5f;
        this.transform.Translate(Vector2.right * moveSpeed * Time.fixedDeltaTime, Space.Self);
        this.transform.Rotate(Vector3.forward * -movementDirection.x * rotationSpeed * Time.fixedDeltaTime);
    }

    private GameObject CreateFollowPart(Transform followTarget,GameObject bodyPartsInit)
    {
    
    
        var spaceBetween = 2f;
        var bodyPart = Instantiate(followPartPref, FollowPosition(followTarget, spaceBetween), Quaternion.identity);
        bodyPart.transform.parent = bodyPartsInit.transform;
        BodyPart bodyPartComponent = bodyPart.GetComponent<BodyPart>();
        bodyPartComponent.FollowTarget = followTarget;
        bodyPartComponent.SpaceBetween = spaceBetween;
        return bodyPart;
    }

    private Vector3 FollowPosition(Transform target, float spaceBetween)
    {
    
    
        var position = target.position;
        return position - target.right * spaceBetween;
    }
}

BodyPart.cs

public class BodyPart : MonoBehaviour
{
    
    
    // 要跟随的目标对象
    public Transform FollowTarget;
    // 跟随部件之间的间隔距离
    public float SpaceBetween = 10f;
    // 默认移动速度
    private float defaultSpeed = 1.5f;
    // 当前移动速度
    private float speed = 1.5f;

    // 用于处理速度变化的方法
    public void ChangeSpeed(bool isHoldingSpace)
    {
    
    
        // 根据是否按住空格键来改变速度
        if (isHoldingSpace)
        {
    
    
            speed = defaultSpeed * 2;
        }
        else
        {
    
    
            speed = defaultSpeed;
        }
    }

    // 每帧执行的方法
    private void Update()
    {
    
    
        // 计算向目标对象移动的向量
        Vector3 vectorToTarget = FollowTarget.position - transform.position;
        // 旋转向量以确保部件始终面向前方
        Vector3 rotateVectorToTarget = Quaternion.Euler(0, 0, 90) * vectorToTarget;
        transform.rotation = Quaternion.LookRotation(Vector3.forward, rotateVectorToTarget);
        // 计算到目标对象的距离
        var distanceToHead = (transform.position - FollowTarget.position).magnitude;
        // 如果距离超过指定的间隔距离,则向前移动
        if (distanceToHead > SpaceBetween)
        {
    
    
            transform.Translate(Vector3.right * speed * Time.deltaTime);
        }
    }
}

Unity operation

This BodyPartsrequires standardized operations, and when it needs to be set as the parent class of the instance, it needs to be instantiated ( see comments ).

Please add a picture description

[3] Follow the object when eating, the snake moves and grows a snake

screenplay

box.cs

public class Box : MonoBehaviour
{
    
    
    private Vector2 movementDirection;

    public GameObject followPartPref;
    public GameObject bodyParts;
    public LayerMask collectMask;
    private int count;

    public Transform GO;

    private void Start()
    {
    
    
        GameObject part1 = CreateFollowPart(this.transform);
        GameObject part2 = CreateFollowPart(part1.transform);
        count = GO.childCount;
    }

    void Update()
    {
    
    
        movementDirection = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
    }

    void FixedUpdate()
    {
    
    
        var rotationSpeed = 150f;
        var moveSpeed = 1.5f;
        this.transform.Translate(Vector2.right * moveSpeed * Time.fixedDeltaTime, Space.Self);
        this.transform.Rotate(Vector3.forward * -movementDirection.x * rotationSpeed * Time.fixedDeltaTime);

        var radius = 1.5f;
        Collider2D[] hitColliders = Physics2D.OverlapCircleAll((Vector2)this.transform.position, radius, collectMask);
        foreach (var hitCollider in hitColliders)
        {
    
    
            var collect = hitCollider.gameObject.GetComponent<Collect>();
            collect.callback = OnEat;
            if (!collect.isCollecting)
            {
    
    
                collect.Eat(this.transform);
            }
        }
    }

    private GameObject CreateFollowPart(Transform followTarget)
    {
    
    
        var spaceBetween = 2f;
        var bodyPartsInit = Instantiate(bodyParts);
        var bodyPart = Instantiate(followPartPref, FollowPosition(followTarget, spaceBetween), Quaternion.identity);
        bodyPart.transform.parent = bodyPartsInit.transform;
        BodyPart bodyPartComponent = bodyPart.GetComponent<BodyPart>();
        bodyPartComponent.FollowTarget = followTarget;
        bodyPartComponent.SpaceBetween = spaceBetween;
        //这里的效果就类似负责存放蛇尾巴的编号
        bodyPart.transform.parent  = GO;
        return bodyPart;
    }

    private Vector3 FollowPosition(Transform target, float spaceBetween)
    {
    
    
        var position = target.position;
        return position - target.right * spaceBetween;
    }
    private void OnEat()
    {
    
    
        if (count > 0)
        {
    
    
            //Debug.Log(count);
            Transform previousPart = GO.GetChild(count - 1);
            CreateFollowPart(previousPart);
            count += 1;
        }
        else
        {
    
    
            Debug.Log("WITHOUT");
        }
    }
}

Collect.cs

public class Collect : MonoBehaviour
{
    
    
    private bool isCollected; // 标志物体是否已被收集
    private float timeElapsed = 0; // 记录时间的累计
    public bool isCollecting = false; // 标志物体是否正在被收集
    public bool isEaten = false; // 标志物体是否被吃掉
    private Transform character; // 与物体交互的角色的Transform组件
    public Action callback; // 当物体被吃掉后要执行的回调函数

    void Update()
    {
    
    
        timeElapsed += Time.deltaTime; // 更新时间累计

        if (isCollecting)
        {
    
    
            var speed = 2; // 移动速度,单位:秒
            var step = speed * Time.deltaTime; // 计算每帧的移动步长
            var target = character.transform.position; // 目标位置
            // 将物体向目标位置移动
            gameObject.transform.position = Vector2.MoveTowards(gameObject.transform.position, target, step);
            // 如果物体接近目标位置,停止收集并销毁物体
            if (Vector3.Distance(gameObject.transform.position, target) < 0.001f)
            {
    
    
                isCollecting = false;
                Destroy(gameObject);
            }
        }

        if (isEaten)
        {
    
    
            var speed = 4; // 移动速度,单位:秒
            var step = speed * Time.deltaTime; // 计算每帧的移动步长
            var target = character.transform.position; // 目标位置
            // 将物体向目标位置移动
            gameObject.transform.position = Vector2.MoveTowards(gameObject.transform.position, target, step);
            // 如果物体接近目标位置,停止吃掉并销毁物体,然后执行回调函数
            if (Vector3.Distance(gameObject.transform.position, target) < 0.001f)
            {
    
    
                isCollecting = false;
                Destroy(gameObject);
                callback();
            }
        }
    }

    // 启动收集物体的方法
    public void StartCollecting(Transform target)
    {
    
    
        timeElapsed = 0; // 重置时间累计
        isCollecting = true; // 标志物体正在被收集
        character = target; // 设置与物体交互的角色Transform
    }

    // 吃掉物体的方法
    public void Eat(Transform target)
    {
    
    
        timeElapsed = 0; // 重置时间累计
        isEaten = true; // 标志物体已被吃掉
        character = target; // 设置与物体交互的角色Transform
    }
}

Unity operation :

The video code issue is so frustrating.

Please add a picture description

The effect is as follows :

Please add a picture description

【4】cinemachineTrack the snake head with a movie camera

download plugincinemachine

Please add a picture description

Then Hierarchyright-click the movie camera game object, and then get it to the same parent empty object, and then follow our snake head.

Please add a picture description

[5] Tracking objects using a film machine with boundaries

The boundary here means that when our snake head moves to the polygon boundary we have delineated, our cinemachine will not go out of this boundary with anyone, and the snake head will not be physically out of bounds because of the collision body. .

Please add a picture description

[6] Snake game, space increases movement speed

The location and quantity of the balls are set unreasonably ( too lazy to fix the bug ), and the space is accelerated. This simple snake eating is done, and there is no failure judgment.

box.cs

A delegate is a type that represents methods and can be used to store references to those methods and invoke them later.

In the following code, the delegate is defined as OnSpeedChangeDelegate, the delegate accepts a boolparameter of type. Subsequently, onSpeedChangeDelegateis declared as a static variable with the same delegate type.

The purpose of this delegate is to store references to one or more methods with the same parameter signature. Then, in other parts of your code, you can use onSpeedChangeDelegateto call these methods without explicitly knowing their names or implementations.

public class Box : MonoBehaviour
{
    
    
    // 存储玩家的移动方向向量
    private Vector2 movementDirection;

    // 用于跟随的游戏对象预制体
    public GameObject followPartPref;
    // 身体部件的父对象
    public GameObject bodyParts;
    // 用于收集的层级掩码
    public LayerMask collectMask;
    // 身体部件的数量
    private int count;

    // 用于生成普通食物的预制体
    public GameObject normalBulletCollisionPref;
    // 存储生成的食物的容器对象
    public GameObject cleanBucket;

    // 用于处理速度变化的委托
    private delegate void OnSpeedChangeDelegate(bool isHoldingSpace);
    private static OnSpeedChangeDelegate onSpeedChangeDelegate;
    private bool isHoldingSpace = false;
    private float spawnElapsedTime;
    private List<Collider2D> allColliders = new List<Collider2D>();

    private void Start()
    {
    
    
        // 创建初始的跟随部件
        GameObject part1 = CreateFollowPart(this.transform);
        GameObject part2 = CreateFollowPart(part1.transform);
        // 获取子对象数量
        count = GO.childCount;
    }

    void Update()
    {
    
    
        // 计算生成食物的时间累计
        spawnElapsedTime += Time.deltaTime;
        // 获取玩家的输入以确定移动方向
        movementDirection = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));

        if (Input.GetKeyDown(KeyCode.Space))
        {
    
    
            // 按下空格键时标志正在按住空格键
            isHoldingSpace = true;
        }

        if (Input.GetKeyUp(KeyCode.Space))
        {
    
    
            // 松开空格键时取消标志
            isHoldingSpace = false;
        }
    }

    void FixedUpdate()
    {
    
    
        var seconds = 0.5f;
        if (spawnElapsedTime * seconds >= seconds)
        {
    
    
            // 生成食物
            spawnElapsedTime = 0;
            var height = Camera.main.orthographicSize;
            var width = height * Camera.main.aspect;
            var foodPosition = new Vector2(
                        UnityEngine.Random.Range(width, -width),
                        UnityEngine.Random.Range(height, -height));
            NormalBulletCollision(foodPosition);
        }

        var rotationSpeed = 150f;
        var moveSpeed = 1.5f;
        if (isHoldingSpace)
        {
    
    
            // 如果按住空格键,加快移动速度
            moveSpeed *= 2;
        }
        onSpeedChangeDelegate(isHoldingSpace);

        // 移动玩家
        this.transform.Translate(Vector2.right * moveSpeed * Time.fixedDeltaTime, Space.Self);
        this.transform.Rotate(Vector3.forward * -movementDirection.x * rotationSpeed * Time.fixedDeltaTime);

        var radius = 1.5f;
        // 检测碰撞区域内的可收集物体
        Collider2D[] hitColliders = Physics2D.OverlapCircleAll((Vector2)this.transform.position, radius, collectMask);
        foreach (var hitCollider in hitColliders)
        {
    
    
            var collect = hitCollider.gameObject.GetComponent<Collect>();
            collect.callback = OnEat;
            if (!collect.isCollecting)
            {
    
    
                // 收集可收集物体
                collect.Eat(this.transform);
            }
        }
    }

    private GameObject CreateFollowPart(Transform followTarget)
    {
    
    
        // 创建跟随部件
        var spaceBetween = 2f;
        var bodyPartsInit = Instantiate(bodyParts);
        var bodyPart = Instantiate(followPartPref, FollowPosition(followTarget, spaceBetween), Quaternion.identity);
        bodyPart.transform.parent = bodyPartsInit.transform;
        BodyPart bodyPartComponent = bodyPart.GetComponent<BodyPart>();
        bodyPartComponent.FollowTarget = followTarget;
        bodyPartComponent.SpaceBetween = spaceBetween;
        bodyPart.transform.parent  = GO;
        onSpeedChangeDelegate += bodyPartComponent.ChangeSpeed;
        return bodyPart;
    }

    private Vector3 FollowPosition(Transform target, float spaceBetween)
    {
    
    
        // 计算跟随部件的位置
        var position = target.position;
        return position - target.right * spaceBetween;
    }

    private void OnEat()
    {
    
    
        // 当吃到食物时创建新的跟随部件
        if (count > 0)
        {
    
    
            Transform previousPart = GO.GetChild(count - 1);
            CreateFollowPart(previousPart);
            count += 1;
        }
        else
        {
    
    
            Debug.Log("WITHOUT");
        }
    }

    private void NormalBulletCollision(Vector2 position)
    {
    
    
        // 生成普通食物
        GameObject bulletCollision = (GameObject)Instantiate(normalBulletCollisionPref.gameObject, position, Quaternion.identity);
        bulletCollision.transform.parent = cleanBucket.transform;
        var collider = bulletCollision.GetComponent<Collider2D>();
        allColliders.Add(collider);
    }
}

Guess you like

Origin blog.csdn.net/kokool/article/details/132568014