【Unity2D游戏开发入门第一卷】✨Unity入门总结Sunnyland示例(上卷)

请添加图片描述
部分功能例如目录跳转,回到顶部功能在这里有问题

追求阅读体验可以转到 ✨本人主战场!✨

✨✨目录










一、入门卷

回到顶部

  1. 前言

  2. 准备资源

  3. Tilemap 地图布置,刚体组件

  4. 角色移动跳跃脚本,以及刚体,碰撞器等组件添加(包含射线检测,解决手感问题)

  5. 角色添加动画逻辑,以及动画组件(包含动画状态机设置)

  6. 相机跟随 Player 移动(简单代码实现)

  7. 游戏场景切换










[入门卷] 0. 前言

回到顶部

本卷以 Sunnyland 素材为例,简单总结了一下 Unity 在 2D 游戏制作方面的基本使用。










[入门卷] 1. 准备资源

回到顶部

新建2D项目 FirstGameSunnyland

A. 导入资源

1. 添加资源

菜单栏 Window -> Asset Store -> Search online 搜索 Sunnyland -> 添加至我的资源

2. 导入素材

-> 在 Unity 中打开 或者 在Unity编辑器中 菜单栏 Window -> Package Manager

-> 左上角 Packages 选择 My Assets -> 选中 Sunny Land Download -> Import

-> All, Import

B. 整理文件夹

我习惯在 Asset 文件夹中新建 _Game 文件夹,前缀 _ 按对应字典排序在前面,会出现在比较靠前的位置,这个不做强制要求。

在 _Game 中暂时新建 Animations,Prefabs,Scripts,Maps,Scenes,Materials 6个文件夹。

我还把外面的两个文件夹拖了进来

C. 资源格式设置

在游戏中一般都会使用相同的 Pixels Per Units,默认是 100,这里我使用 16,可以根据需求更改,不同资源格式可以不一样,在使用资源时注意更改即可。

D. 编辑器视图布局

我之前使用的是在默认布局基础上做了一点更改,可以在右上角,Layout 点击 Default 设置。

可以右键下方 Project 或 Console,Add Tab -> Project,我喜欢用两个 Project

下面再加几个常用的窗口:

  • Window -> 2D -> Tile Palette (可以拖拽到自己喜欢的位置)

  • Window -> Animation -> Animation(动画制作)

  • Window -> Animation -> Animator(动画状态机控制)

注意:没必要跟我一模一样,这只是我目前的习惯,你完全可以按照你的喜好来










[入门卷] 2. Tilemap 地图布置,Tilemap 碰撞体组件

回到顶部

A. Tilemap 地图布置

xxx/Sunnyland/artwork/Environment 下有一些资源。

  • (1). 背景

可以拖一张背景图片 back 到 Scene 场景中,重命名为 Background,在 Inspector 中,Transform 组件右上角 三点左击 Reset 可以重置 Transform 中的属性。这里将 Scale 设置为 (3,3,3),复制两份(可以手动复制,或者 Ctrl+D),按住 V 会出现锚点,拖动这个锚点往想去的那个地方拖拽(有类似吸附效果),将 3个背景稍微整齐排列。

  • (2). Tileset 瓦片集

资源自带的一个 tileset,已经切割好了,不过这里为了介绍基本使用,我们可以再切割一次

  • (3). 素材切割

点击 tileset,在 Inspector 中,将 Sprite Mode 改为 Multiple,点击 Sprite Editor 进行切割。

Slice -> Type 选中 Grid By Cell Size -> Pixel Size 改为 x16 y16(因为我将素材都改为了 16像素每单位) -> 最后点击 Slice 切割 -> Apply

  • (4). Sprite 图层 Layer


这里简单介绍一下图层的概念,Unity 中的图层渲染顺序是从上开始往下渲染的,也就是说越下面的图层越在上面,或者你也可以更改 Orider in Layer,数字越大越在上面

选择 Tilemap_BaseMap 这里在 Inspector -> xxx Renderer(如 Tilemap Renderer) 中的 Sorting Layer 中从上到下添加了 Background,Environment,Foreground

在 Hierarchy 窗口中右键创建空物体 Create Empty 重命名为 Background,来管理其他 3个背景,其他按格式 bg 重命名

选中 Background 在 Inspector 窗口中添加组件 Sprite Renderer,设置 Sorting LayerBackground

  • (5). 地图布置

  1. 这里好像自带了一个 Main Palette,可以暂时不用管他,在 Tile Palette 窗口中 Create New Palette,我将其命名为 BaseMap放入 _Game/Maps/BaseMap 中,将切割好的 tileset 素材拖入 Tile Palette 窗口对应的 Palette 中。

  1. 在 Hierarchy 窗口中,右键 2D Object -> Tilemap -> Rectangular,重命名 Tilemap 为 Tilemap_BaseMap。

  2. 接下来可以使用 Tile Palette 窗口中对应的 Palette 资源在刚刚创建的 Grid 中的 Tilemap 上绘制了,面板上面有很多工具,如笔刷,橡皮。注意:Tile Palette 窗口中 Active Tilemap 要选择需要绘制的 Tilemap

    你还可以选择 Edit 选项去编辑 Tile Palette 如果你去试试,会很简单

  3. 这里布置地图如下
    不过你完全可以按你的喜好来,不要让文章限制你的想法,我这里布置的比较随意,因为可能用不到这么大的地图,本卷主要以介绍基本使用和逻辑为主

2. Tilemap 碰撞体组件

为 Tilemap_BaseMap 添加 [复合] 碰撞体组件。

点击 Tilemap_BaseMap,在 Inspector 窗口下面,Add Component,搜索 Tilemap Collider 2D 并添加,这里勾选下面的 Used By Composite 选项(防止碰撞体之间的卡住现象,可以自行试一下,例如,有时角色冻结 Z 轴,移动会卡住,不冻结会是绕 Z轴旋转的现象),若勾选了此选项,还需要添加 Composite Collider 2D 组件但此时会自动添加 Rigidbody 2D,默认会有重力,这不是我们想要的,简单的可以将 Rigidbody 2D 中的 Body Type 改为 Static或将重力设为 0

将鼠标放在 Hierarchy 物体对象上左边会有小眼睛,点击可以隐藏该组件
你现在可以试试隐藏 Background 然后选择 Tilemap_BaseMap 查看刚刚设置的 复合 碰撞体组件了










[入门卷] 3. 角色移动跳跃脚本,以及刚体,碰撞器等组件添加(包含射线检测,解决手感问题)

回到顶部

在 …/Sunnyland/artwork/Sprites/player/idle 中找到 Player 的 “闲置状态” 素材,注意 Pixels Per Unit 的设置 这里统一 16。将 第一张素材图片拖入 Scene 中,或者在 hierarchy 中右键创建 2D Object -> Sprites -> Square 或者随便选一个,这里选的是 Square(这个只是设置 Renderer 中的 Sprite,后面要改,所以随便选择),将 Sprite 设置为第一张素材图片,可以拖拽,将 Sorting Layer 设置为 Foreground。

注意命名该对象为 Player

A. 刚体,碰撞器组件

  1. 添加 Rigidbody 2D【重力设置为 3】Capsule Collider 2D

  2. 在 Rigidbody 2D 中,设置 Collision Detection -> Continuous(让检测更频繁),Interpolate -> Interpolate(落地会有一点凹陷,然后恢复,模拟更真实的效果)。在 Constraints 中,勾选 Freeze Rotation,防止 Z 轴翻滚

  3. 在 Capsule Collider 2D 中,点击 Edit Collider 可以设置 碰撞体形状,设置成合适的大小。(如果看不清,可以先隐藏 背景 Background)

  4. 给 Player 添加材质。在 …/_Game/Materials/ 中右键 Create -> 2D -> Physics Material 2D,命名为 M_ZeroFriction,我一般喜欢将材质命名为 M 开头,将 Friction 设为 0。拖到 Capsule Collider 2D 的 Material 上

B. 按键设置

Edit -> Project Settings -> Input Manager -> Axes 展开

这里会用到里面自带 Horizontal 和 Jump

再添加一个 Crouch 按键用来触发 下蹲随便用鼠标右击一个按键例如 Jump -> Duplicate Array Element,复制一份,再更改 Name 为 Crouch,Positive Button 为 s,Alt Positive Button,是替代按键,暂时不用设置。

C. 为 Player 添加脚本组件(暂时不包括动画)

(1). 配置编辑器

✨ 配置 VsCode 参考链接

如果是 Visual Studio 例如(Visual Studio 2019)直接使用,不用配置,这里使用的是 VS 2019

Edit -> Preferences -> Analysis > External Tools -> External Script Editor

(2). 示例代码

  1. 在 Project 中右键 Create -> C# Script(拖到 Player 上添加组件),或者 直接在 Inspector 中添加。这里选择前者,后者会默认创建在 Assets 目录,还要手动移动到 Scripts 文件夹中,所以就直接右键在 指定文件夹中创建了。(双击组件属性 Script 或 脚本文件进入 Vscode 编写脚本)

  2. PlayerController.cs 移动示例代码如下(包括,水平移动,转向,跳跃,下蹲,射线检测)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    
    
    // --- Private ----------------------------------
    private Rigidbody2D m_Rb;                                   // Player 刚体组件
    private CapsuleCollider2D m_CapsuleCollider2D;              // 胶囊碰撞体组件

    // 检测相关
    [Header("【只能看不能改】")]
    [SerializeField] private bool m_BTurnDirection = false;     // Player 是否转向
    [SerializeField] private bool m_BDoJump = false;            // 是否执行跳跃相关
    [SerializeField] private bool m_BPressedCrouch = false;     // 是否按下 下蹲键
    [SerializeField] private bool m_BOnGround = false;          // 是否在地面
    [SerializeField] private int m_CurrentAllowAirJumpCount = 0;// 当前 允许跳空中跃次数
    [SerializeField] private Vector2 m_CapsuleCollider2DSize;   // 保存碰撞体 初始大小
    [SerializeField] private Vector2 m_CapsuleCollider2DOffset; // 保存碰撞体 初始偏移
    [SerializeField] private bool m_BTopHasWall = false;        // 上方是否有墙

    // --- Public -----------------------------------
    [Header("【移动参数】")]
    public float m_HorizontalSpeedFactorPerSecond = 300.0f;     // 水平移动速度
    public float m_JumpSpeedPerSecond = 520.0f;                 // 跳跃速度
    public int m_AllowAirJumpCount = 1;                         // 允许跳空中跃次数

    [Header("【检测相关】")]
    public LayerMask m_LMGround;                                // 地面图层蒙版
    public float m_CheckGround_Left_XOffset = -0.4f;            // 地面检测 射线 x 左偏移
    public float m_CheckGround_Right_XOffset = 0.2f;            // 地面检测 射线 x 右偏移
    public float m_CheckGround_YOffset = -0.95f;                // 地面检测 射线 y 偏移
    public float m_CheckGround_Distance = 0.1f;                 // 地面检测 射线发射距离
    public float m_CheckTopHasWall_Distance = 0.4f;             // 检测上方是否右墙 射线 距离
    public float m_CheckTopHasWall_Left_XOffset = -0.4f;        // 地面检测 射线 x 左偏移
    public float m_CheckTopHasWall_Right_XOffset = 0.35f;       // 地面检测 射线 x 右偏移

    // Start is called before the first frame update
    private void Start()
    {
    
    
        // 获取组件
        m_Rb = GetComponent<Rigidbody2D>();
        m_CapsuleCollider2D = GetComponent<CapsuleCollider2D>();

        // 设置参数
        m_CurrentAllowAirJumpCount = m_AllowAirJumpCount;
        m_CapsuleCollider2DSize = m_CapsuleCollider2D.size;
        m_CapsuleCollider2DOffset = m_CapsuleCollider2D.offset;
    }

    private void FixedUpdate()
    {
    
    
        Move();
    }

    // Update is called once per frame
    private void Update()
    {
    
    
        Check();
    }

    // --- public -------------------------------------------

    /// <summary>
    /// @breif 移动
    /// </summary>
    public void Move()
    {
    
    
        HorizontalMove();   // 水平移动
        Jump();             // 跳跃
        Crouch();           // 下蹲
    }
    
    /// <summary>
    /// @brief 检测部分
    /// </summary>
    public void Check()
    {
    
    
        CheckInput();       // 输入检测
        CheckOnGround();    // 检测是否在地面
        CheckTopHasWall();  // 检测头上是否有墙壁
    }

    // --- private ------------------------------------------

    /// <summary>
    /// @brief 输入检测
    /// </summary>
    private void CheckInput()
    {
    
    
        // 检测跳跃键【GetButtonDown 这个函数,一直按下也只会算一次,需要松开再按才算下一次】
        //Debug.LogWarning(Input.GetButtonDown("Jump"));
        if (Input.GetButtonDown("Jump") && m_CurrentAllowAirJumpCount > 0)
        {
    
    
            // 如果可以跳跃,执行跳跃相关
            m_BDoJump = true;
        }
        // 检测下蹲键
        m_BPressedCrouch = Input.GetButton("Crouch");
    }

    /// <summary>
    /// @brief 检测上方是否有墙壁
    /// </summary>
    private void CheckTopHasWall()
    {
    
    
        float yOffset = -0.2f;  // 检测上方是否有墙壁的偏移量
        // 射线起点
        Vector2 leftStart2 = new Vector2(transform.position.x + m_CheckTopHasWall_Left_XOffset, transform.position.y + yOffset);
        Vector2 rightStart2 = new Vector2(transform.position.x + m_CheckTopHasWall_Right_XOffset, transform.position.y + yOffset);
        // 射线方向
        Vector2 direction2 = Vector2.up;
#if DEBUG  // 调试用变量
        Vector3 leftStart3 = new Vector3(transform.position.x + m_CheckTopHasWall_Left_XOffset, transform.position.y + yOffset, 0.0f);
        Vector3 rightStart3 = new Vector3(transform.position.x + m_CheckTopHasWall_Right_XOffset, transform.position.y + yOffset, 0.0f);
        Vector3 direction3 = Vector3.up;
#endif
        // 射线持续时间(Debug)
        float durationTime = 0.0f;
        RaycastHit2D leftHitResult = Physics2D.Raycast(leftStart2, direction2, m_CheckTopHasWall_Distance, m_LMGround);
        RaycastHit2D rightHitResult = Physics2D.Raycast(rightStart2, direction2, m_CheckTopHasWall_Distance, m_LMGround);

        if (leftHitResult || rightHitResult)
        {
    
    
            m_BTopHasWall = true;
            // Debug
#if DEBUG
            Debug.DrawLine(leftStart3, leftStart3 + direction3 * m_CheckTopHasWall_Distance, Color.green, durationTime);  // 绿
            Debug.DrawLine(rightStart3, rightStart3 + direction3 * m_CheckTopHasWall_Distance, Color.green, durationTime);  // 绿
#endif
        }
        else
        {
    
    
            m_BTopHasWall = false;
            // Debug
#if DEBUG
            Debug.DrawLine(leftStart3, leftStart3 + direction3 * m_CheckTopHasWall_Distance, Color.red, durationTime);  // 红
            Debug.DrawLine(rightStart3, rightStart3 + direction3 * m_CheckTopHasWall_Distance, Color.red, durationTime);  // 红
#endif
        }
    }

    /// <summary>
    /// @brief 检测是否在地面上
    /// </summary>
    private void CheckOnGround()
    {
    
    
        // 射线起点
        Vector2 leftStart2 = new Vector2(transform.position.x + m_CheckGround_Left_XOffset, transform.position.y + m_CheckGround_YOffset);
        Vector2 rightStart2 = new Vector2(transform.position.x + m_CheckGround_Right_XOffset, transform.position.y + m_CheckGround_YOffset);
        Vector2 leftTurnDirectionStart2 = new Vector2(leftStart2.x + 0.18f, leftStart2.y);
        Vector2 rightTurnDirectionStart2 = new Vector2(rightStart2.x + 0.22f, rightStart2.y);
#if DEBUG
        Vector3 leftStart3 = new Vector3(leftStart2.x, leftStart2.y, 0.0f);
        Vector3 rightStart3 = new Vector3(rightStart2.x, rightStart2.y, 0.0f);
        Vector3 leftTurnDirectionStart3 = new Vector3(leftTurnDirectionStart2.x, leftTurnDirectionStart2.y, 0.0f);
        Vector3 rightTurnDirectionStart3 = new Vector3(rightTurnDirectionStart2.x, rightTurnDirectionStart2.y, 0.0f);
#endif
        // 射线方向
        Vector2 direction2 = Vector2.down;
        Vector3 direction3 = Vector3.down;
        // 射线持续时间(Debug)
        float durationTime = 0.0f;
        // 射线结果,该结构体也有重写 bool operator,所以可以直接用来判断
        RaycastHit2D leftHitResult;
        RaycastHit2D rightHitResult;
        if (m_BTurnDirection)  // 如果转向了
        {
    
    
            leftHitResult = Physics2D.Raycast(leftTurnDirectionStart2, direction2, m_CheckGround_Distance, m_LMGround);
            rightHitResult = Physics2D.Raycast(rightTurnDirectionStart2, direction2, m_CheckGround_Distance, m_LMGround);
        }
        else  // 如果没转向
        {
    
    
            leftHitResult = Physics2D.Raycast(leftStart2, direction2, m_CheckGround_Distance, m_LMGround);
            rightHitResult = Physics2D.Raycast(rightStart2, direction2, m_CheckGround_Distance, m_LMGround);
        }

        if (leftHitResult.collider || rightHitResult.collider)
        {
    
    
            // 在地面上
            m_BOnGround = true;
            // 重置空中跳跃次数
            m_CurrentAllowAirJumpCount = m_AllowAirJumpCount;
            // 调试,击中为绿色
#if DEBUG
            if (m_BTurnDirection)
            {
    
    
                Debug.DrawLine(leftTurnDirectionStart3, leftTurnDirectionStart3 + direction3 * m_CheckGround_Distance, Color.green, durationTime);
                Debug.DrawLine(rightTurnDirectionStart3, rightTurnDirectionStart3 + direction3 * m_CheckGround_Distance, Color.green, durationTime);
            }
            else
            {
    
    
                Debug.DrawLine(leftStart3, leftStart3 + direction3 * m_CheckGround_Distance, Color.green, durationTime);
                Debug.DrawLine(rightStart3, rightStart3 + direction3 * m_CheckGround_Distance, Color.green, durationTime);
            }
#endif
        }
        else
        {
    
    
            // 不在地面上
            m_BOnGround = false;
            // 调试
#if DEBUG
            if (m_BTurnDirection)
            {
    
    
                Debug.DrawLine(leftTurnDirectionStart3, leftTurnDirectionStart3 + direction3 * m_CheckGround_Distance, Color.red, durationTime);
                Debug.DrawLine(rightTurnDirectionStart3, rightTurnDirectionStart3 + direction3 * m_CheckGround_Distance, Color.red, durationTime);
            }
            else
            {
    
    
                Debug.DrawLine(leftStart3, leftStart3 + direction3 * m_CheckGround_Distance, Color.red, durationTime);
                Debug.DrawLine(rightStart3, rightStart3 + direction3 * m_CheckGround_Distance, Color.red, durationTime);
            }
#endif
        }
    }

    /// <summary>
    /// @brief 水平移动
    /// </summary>
    private void HorizontalMove()
    {
    
    
        // 获取水平输入 [-1, 1],松开按键时为 0
        float horizontalValue = Input.GetAxis("Horizontal");
        m_Rb.velocity = new Vector2(horizontalValue * m_HorizontalSpeedFactorPerSecond * Time.fixedDeltaTime, m_Rb.velocity.y);

        // Player 转向
        if (horizontalValue < 0.0f)  // 默认是右边
        {
    
    
            m_BTurnDirection = true;
            transform.rotation = new Quaternion(0.0f, 180.0f, 0.0f, 1.0f);
        }
        else if (horizontalValue > 0.0f)
        {
    
    
            m_BTurnDirection = false;
            transform.rotation = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
        }
    }

    /// <summary>
    /// @brief 跳跃
    /// </summary>
    private void Jump()
    {
    
    
        if (m_BOnGround && m_BDoJump)
        {
    
    
            m_BDoJump = false;
            m_Rb.velocity = new Vector2(m_Rb.velocity.x, m_JumpSpeedPerSecond * Time.fixedDeltaTime);
        }
        if (!m_BOnGround && m_CurrentAllowAirJumpCount > 0 && m_BDoJump)
        {
    
    
            // 空中跳跃
            m_BDoJump = false;
            --m_CurrentAllowAirJumpCount;
            m_Rb.velocity = new Vector2(m_Rb.velocity.x, m_JumpSpeedPerSecond * Time.fixedDeltaTime);
        }
    }

    /// <summary>
    /// @brief 下蹲
    /// </summary>
    private void Crouch()
    {
    
    
        if (m_BPressedCrouch)
        {
    
    
            m_CapsuleCollider2D.size = new Vector2(m_CapsuleCollider2D.size.x, 0.6f);
            m_CapsuleCollider2D.offset = new Vector2(m_CapsuleCollider2D.offset.x, -0.545f);
        }
        else if (!m_BTopHasWall)  // 上方如果检测到有墙不能起来
        {
    
    
            m_CapsuleCollider2D.size = m_CapsuleCollider2DSize;
            m_CapsuleCollider2D.offset = m_CapsuleCollider2DOffset;
        }
    }
}

添加一个 Layer 命名为 Ground,不要忘记将 Tilemap_BaseMap 的图层 Layer 设置为 Ground

编辑器参数设置

如果脚本更改,一般需要刷新组件才能更新编辑器中的数值显示

这里应该只需要修改图层为 Ground

  • (3). 特别注意

常见的是跳跃手感问题,代码逻辑不当会让玩家感觉跳跃按键不灵。

简单说一下这里使用的一些手段,其他代码应该挺简单的,结合注释应该很好阅读

1.** 将检测部分放入 Update 中,将与刚体运动相关的放入 FixedUpdate 中。**

  1. 代码逻辑,参考如下(从上方摘取)【这里指出关键代码让大家体会】

    检测部分放入 Update 中

     [SerializeField] private bool m_BDoJump = false;            // 是否执行跳跃相关
    
     private void CheckInput()
     {
          
          
         // 检测跳跃键【GetButtonDown 这个函数,一直按下也只会算一次,需要松开再按才算下一次】
         //Debug.LogWarning(Input.GetButtonDown("Jump"));
         if (Input.GetButtonDown("Jump") && m_CurrentAllowAirJumpCount > 0)
         {
          
          
             // 如果可以跳跃,执行跳跃相关
             m_BDoJump = true;
         }
         // 检测下蹲键
         m_BPressedCrouch = Input.GetButton("Crouch");
     }
    

    与刚体运动相关的放入 FixedUpdate 中

     public float m_JumpSpeedPerSecond = 520.0f;                 // 跳跃速度
     
     private void Jump()
     {
          
          
         if (m_BOnGround && m_BDoJump)
         {
          
          
             m_BDoJump = false;
             m_Rb.velocity = new Vector2(m_Rb.velocity.x, m_JumpSpeedPerSecond * Time.fixedDeltaTime);
         }
         if (!m_BOnGround && m_CurrentAllowAirJumpCount > 0 && m_BDoJump)
         {
          
          
             // 空中跳跃
             m_BDoJump = false;
             --m_CurrentAllowAirJumpCount;
             m_Rb.velocity = new Vector2(m_Rb.velocity.x, m_JumpSpeedPerSecond * Time.fixedDeltaTime);
         }
     }
    
  • (4). Unity 函数生命周期参考

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DhUIlIkx-1670755939902)(https://img2023.cnblogs.com/blog/2805185/202212/2805185-20221211184019686-591164807.svg)]










[入门卷] 4. 角色添加动画逻辑,以及动画组件(包含动画状态机设置)

回到顶部

  1. 为 Player 添加 Animator 组件(或者先不添加),然后在 …/_Game/Animations/Player 中 右键 Create -> Animator Controller,这里命名为 AC_Player 并拖入 Hierarchy 面板上的 Player 对象中 或者 拖入 Player 的 Inspector -> Animator -> Controller 上。

  2. 在此目录下创建动画,在 Animation 窗口中,点击右下框的 Create,创建动画,这里命名为 Idling.anim,创建一次后即可在 左边选择动画列表中(例如这边叫做 Player,左键点一下就会弹出下拉菜单),下方点击 Create New Clip 创建动画暂时创建 Idling(刚刚创建了,不用创建了),RunningJumpingFallingCrouching 5个动画片段。

注意:将素材设置为统一的格式,然后这里 Pixels Per Unit 设置的是 16 !

  1. 把相应的动画素材全选拖入 Animation 右边时间轴窗口。点击右边第二行三点,Show Sample Rate (我比较喜欢用),这个可以调节频率,按自己喜好调节 (我这里Idling,Running,Crouching 分别调节的是 10, 10, 7)Jumping 和 Falling 只有一张,我将 Samples 设置了 1,不知道是否能改善性能呢?红色录制按钮右边还有播放按钮,非常方便。考虑本卷主要介绍基本使用,所有不弄太复杂,之后可能会单独出模块介绍。

  1. 设置 Animator。(也可以不使用 Unity 编辑器中的动画状态机,可以在代码操作,但是这次使用的是动画状态机)

右键单个动画片段,可以 Make Transition 然后关联(连线)动画片段。

动画控制器视图参考

✨ A. 动画控制器属性设置如下

【这里可以耐心配置一下,截图会占太多篇幅,所以可能需要大家稍微花点时间过一下,挺快的,我都打出来了,哈哈】

(1). 参数设置

Inspector 面板中,右下角有 Conditions List 条件列表,有 +- 可以操作 CRUD 列表

Idling -> Running
Conditions: HorizontalSpeedPerSecond > 0.1
Idling <- Running
Conditions: HorizontalSpeedPerSecond < 0.1

Running -> Jumping
Conditions: BJumping = true;

Jumping -> Falling
Conditions: BFalling = true;
Falling -> Jumping
Conditions: BJumping = true, BFalling = false;

Falling -> Idling
Conditions: BFalling = false, BIdling = true;
Idling -> Falling
Conditions: BFalling = true;

Idling -> Crouching
Conditions: BCrouching = true;
Crouching -> Idling
Conditions: BCrouching = false, BIdling = true;

Running -> Crouching
Conditions: BCrouching = true;
Crouching -> Running
Conditions: BCrouching = false, HorizontalSpeedPerSec > 0.1;

Crouching -> Jumping
Conditions: BCrouching = false, BJumping = true;
Falling -> Crouching
Conditions: BCrouching = true, BFalling = true;

Idling -> Jumping
Conditions: BJumping = true;
Jumping -> Idling
Conditions: BIdling = true, BFalling = false;

(2). 其他设置

把所有动画 Inspector 中,Has Exit Time 取消勾选,Settings -> Transition Duration(s) = 0。

但是 Idling -> Flling,Idling -> Jumping,Transition Duration(s) = 0.1,防止警告。

B. 添加脚本,加入动画控制逻辑,如下

  • 添加组件
private Animator _Animator;                                 // Animator
  • 在按键检测注释下面添加
// 检测动画状态
[SerializeField] private bool m_BAnimIdling = true;         // 闲置状态
[SerializeField] private bool m_BAnimRunning = false;       // 跑动状态
[SerializeField] private bool m_BAnimJumping = false;       // 跳跃状态
[SerializeField] private bool m_BAnimFalling = false;       // 下落状态
[SerializeField] private bool m_BAnimCrouching = false;     // 下蹲状态
  • Start() 获取组件下面添加
m_Animator = GetComponent<Animator>();
  • 添加方法
/// <summary>
/// @brief 动画控制
/// </summary>
public void AnimatorControl()
{
    
    
    m_Animator.SetFloat("HorizontalSpeedPerSecond", Mathf.Abs(m_Rb.velocity.x));
    m_Animator.SetBool("BIdling", m_BAnimIdling);
    m_Animator.SetBool("BJumping", m_BAnimJumping);
    m_Animator.SetBool("BFalling", m_BAnimFalling);
    m_Animator.SetBool("BCrouching", m_BAnimCrouching);
}

/// <summary>
/// 检测状态
/// </summary>
private void CheckState()
{
    
    
    // 跑动
    m_BAnimRunning = (Mathf.Abs(m_Rb.velocity.x) > 0.1f && !m_BAnimCrouching && !m_BAnimJumping && !m_BAnimFalling);
    // 下落
    m_BAnimFalling = m_Rb.velocity.y < -0.1f;
    // 跳跃
    if ((m_BOnGround && m_BDoJump) || (!m_BOnGround && m_AllowAirJumpCount > 0 && m_BDoJump))
    {
    
    
        m_BAnimJumping = true;
    }
    else if (m_BAnimFalling)
    {
    
    
        m_BAnimJumping = false;
    }
    // 下蹲
    m_BAnimCrouching = (m_BOnGround && ((m_BPressedCrouch && (!m_BAnimJumping || !m_BAnimFalling)) || m_BTopHasWall));
    // 闲置
    m_BAnimIdling = (m_BOnGround && !m_BAnimCrouching && !m_BAnimFalling);
}

AnimatorControl() 添加到 Update() 中,CheckState() 添加到 Check() 中

C. 添加动画逻辑后的完整代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    
    
    // --- Private ----------------------------------
    private Rigidbody2D m_Rb;                                   // Player 刚体组件
    private CapsuleCollider2D m_CapsuleCollider2D;              // 胶囊碰撞体组件
    private Animator m_Animator;                                // 动画控制器

    /** 检测相关 */
    [Header("【只能看不能改】")]
    [SerializeField] private bool m_BTurnDirection = false;     // Player 是否转向
    [SerializeField] private bool m_BDoJump = false;            // 是否执行跳跃相关
    [SerializeField] private bool m_BPressedCrouch = false;     // 是否按下 下蹲键
    [SerializeField] private bool m_BOnGround = false;          // 是否在地面
    [SerializeField] private int m_CurrentAllowAirJumpCount = 0;// 当前 允许跳空中跃次数
    [SerializeField] private Vector2 m_CapsuleCollider2DSize;   // 保存碰撞体 初始大小
    [SerializeField] private Vector2 m_CapsuleCollider2DOffset; // 保存碰撞体 初始偏移
    [SerializeField] private bool m_BTopHasWall = false;        // 上方是否有墙
    // 检测动画状态
    [SerializeField] private bool m_BAnimIdling = true;         // 闲置状态
    [SerializeField] private bool m_BAnimRunning = false;       // 跑动状态
    [SerializeField] private bool m_BAnimJumping = false;       // 跳跃状态
    [SerializeField] private bool m_BAnimFalling = false;       // 下落状态
    [SerializeField] private bool m_BAnimCrouching = false;     // 下蹲状态

    // --- Public -----------------------------------
    [Header("【移动参数】")]
    public float m_HorizontalSpeedFactorPerSecond = 300.0f;     // 水平移动速度
    public float m_JumpSpeedPerSecond = 520.0f;                 // 跳跃速度
    public int m_AllowAirJumpCount = 1;                         // 允许跳空中跃次数

    [Header("【检测相关】")]
    public LayerMask m_LMGround;                                // 地面图层蒙版
    public float m_CheckGround_Left_XOffset = -0.4f;            // 地面检测 射线 x 左偏移
    public float m_CheckGround_Right_XOffset = 0.2f;            // 地面检测 射线 x 右偏移
    public float m_CheckGround_YOffset = -0.95f;                // 地面检测 射线 y 偏移
    public float m_CheckGround_Distance = 0.1f;                 // 地面检测 射线发射距离
    public float m_CheckTopHasWall_Distance = 0.4f;             // 检测上方是否右墙 射线 距离
    public float m_CheckTopHasWall_Left_XOffset = -0.4f;        // 地面检测 射线 x 左偏移
    public float m_CheckTopHasWall_Right_XOffset = 0.35f;       // 地面检测 射线 x 右偏移

    // Start is called before the first frame update
    private void Start()
    {
    
    
        // 获取组件
        m_Rb = GetComponent<Rigidbody2D>();
        m_CapsuleCollider2D = GetComponent<CapsuleCollider2D>();
        m_Animator = GetComponent<Animator>();

        // 设置参数
        m_CurrentAllowAirJumpCount = m_AllowAirJumpCount;
        m_CapsuleCollider2DSize = m_CapsuleCollider2D.size;
        m_CapsuleCollider2DOffset = m_CapsuleCollider2D.offset;
    }

    private void FixedUpdate()
    {
    
    
        Move();
    }

    // Update is called once per frame
    private void Update()
    {
    
    
        Check();            // 检测
        AnimatorControl();  // 动画控制
    }

    // --- public -------------------------------------------

    /// <summary>
    /// @breif 移动
    /// </summary>
    public void Move()
    {
    
    
        HorizontalMove();   // 水平移动
        Jump();             // 跳跃
        Crouch();           // 下蹲
    }
    
    /// <summary>
    /// @brief 检测部分
    /// </summary>
    public void Check()
    {
    
    
        CheckInput();       // 输入检测
        CheckOnGround();    // 检测是否在地面
        CheckTopHasWall();  // 检测头上是否有墙壁
        CheckState();
    }

    /// <summary>
    /// @brief 动画控制
    /// </summary>
    public void AnimatorControl()
    {
    
    
        m_Animator.SetFloat("HorizontalSpeedPerSecond", Mathf.Abs(m_Rb.velocity.x));
        m_Animator.SetBool("BIdling", m_BAnimIdling);
        m_Animator.SetBool("BJumping", m_BAnimJumping);
        m_Animator.SetBool("BFalling", m_BAnimFalling);
        m_Animator.SetBool("BCrouching", m_BAnimCrouching);
    }

    // --- private ------------------------------------------

    /// <summary>
    /// 检测状态
    /// </summary>
    private void CheckState()
    {
    
    
        // 跑动
        m_BAnimRunning = (Mathf.Abs(m_Rb.velocity.x) > 0.1f && !m_BAnimCrouching && !m_BAnimJumping && !m_BAnimFalling);
        // 下落
        m_BAnimFalling = m_Rb.velocity.y < -0.1f;
        // 跳跃
        if ((m_BOnGround && m_BDoJump) || (!m_BOnGround && m_AllowAirJumpCount > 0 && m_BDoJump))
        {
    
    
            m_BAnimJumping = true;
        }
        else if (m_BAnimFalling)
        {
    
    
            m_BAnimJumping = false;
        }
        // 下蹲
        m_BAnimCrouching = (m_BOnGround && ((m_BPressedCrouch && (!m_BAnimJumping || !m_BAnimFalling)) || m_BTopHasWall));
        // 闲置
        m_BAnimIdling = (m_BOnGround && !m_BAnimCrouching && !m_BAnimFalling);
    }

    /// <summary>
    /// @brief 输入检测
    /// </summary>
    private void CheckInput()
    {
    
    
        // 检测跳跃键【GetButtonDown 这个函数,一直按下也只会算一次,需要松开再按才算下一次】
        //Debug.LogWarning(Input.GetButtonDown("Jump"));
        if (Input.GetButtonDown("Jump") && m_CurrentAllowAirJumpCount > 0)
        {
    
    
            // 如果可以跳跃,执行跳跃相关
            m_BDoJump = true;
        }
        // 检测下蹲键
        m_BPressedCrouch = Input.GetButton("Crouch");
    }

    /// <summary>
    /// @brief 检测上方是否有墙壁
    /// </summary>
    private void CheckTopHasWall()
    {
    
    
        float yOffset = -0.2f;  // 检测上方是否有墙壁的偏移量
        // 射线起点
        Vector2 leftStart2 = new Vector2(transform.position.x + m_CheckTopHasWall_Left_XOffset, transform.position.y + yOffset);
        Vector2 rightStart2 = new Vector2(transform.position.x + m_CheckTopHasWall_Right_XOffset, transform.position.y + yOffset);
        // 射线方向
        Vector2 direction2 = Vector2.up;
#if DEBUG  // 调试用变量
        Vector3 leftStart3 = new Vector3(transform.position.x + m_CheckTopHasWall_Left_XOffset, transform.position.y + yOffset, 0.0f);
        Vector3 rightStart3 = new Vector3(transform.position.x + m_CheckTopHasWall_Right_XOffset, transform.position.y + yOffset, 0.0f);
        Vector3 direction3 = Vector3.up;
#endif
        // 射线持续时间(Debug)
        float durationTime = 0.0f;
        RaycastHit2D leftHitResult = Physics2D.Raycast(leftStart2, direction2, m_CheckTopHasWall_Distance, m_LMGround);
        RaycastHit2D rightHitResult = Physics2D.Raycast(rightStart2, direction2, m_CheckTopHasWall_Distance, m_LMGround);

        if (leftHitResult || rightHitResult)
        {
    
    
            m_BTopHasWall = true;
            // Debug
#if DEBUG
            Debug.DrawLine(leftStart3, leftStart3 + direction3 * m_CheckTopHasWall_Distance, Color.green, durationTime);  // 绿
            Debug.DrawLine(rightStart3, rightStart3 + direction3 * m_CheckTopHasWall_Distance, Color.green, durationTime);  // 绿
#endif
        }
        else
        {
    
    
            m_BTopHasWall = false;
            // Debug
#if DEBUG
            Debug.DrawLine(leftStart3, leftStart3 + direction3 * m_CheckTopHasWall_Distance, Color.red, durationTime);  // 红
            Debug.DrawLine(rightStart3, rightStart3 + direction3 * m_CheckTopHasWall_Distance, Color.red, durationTime);  // 红
#endif
        }
    }

    /// <summary>
    /// @brief 检测是否在地面上
    /// </summary>
    private void CheckOnGround()
    {
    
    
        // 射线起点
        Vector2 leftStart2 = new Vector2(transform.position.x + m_CheckGround_Left_XOffset, transform.position.y + m_CheckGround_YOffset);
        Vector2 rightStart2 = new Vector2(transform.position.x + m_CheckGround_Right_XOffset, transform.position.y + m_CheckGround_YOffset);
        Vector2 leftTurnDirectionStart2 = new Vector2(leftStart2.x + 0.18f, leftStart2.y);
        Vector2 rightTurnDirectionStart2 = new Vector2(rightStart2.x + 0.22f, rightStart2.y);
#if DEBUG
        Vector3 leftStart3 = new Vector3(leftStart2.x, leftStart2.y, 0.0f);
        Vector3 rightStart3 = new Vector3(rightStart2.x, rightStart2.y, 0.0f);
        Vector3 leftTurnDirectionStart3 = new Vector3(leftTurnDirectionStart2.x, leftTurnDirectionStart2.y, 0.0f);
        Vector3 rightTurnDirectionStart3 = new Vector3(rightTurnDirectionStart2.x, rightTurnDirectionStart2.y, 0.0f);
#endif
        // 射线方向
        Vector2 direction2 = Vector2.down;
        Vector3 direction3 = Vector3.down;
        // 射线持续时间(Debug)
        float durationTime = 0.0f;
        // 射线结果,该结构体也有重写 bool operator,所以可以直接用来判断
        RaycastHit2D leftHitResult;
        RaycastHit2D rightHitResult;
        if (m_BTurnDirection)  // 如果转向了
        {
    
    
            leftHitResult = Physics2D.Raycast(leftTurnDirectionStart2, direction2, m_CheckGround_Distance, m_LMGround);
            rightHitResult = Physics2D.Raycast(rightTurnDirectionStart2, direction2, m_CheckGround_Distance, m_LMGround);
        }
        else  // 如果没转向
        {
    
    
            leftHitResult = Physics2D.Raycast(leftStart2, direction2, m_CheckGround_Distance, m_LMGround);
            rightHitResult = Physics2D.Raycast(rightStart2, direction2, m_CheckGround_Distance, m_LMGround);
        }

        if (leftHitResult.collider || rightHitResult.collider)
        {
    
    
            // 在地面上
            m_BOnGround = true;
            // 重置空中跳跃次数
            m_CurrentAllowAirJumpCount = m_AllowAirJumpCount;
            // 调试,击中为绿色
#if DEBUG
            if (m_BTurnDirection)
            {
    
    
                Debug.DrawLine(leftTurnDirectionStart3, leftTurnDirectionStart3 + direction3 * m_CheckGround_Distance, Color.green, durationTime);
                Debug.DrawLine(rightTurnDirectionStart3, rightTurnDirectionStart3 + direction3 * m_CheckGround_Distance, Color.green, durationTime);
            }
            else
            {
    
    
                Debug.DrawLine(leftStart3, leftStart3 + direction3 * m_CheckGround_Distance, Color.green, durationTime);
                Debug.DrawLine(rightStart3, rightStart3 + direction3 * m_CheckGround_Distance, Color.green, durationTime);
            }
#endif
        }
        else
        {
    
    
            // 不在地面上
            m_BOnGround = false;
            // 调试
#if DEBUG
            if (m_BTurnDirection)
            {
    
    
                Debug.DrawLine(leftTurnDirectionStart3, leftTurnDirectionStart3 + direction3 * m_CheckGround_Distance, Color.red, durationTime);
                Debug.DrawLine(rightTurnDirectionStart3, rightTurnDirectionStart3 + direction3 * m_CheckGround_Distance, Color.red, durationTime);
            }
            else
            {
    
    
                Debug.DrawLine(leftStart3, leftStart3 + direction3 * m_CheckGround_Distance, Color.red, durationTime);
                Debug.DrawLine(rightStart3, rightStart3 + direction3 * m_CheckGround_Distance, Color.red, durationTime);
            }
#endif
        }
    }

    /// <summary>
    /// @brief 水平移动
    /// </summary>
    private void HorizontalMove()
    {
    
    
        // 获取水平输入 [-1, 1],松开按键时为 0
        float horizontalValue = Input.GetAxis("Horizontal");
        m_Rb.velocity = new Vector2(horizontalValue * m_HorizontalSpeedFactorPerSecond * Time.fixedDeltaTime, m_Rb.velocity.y);

        // Player 转向
        if (horizontalValue < 0.0f)  // 默认是右边
        {
    
    
            m_BTurnDirection = true;
            transform.rotation = new Quaternion(0.0f, 180.0f, 0.0f, 1.0f);
        }
        else if (horizontalValue > 0.0f)
        {
    
    
            m_BTurnDirection = false;
            transform.rotation = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
        }
    }

    /// <summary>
    /// @brief 跳跃
    /// </summary>
    private void Jump()
    {
    
    
        if (m_BOnGround && m_BDoJump)
        {
    
    
            m_BDoJump = false;
            m_Rb.velocity = new Vector2(m_Rb.velocity.x, m_JumpSpeedPerSecond * Time.fixedDeltaTime);
        }
        if (!m_BOnGround && m_CurrentAllowAirJumpCount > 0 && m_BDoJump)
        {
    
    
            // 空中跳跃
            m_BDoJump = false;
            --m_CurrentAllowAirJumpCount;
            m_Rb.velocity = new Vector2(m_Rb.velocity.x, m_JumpSpeedPerSecond * Time.fixedDeltaTime);
        }
    }

    /// <summary>
    /// @brief 下蹲
    /// </summary>
    private void Crouch()
    {
    
    
        if (m_BPressedCrouch)
        {
    
    
            m_CapsuleCollider2D.size = new Vector2(m_CapsuleCollider2D.size.x, 0.6f);
            m_CapsuleCollider2D.offset = new Vector2(m_CapsuleCollider2D.offset.x, -0.545f);
        }
        else if (!m_BTopHasWall)  // 上方如果检测到有墙不能起来
        {
    
    
            m_CapsuleCollider2D.size = m_CapsuleCollider2DSize;
            m_CapsuleCollider2D.offset = m_CapsuleCollider2DOffset;
        }
    }
}

D. 瓦片裂缝问题?

✨参考链接

这里使用调节运行时分辨率的方式解决,之后可以优化

E. 小节演示










[入门卷] 5. 相机跟随 Player 移动(简单代码实现)

回到顶部

可以使用插件或代码或者两者结合使用,这里简单点使用纯代码实现,之后可能会单独出模块介绍

A. 实现简单相机跟随

  1. Hierarchy 窗口中的 Main Camera 添加 脚本,命名为 CameraController

  2. 示例代码如下

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraController : MonoBehaviour
{
    
    
    public GameObject m_Player;

    void Start()
    {
    
    
        m_Player = GameObject.Find("Player");
    }

    void Update()
    {
    
    
        transform.position = new Vector3(m_Player.transform.position.x, m_Player.transform.position.y, transform.position.z);
    }
}

注意 Z轴坐标的调节 transform.position.z

附加:也可以用 Unity 中的插件 Cinemachine 实现,这里入门篇不做介绍

B. 小结演示










[入门篇] 6. 游戏场景切换

回到顶部

  1. 新建场景。Ctrl + N 或者在 Project xxx文件夹下 右键 Create -> Scene 或者 File -> New Scene -> Basic 2D (Build-in)

保存在 _Game/Scenes 文件夹下,这里命名为 Scene2

  1. 在场景中添加一个触发物品,这里用 Sunnyland 中自带的 Prop -> door 门。

  1. 右键 UI -> Panel,在 Panel 上,右键 UI -> Legacy -> Text

这里我将画板 Panel 改了名字,Panel_EnterDoor

  1. 这里点击 EnterDoor,在 Animation 窗口点击 Create 可以创建空动画,这里点击录制按钮进行录制,比较简单,这里不做详细介绍。Text 文本可以按喜好输入文字。

  2. 这里用到了 “预制体”,在 …/_Game/Prefabs 文件夹,例如,将 Player 拖入到此文件夹中,或者在 文件夹中 右键 Create -> Prefab,这里使用前者更改文件夹中的预制体可以更改所有预制体实例,或者通过 Hierarchy 窗口中的 预制体实例右边的 小箭头进入也可以修改全部,但是仅仅修改 预制体实例,是只影响单个的。这里只是简单介绍 Prefab 的使用。

  3. 第二个场景如下

同样要 Tilemap,更之前创建方法一样,这里不过多介绍,相机脚本要重新赋值一下

有个问题说一下,还是网格裂缝问题,这里最好改一下网格大小,改成 0.99,改分辨率没啥用

先切换到第一个场景,这里可以改个名字,MainScene

A. 代码部分

  1. 给 EnterDoor 添加脚本组件,命名为 DoorController,实例代码如下
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class DoorController : MonoBehaviour
{
    
    
    [SerializeField] private bool m_BAtDoorPosition = false;

    [Header("UI 参数")]
    public GameObject m_UIEnterDoor;

    private void Start()
    {
    
    
        m_UIEnterDoor.SetActive(false);
    }

    private void Update()
    {
    
    
        if (m_BAtDoorPosition && Input.GetKeyDown(KeyCode.E))
        {
    
    
            // using UnityEngine.SceneManagement
            // 加载当前激活场景的下一个场景
            SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
        }
    }

    /// <summary>
    /// @biref 进入碰撞体开关
    /// </summary>
    private void OnTriggerEnter2D(Collider2D collision)
    {
    
    
        if (collision && collision.tag == "Player")
        {
    
    
            m_BAtDoorPosition = true;
            m_UIEnterDoor.SetActive(true);
        }
    }

    /// <summary>
    /// @biref 退出碰撞体开关
    /// </summary>
    /// <param name="collision"></param>
    private void OnTriggerExit2D(Collider2D collision)
    {
    
    
        if (collision && collision.tag == "Player")
        {
    
    
            m_BAtDoorPosition = true;
            m_UIEnterDoor.SetActive(false);
        }
    }
}

这里用到了 Inspector 中的 Tag,将 Player 的 Tag 设置为 Player,EnterDoor 可以设置默认禁用(我们在代码中也设置了,不影响)

组件启用是 enable,GameObject 激活是 SetActive。

这里还是说一下更改预制体的操作

将脚本挂到 door 上

B. 美化文字和画布

双击 Text 文字编辑

双击 Panel_EnterDoor,即可进入编辑模式

这里还可以做动画,例如增加渐入渐出的效果,使用录制功能,这里暂时不做介绍,我想放在单独的模块介绍

C. Build Settings 设置场景顺序

在菜单栏 File -> Build Settings

代码中我们用到的是场景的序号,然后这里需要设置一下场景

直接拖拽即可,或者点击添加打开的场景 Add Open Scenes

Ok,基本完成了,现在你可以运行你的项目

D. 小节演示










二、杂项篇

回到顶部

[杂项篇] 1. Visual Studio Code 配置 Unity

回到顶部

安装 C#,Uniy Code Snippets 2个插即可件

  1. C# 支持 C#;

  2. Uniy Code Snippets 提供 Awake(),Update() 等方法提示;

  3. 在 Unity 编辑器中 Edit -> Preferences -> External Tools -> External Script Editor -> 找到 Visual Studio Code,没有的话可以手动 Brower 到安装目录下找到 Code.exe;

  4. 我这里有下载 Visual Studio 2019,有安装 “使用 Unity 的游戏开发” 组件,如果没有,需要单独安装开发包,可以去 Vscode 官网看看怎么配置 Unity,简单一点就装一下 VS Unity 组件。(装了还没有提示,在 Vscode 设置中 开一下 Omnisharp Auto Start 试一下)

[杂项篇] 2. 瓦片裂缝问题?

回到顶部

有两种简单的方法这里先介绍一下,可以调节运行时的分辨率,或者改变网格大小,例如默认是 1,1,1 可以改成 0.99, 0.99, 0.99

更新补充

2022/12/12

人物转向时可能有 Bug,所以后面改用下面这种

/// <summary>
/// @brief 水平移动
/// </summary>
private void HorizontalMove()
{
    
    
    // 获取水平输入 [-1, 1],松开按键时为 0
    float horizontalValue = Input.GetAxis("Horizontal");
    m_Rb.velocity = new Vector2(horizontalValue * m_HorizontalSpeedFactorPerSecond * Time.fixedDeltaTime, m_Rb.velocity.y);

    // Player 转向
    if (horizontalValue < 0.0f)  // 默认是右边
    {
    
    
        m_BTurnDirection = true;
        transform.rotation = Quaternion.Euler(0.0f, 180.0f, 0.0f);
    }
    else if (horizontalValue > 0.0f)
    {
    
    
        m_BTurnDirection = false;
        transform.rotation = Quaternion.Euler(0.0f, 0.0f, 0.0f);
    }
}

三、最后



第一次写这么长的篇幅,不知道大家习不习惯,可以在评论区评论或者给我留言

入门卷还未完结,之后还会介绍敌人、光照等等

那么,本卷的内容就到这里了,下卷会继续分享 Unity2D 游戏开发入门相关知识

The End.

猜你喜欢

转载自blog.csdn.net/weixin_50114337/article/details/128277392