Unity Game Demo Production Dungeon II

        This article roughly optimizes the logic of the room doors and walls, then adds the logic of the characters, and then adds the switch of the camera position while switching the room. Part of the logic also refers to the materials of other predecessors.

        The first is the room door, because it is difficult to find free materials, and the game itself pays tribute to Isaac, so it directly uses Isaac’s art resources (the pixel style horizontal version has more materials, and there will be materials such as jumping, but 2.5D not very useful).

        Let’s talk about the character logic first. I did the most basic movement and animation. When using resources, I found that the head and body of Isaac’s character are separated (and the head has many different materials, because eating different props will change the shape), Because its attack logic is to shoot bullets in the corresponding direction with the up, down, left, and right keys, and then the moving direction and attacking direction can be separated, and the bullets fired by the attack will have a physics engine (can be thrown, and the trajectory is not horizontal, it will fall in a parabolic form), So you can create a prefab, name it player, then drag the head and body as sub-objects, and then mount Animator on them to make different animations. 

        The animation state machine of the body is switched as follows. Up and down can be switched to left and right, but left and right cannot be switched to up and down because of walking diagonally. Although the displacement is diagonally moving when walking diagonally, the animation still uses left-right moving animation. If both If you can freely switch animations, you will twitch. It depends on your personal performance needs. Here, pay attention to uncheck the exit time of animation switching, and reset the lower attributes to zero, otherwise animation switching will be delayed, which looks very uncomfortable.

        You can switch to left and right or up and down movement by standing mode. Up and down movement does not need to be distinguished. Left and right movement can be judged by the positive or negative of the input moveInput.x, and the display on the screen can be processed by switching filp.x in Spirte Renerder.

    void UpdateMovement()
    {
        var h = Input.GetAxis("Horizontal");
        var v = Input.GetAxis("Vertical");
        moveInput = h * Vector2.right + v * Vector2.up;
        //归一化,这样斜着走的速度不会超过移动速度。
        if (moveInput.magnitude > 1f)
        {
            moveInput.Normalize();
        }
        myRigidbody.velocity = moveInput * Speed * Time.deltaTime;
    }

    void UpdateAnimator()
    {
        //身体左右翻转
        if (moveInput.x < 0) { body.flipX = true; }
        if (moveInput.x > 0) { body.flipX = false; }
        bodyAnimation.SetFloat("UpAndDown", Mathf.Abs(moveInput.y));
        bodyAnimation.SetFloat("RightAndLeft", Mathf.Abs(moveInput.x));
    }

         The animation logic of the head is similar, even simpler. This involves the logic of bullet firing. When the arrow keys in different directions are pressed, the animation is triggered, so you only need to press the corresponding key to modify the bool value to trigger the animation. .

         Then there is the logic of the room and the door, because this material is a whole background, the floor and the wall are connected, and the door is another material, so we directly create a basic room and paste the doors on the four sides, and judge through the code The doors in which directions should be displayed. The logic has been mentioned in the previous article here, so I won’t go into details. I will mainly talk about the collider settings in the room.

        Collider must be set on the wall, otherwise the player can go through it directly, and then the structure of the door is a little more complicated, divided into no door (with collider), closed with door (with collider), open with door (with trigger device), after sorting it out, you can write the enable state for different situations, and then when there is a door and it is opened, an additional event will be triggered:

        Through the trigger on the player, judge whether the tag is a door, and then judge the name of the trigger to call the switching room logic in the room manager (the GameManager class is set here to store the commonly used management classes in the game, such as the overall room manager , Character Manager, etc.).

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(isControllable && collision.transform.CompareTag("MoveDirection"))
        {
            if(collision.transform.name == "Door_Left")
            {
                GameManager.Instance.roomGenerator.MoveToNextRoom(Vector2.left);
            }
            if (collision.transform.name == "Door_Right")
            {
                GameManager.Instance.roomGenerator.MoveToNextRoom(Vector2.right);
            }
            if (collision.transform.name == "Door_Up")
            {
                GameManager.Instance.roomGenerator.MoveToNextRoom(Vector2.up);
            }
            if (collision.transform.name == "Door_Down")
            {
                GameManager.Instance.roomGenerator.MoveToNextRoom(Vector2.down);
            }
        }
    }

         Due to the material, the method of simply switching the camera position originally considered is not suitable (specifically, to install a trigger for each room, and when the trigger is triggered, the transform of the corresponding room will be transmitted), but this material is not between the room and the room. The straight-through is to jump from point B to point A, so not only the camera needs to be switched, but also the position of the character needs to be switched. Using the previous method requires the character to go to the black connection area to switch the map, which is obviously unreasonable, so here you can Use the door's trigger to determine if the room is crossed (code above).

         The reason why the coroutine is used here is that in theory, the player cannot move within a few tenths of a second when switching rooms, and then process information such as the generated objects in the room. Currently, there is not much processing content, so there is no need to wait for the next frame after the map is processed. Call it again, but if you add room loading information later, you may need to wait for one frame.

    public void MoveToNextRoom(Vector2 MoveDirection)
    {
        StartCoroutine(MoveToDesignativetRoom(MoveDirection));
    }

    private IEnumerator MoveToDesignativetRoom(Vector2 MoveDirection)
    {
        Camera mainCamera = GameManager.Instance.myCamera;
        Transform player = GameManager.Instance.playerPrefab.transform;

        if (MoveDirection == Vector2.right)
        {
            Vector3 originPos = mainCamera.transform.position;
            Vector3 targetPos = mainCamera.transform.position;
            targetPos.x = mainCamera.transform.position.x + xOffset;
            mainCamera.transform.position = Vector3.Lerp(originPos, targetPos, 5f);
            player.transform.position = new Vector3(player.position.x + playerXOffset, player.position.y, player.position.z);
        }
        else if (MoveDirection == Vector2.left)
        {
            Vector3 originPos = mainCamera.transform.position;
            Vector3 targetPos = mainCamera.transform.position;
            targetPos.x = mainCamera.transform.position.x - xOffset;
            mainCamera.transform.position = Vector3.Lerp(originPos, targetPos, 5f);
            player.position = new Vector3(player.position.x - playerXOffset, player.position.y, player.position.z);
        }
        else if (MoveDirection == Vector2.up)
        {
            Vector3 originPos = mainCamera.transform.position;
            Vector3 targetPos = mainCamera.transform.position;
            targetPos.y = mainCamera.transform.position.y + yOffset;
            mainCamera.transform.position = Vector3.Lerp(originPos, targetPos, 5f);
            player.position = new Vector3(player.position.x, player.position.y + playerYOffset, player.position.z);
        }
        else if (MoveDirection == Vector2.down)
        {
            Vector3 originPos = mainCamera.transform.position;
            Vector3 targetPos = mainCamera.transform.position;
            targetPos.y = mainCamera.transform.position.y - yOffset;
            mainCamera.transform.position = Vector3.Lerp(originPos, targetPos, 5f);
            player.position = new Vector3(player.position.x, player.position.y - playerYOffset, player.position.z);
        }
        yield return null;
    }

        The last is the logic of firing bullets. Memory is optimized through the object pool. In short, it is to judge whether there are bullets in the object pool. If there are bullets in the object pool, use the bullets in the object pool directly. If not, generate a bullet.

public class BulletPools : MonoBehaviour
{

    public GameObject bulletPrefab;
    Queue<GameObject> pool = new Queue<GameObject>();

    public GameObject Take()
    {
        if (pool.Count > 0)
        {
            GameObject instanceToReuse = pool.Dequeue();
            instanceToReuse.SetActive(true);
            return instanceToReuse;
        }
        return Instantiate(bulletPrefab);
    }

    public void Back(GameObject gameObjectToPool)
    {
        pool.Enqueue(gameObjectToPool);
        gameObjectToPool.transform.SetParent(transform);
        gameObjectToPool.SetActive(false);
    }
}

        Then Isaac's bullets are affected by gravity, so the gravity can be increased after it is generated, and then the gravity can be reset to zero when he disappears.

    public void Initialization()
    {
        isDestory = false;
        damage = player.Damage;
        playerKnockback = player.Knockback;
        //子弹一段时间后触发自动销毁
        Invoke("AutoDestroy", player.Range * 0.03f);
    }

    /// <summary>
    /// 自动销毁
    /// </summary>
    void AutoDestroy()
    {
        if (isDestory) { return; }
        //子弹快速下落一小段时间后销毁
        myRigidbody.gravityScale = 1.7f;
        Invoke("Destroy", 0.13f);
    }

    /// <summary>
    /// 销毁
    /// </summary>
    void Destroy()
    {
        //关闭重力,停止移动,关闭碰撞体,播放消失动画,返回对象池
        isDestory = true;
        myRigidbody.gravityScale = 0;
        myRigidbody.velocity = Vector2.zero;
        myCollider.enabled = false;
        animator.Play("Destory");
        StartCoroutine(GoBackToPool());
    }

    /// <summary>
    /// 回收到对象池
    /// </summary>
    /// <returns></returns>
    IEnumerator GoBackToPool()
    {
        yield return WaitSeconds;
        transform.position = Vector2.zero;
        myCollider.enabled = true;
        animator.Play("Idle");
        animator.Update(0);
        bulletPool.Back(gameObject);
    }

        Finally, call the Take() method in the object pool on the character interface, and apply a force to the bullet in different directions input by the player. This can be adjusted according to your own needs and range. Currently, half of the completion of the bullet is completed. Objects that collide with different tags are classified.

Guess you like

Origin blog.csdn.net/weixin_45081191/article/details/129409426