部分功能例如目录跳转,回到顶部功能在这里有问题

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

✨✨目录

  • 一、入门卷

  • 二、杂项卷

  • 三、最后

一、入门卷

回到顶部

  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 updateprivate 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 frameprivate 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 DEBUGDebug.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 DEBUGDebug.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 DEBUGVector3 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 DEBUGif (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 DEBUGif (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],松开按键时为 0float 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 updateprivate 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 frameprivate 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 DEBUGDebug.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 DEBUGDebug.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 DEBUGVector3 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 DEBUGif (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 DEBUGif (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],松开按键时为 0float 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],松开按键时为 0float 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.

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

  1. 游戏开发的HelloWorld,快速入门,新手上路,使用CocosCreator+JS,flyBird(飞翔小鸟)

    游戏开发的HelloWorld,快速入门,新手上路,使用CocosCreator+JS,flyBird(飞翔小鸟) 介绍 开发环境 游戏原理 文件结构 详细操作 全部代码 介绍 拓展什么的都没有,真正 ...

  2. unity2d游戏开发系列教程:二、新建工程并熟悉Unity编辑器常用功能

    目录 unity2d游戏开发系列教程:一.环境安装 第一步.打开项目 耐心等待一小会 工程界面 第二步.创建第一个场景(第一关)进行试玩 点击图中标号1的运行按钮,即可简单试玩感受,操作如下 移动A, ...

  3. unity2d游戏开发系列教程:四、一个2D游戏所需要的主要功能(游戏框架)

    目录 unity2d游戏开发系列教程:一.环境安装 unity2d游戏开发系列教程:二.新建工程并熟悉Unity编辑器常用功能 unity2d游戏开发系列教程:三.场景布置,增加怪物和机关 原文下载 ...

  4. 【游戏开发渲染】Unity ShaderGraph使用教程与各种特效案例:Unity2022(持续更新)

    文章目录 一.ShaderGraph前言 二.ShaderGraph科普 1.渲染管线(Render Pipline) 2.可编程渲染管线,SRP(Scriptable Render Pipline) ...

  5. Unity-2D游戏开发套件指南-免费资源

    目录 项目介绍 创建新场景 绘制关卡 测试关卡 添加移动平台 使用事件触发开门动作 敌人 摧毁敌人 装饰 传送玩家 示例 今年年初,Unity Brighton的内容团队推出了他们的最新作品-2D游戏 ...

  6. Unity2D游戏开发和C#编程大师班

    本课程采用现代游戏开发的最新内容和最新技术(Unity 2D 2022) 学习任何东西的最好方法是以一种真正有趣的方式去做,这就是这门课程的来源.如果你想了解你看到的这些不可思议的游戏是如何制作的,没 ...

  7. Unity2D游戏开发基础教程1.2项目、资源和场景

    Unity2D游戏开发基础教程1.2项目.资源和场景 如果使用Unity制作游戏,就一定会接触到项目(Project.资源(Asset)和场景(Scene).本节将依次介绍它们. 1.2.1  项目 ...

  8. Unity2D游戏开发基础教程1.2 项目、资源和场景

    Unity2D游戏开发基础教程1.2 项目.资源和场景 如果使用Unity制作游戏,就一定会接触到项目(Project.资源(Asset)和场景(Scene).本节将依次介绍它们. 1.2.1  项目 ...

  9. unity2d游戏开发系列教程:一、环境安装

    从这篇文章开始,一步一步教大家从0开始通过2DGameKit项目进行2D游戏开发 第一步.环境安装 1.先使用手机下载Unity Connect并注册登陆 2.进入unity官网https://uni ...

最新文章

  1. gcc 编译 java,编译lineage,gcc的版本问题
  2. 码农新机会!2019-2020行业调查报告出炉,这个领域程序员缺口很大!
  3. 掉网问题的log分析
  4. 伺服电机常用参数设置_6个步骤教你如何快速调试伺服电机
  5. 刚刚,这几门保证就业的算法类课程,居然能免费听了!
  6. 一张大图了解ASP.NET Core 3.1 中的Authentication与Authorization
  7. linux cifs windows 慢,windows上使用dockerIO特别慢有没有更优的解决方案?
  8. Map的value转化为其它类型
  9. mysqlbinlog相关
  10. MySQL 这三道必问面试题,你都会吗?
  11. 训练SnowNLP新模型
  12. 西门子s7删除注册表,西门子S7200编程软件卸载步骤
  13. base64加密后字符串自动换行问题
  14. 解读全新闪存FlashSystem 9100
  15. 选课通知 | 北交大《数据分析方法及应用实战》秋季全校选修课简介!
  16. uniapp 生成html5_uni-app 登录(h5+ app 篇)
  17. 百度大数据+零售发挥引擎优势 ------BDL ,大数据+亚当科茨领导的人工智能实验室和由张潼领导的大数据实验室
  18. python 论文写作_AI基础:论文写作工具
  19. C# DirectoryInfo GetFiles()获得的文件列表与本机下文件顺序一致
  20. android gta5 下载地址,gta5 for android

热门文章

  1. Jaccard 相似度
  2. java enum 随机_Java 枚举(enum)
  3. 取消Ctrl+z撤销的方法
  4. switch安装linux教程,ubuntu 14.04 freeswitch安装及应用
  5. 推荐八个技术学习网站
  6. 【Python】高级特性 一
  7. lambda表达式filter使用
  8. 在线编程学习网站大搜罗
  9. Linux:常用性能检查命令(内存、CPU 、网络、磁盘、Java应用)
  10. Android面试题(基础部分1)