如何使用 Unity 开发第一个2D游戏

1,646 阅读17分钟

作为程序员,之前一直想自己开发一个2D的游戏,但是由于游戏知识的匮乏、游戏素材的缺失,一直没有开始第一步。现在跟着 B 站免费的《勇士传说》游戏开发视频教程一步一步开发实战,总算开发好了自己的一个小游戏。这篇文章记录一下开发2D游戏需要的知识点。

环境配置

安装 Unity Hub

Unity Hub 是一个帮助我们管理Unity项目的工具,类似于 Idea、Vs Code 等集成开发环境。

首先进入Unity中国官网,点击下载Unity,会跳转到 Unity 的下载界面。需要注意,下载 Unity 是需要 Unity 账号的,如果没有则需要先注册一个。下载界面如下图所示:

image.png

注意:视频教程是使用的 2022 年的版本,建议是保持一致。

设置中文

安装好 Unity Hub 后,也是需要登录的。登录成功后,可以通过 偏好设置 -> 外观 -> 语言 -> 简体中文 来设置中文。如下图所示:

image.png

安装 Unity 编辑器

通过 安装 -> 安装编辑器 打开对应的安装界面,选择你想要安装的 Unity 版本。

image.png

视频教程是使用的 2022.3.60f1,建议是保持一致。

创建2D项目

通过 项目 -> 新建项目 打开如下图的面板。

屏幕截图 2025-05-04 064115.png

其中编辑器版本选择你需要的,我这里选择的是 2022.3.60f1;项目模板选择 Universal 2D;还有记得取消勾选连接到 Unity Cloud,否则项目会非常卡。

设置 Vs Code 作为代码开发的配置

Unity 使用 C# 作为它的开发语言,它支持 VS Studio、Vs Code 等多款 IDE 来开发,我这里选择了 Vs Code。具体配置可以看:unity 使用 vs code 支持开发的配置:code.visualstudio.com/docs/other/…

游戏资源下载

项目中需要的免费的游戏资源如下:

这里先下载好,后面介绍如何导入。

Unity常用窗口介绍

image.png

  • (A)  工具栏提供最基本的工作功能。左侧包含用于操作 Scene 视图及其中游戏对象的基本工具。中间是播放、暂停和步进控制工具。右侧的按钮用于访问 Unity Collaborate、Unity 云服务和 Unity 帐户,然后是层可见性菜单,最后是 Editor 布局菜单(提供一些备选的 Editor 窗口布局,并允许保存自定义布局)。
  • (B)  Hierarchy 窗口是场景中每个游戏对象的分层文本表示形式。场景中的每一项都在层级视图中有一个条目,因此这两个窗口本质上相互关联。层级视图显示了游戏对象之间相互连接的结构。
  • (C)  Game 视图通过场景摄像机模拟最终渲染的游戏的外观效果。单击 Play 按钮时,模拟开始。
  • (D)  Scene 视图可用于直观导航和编辑场景。根据正在处理的项目类型,Scene 视图可显示 3D 或 2D 透视图。
  • (E)  Inspector 窗口可用于查看和编辑当前所选游戏对象的所有属性。由于不同类型的游戏对象具有不同的属性集,因此在您每次选择不同游戏对象时,Inspector 窗口的布局和内容也会变化。
  • (F)  Project 窗口显示可在项目中使用的资源库。将资源导入到项目中时,这些资源将显示在此处。

注意,如果你的窗口布局不小心弄乱了,可以在右上角的选项中选中 Default ,这表示默认的窗口布局

屏幕截图 2025-05-04 072211.png

导入资源

导入三方资源非常简单,把上面下载的资源压缩包解压后,直接拖拽到 Project 窗口的 Assets 目录下就可以了。我这里为了目录清晰,创建了一个 Art Assets 目录来存放这些资源。

image.png

Project 窗口的显示模式可以通过右边的三个点内的 One Column Layout 和 Two Column Layout 来切换,如下图所示:

屏幕截图 2025-05-04 073137.png

搭建场景

完成上面的前置步骤后,我们终于可以开始尝试做游戏了。首先我们来搭建下图的场景。

image.png

素材处理

在搭建场景之前,我们需要先对素材进行处理。在 Project 窗口中找到 Tiles 素材图片,选中后在 Inspector 窗口中查看。

我们需要该素材的配置进行修改,如下图所示:

屏幕截图 2025-05-04 080834.png

  • Sprite Mode:由于该素材是由多个Sprite图片组成的,因此这里选中 Multiple
  • Pixels Per Unit:每个单元格对应的像素尺寸,由于素材是16,因此这里也设置成16
  • Filter Mode:像素素材一般选择 Point 模式,就是不对素材进行过滤
  • Compression: 压缩选项,像素素材一般选择 None,不进行压缩

最后点击 apply 应用修改。

素材切割

Tiles 素材图片是由多个Sprite图片组成的,我们可以在 Inspector 窗口点击 Sprite Editor 来打开编辑窗口。

屏幕截图 2025-05-04 093821.png

在窗口中,点击 slice,选择 Grid by Cell size,Pixel 设置为 16 x 16,Pivot 选择 Bottom,点击 Slice,最后还需要点击 Apply 保存修改。就可以看到素材被分割成 16 x 16 的小块了。后面就需要这些切割好的小块来搭建场景。

image.png

Tile Palette

瓦片面板 (Tile Palette) 是 Unity 中一个强大的工具,主要用于创建和管理 2D 地图。通过瓦片面板,开发者可以选择和放置各种预定义的瓦片,并灵活地编辑和修改地图,以创建复杂的场景。

我们通过 window -> 2D -> Tile Palette,就可以打开 Tile Palette 窗口。

屏幕截图 2025-05-04 092532.png

我们通过 Create New Palette 打开创建窗口,设置好名字后,选择保存的位置,我这里创建了 Map Assets/Places 目录来保存。

然后把 Tiles 素材图片拖拽到 Tile Palette 窗口,然后选择保存位置,我保存在 Map Assets/ Tiles 目录下。

屏幕截图 2025-05-04 092834.png

完成上面步骤后,在 Hierarchy 窗口,右键 -> 2D object -> Tilemap -> Rectangular 创建一个 Tile 对象

屏幕截图 2025-05-04 093412.png

回到 Tile Palette 窗口,可以看到此时已经选中了我们创建的 Tile 对象。

屏幕截图 2025-05-04 093955.png

这时,我们就可以使用鼠标选中需要的一个小块或者多个小块在 Scene 窗口来绘制我们需要的场景了。效果如下所示:

2025-05-04094654-ezgif.com-video-to-gif-converter.gif

更多关于Tile Palette,可以看【unity3D】TileMap基础知识(详细版)

规则瓦片

可以看到上面搭建场景的方式太慢了,Unity 提供了规则 tilemap,可以让Unity按照你定义的规则来生成对应的 tilemap。

在 Map Assets 目录下,我们通过 右键 -> Create -> 2D -> Tile -> Rule Tile 来创建一个Rule Tile对象。选中该对象,Inspector 窗口就可以设置对应的规则了。

image.png

如上图所示,规则比较简单,就是九宫格,红叉表示没有方块,箭头表示有方块。然后,我们把创建的Rule Tile对象拖拽到Tile Palette中去,这时就可以使用它来生成对应的场景了,如下所示:

2025-05-04100745-ezgif.com-video-to-gif-converter.gif

更多关于规则瓦片,可以看规则瓦片和动态瓦片的应用怎么连接两个不同的RuleTiles

动画瓦片

在 Map Assets 目录下,我们通过 右键 -> Create -> 2D -> Tile -> Animated Tile 来创建一个Animated Tile对象。Animated Tile可以添加多个图片,这组图片会按照一定时间按顺序变化,如下图所示,把水流的四种状态设置给 Animated Tile。

屏幕截图 2025-05-04 103846.png

image.png

类似的,我们再创建两个 Animated Tile,将水流的另外两部分设置给它们。效果如下:

2025-05-04104318-ezgif.com-video-to-gif-converter.gif

碰撞体

要想让角色和场景互动,就需要为场景添加碰撞体组件。选中我们的场景,在 Inspector 窗口中,点击 Add Component 来添加 Tilemap Collider 2D 组件。

image.png

从上图可以看到碰撞体是一块一块的。如果你想要把 tilemap 变成一个整体,需要添加 Composite Collider 2D 组件,然后在 Tilemap Collider 2D 中设置 Used by Composite 为勾选状态。如下图所示:

屏幕截图 2025-05-04 105939.png

添加 Composite Collider 2D 组件后,Unity 会自动添加 Rigidbody 2D 重力组件。我们需要设置其 Body Type 为 Static,这样我们的场景才不会掉下来。效果如下图所示:

image.png

Composite Collider 2D 如果触碰检测出现问题,需要修改 Composite Collider 2D 中的 Geometry Type 属性为 Polygons。

创建人物

完成场景后,我们就可以开始创建人物了。首先,我们在资源文件夹下找到 char_blue_1 素材图片,按照之前介绍的步骤配置好后,对图片素材进行如下配置的裁剪。

image.png

然后在裁剪好的图片中,选取一个空闲状态拖拽到Hierarchy 窗口。

image.png

这样一个人物就添加进来了。

添加重力和碰撞体

选中人物对象,在 Inspector 窗口中,我们可以给它添加 Rigidbody 2D 重力组件。

image.png

其中 Body Type 是指刚体类型:

  • Static(静态) :静态刚体不受物理力的影响,也不会被其他刚体推动。它通常用于场景中固定不动的物体,如地面、墙壁等。
  • Kinematic(运动学) :运动学刚体不受重力和其他力的影响,但可以通过代码控制其移动和旋转。常用于需要精确控制移动的物体,如移动平台。
  • Dynamic(动态) :动态刚体受物理力的影响,会与其他刚体发生碰撞、产生反弹等效果。例如游戏中的角色、道具等通常使用动态刚体。

Mass 是指当前物体的质量,质量越大,刚体越难被推动和加速。Gravity Scale 是重力缩放,用于调整刚体受到重力的影响程度。设置为 2 表示受到默认重力的两倍。此外,我们还需要勾选 Freeze Rotation z,防止人物摔倒。

默认重力是 9.8,你可以通过 edit -> project settings -> Physics 2D 来修改。

image.png

Rigidbody 2D 的其他属性可以看官方文档:2D 刚体

设置好重力后,需要给人物增加碰撞体。这里使用 Capsule Collider 2D。

image.png

然后点击 Capsule Collider 2D 的 Edit Collider,就可以拖拽来调整其大小和样式了。

image.png

更多碰撞体可以看2D 边界碰撞体 - Unity 手册

配置输入系统

通过 edit -> project setting -> player 找到设置界面

屏幕截图 2025-04-03 092556.png

通过 window -> package manager -> 切换到 Unity Registry -> 搜索 Input System,然后安装

image.png

通过 Add component 添加 Player Input 组件,在组件中点击 Create Actions 来创建默认的控制器

image.png

这里创建了 PlayerInputController.inputactions 文件。然后移除 Player Input 组件,使用 Generate C# Class 来生成 PlayerInputController.cs 代码,这样我们就可以通过这个脚本文件来进行输入控制了。

image.png

接下来,我们创建 PlayerController 脚本文件,并挂载到人物对象上去。PlayerController 脚本的代码如下:

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

public class PlayerController : MonoBehaviour
{

    private Rigidbody2D rb;

    private PlayerInputController playerInputController;

    public Vector2 inputDiration;

    public float speed = 300;

    private void Awake() {
        playerInputController = new PlayerInputController();

        rb = GetComponent<Rigidbody2D>();
    }

    void OnEnable()
    {
        playerInputController.Enable();
    }

    void OnDisable()
    {
        playerInputController.Disable();
    }

    void Update()
    {
        inputDiration = playerInputController.GamePlayer.Move.ReadValue<Vector2>();
    }

    void FixedUpdate()
    {
        Move();
    }

    public void Move() {
        // 人物移动
        rb.velocity = new Vector2(inputDiration.x * speed * Time.deltaTime, rb.velocity.y);

        // 通过 scale 的方式来实现翻转(由于之前锚点设置在底部中心位置)
        int faceDir = (int)transform.localScale.x;
        if (inputDiration.x >= 0) {
            faceDir = Mathf.Abs((int)transform.localScale.x);
        } else {
            faceDir = -Mathf.Abs((int)transform.localScale.x);
        }
        transform.localScale = new Vector3(faceDir, transform.localScale.y, transform.localScale.z);
    }
}

人物跳跃

双击 PlayerInputController.inputactions 文件,打开输入控制编辑器。往里面增加 Jump 动作,并设置 Jump 动作触发为 空格 ,然后勾选在 Keyborad&Mouse 模板中。

屏幕截图 2025-04-03 161247.png

然后在代码中增加跳跃的逻辑,代码示例如下:

public class PlayerController : MonoBehaviour
{

   ...

    public float jumpForce = 10;

    private void Awake() {
        playerInputController = new PlayerInputController();

        rb = GetComponent<Rigidbody2D>();

        // started 表示按键按下的那一刻
        // += 表示注册
        playerInputController.GamePlayer.Jump.started += Jump;
    }

    private void Jump(InputAction.CallbackContext context)
    {
        rb.AddForce(transform.up * jumpForce, ForceMode2D.Impulse);
    }

    ...
}

跳跃限制

这里需要限制一下人物只能在地面时跳跃。我们先创建 PhysicsCheck 脚本来检测是否在地面,代码示例如下:

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

// 检测 Player 的相关信息 
public class PhysicsCheck : MonoBehaviour
{

    // 是否在地面上
    public bool isGround;
    // 检测是否在地面上的范围
    public float checkGroundRadius;
    // 检测的碰撞图层
    public LayerMask groundLayer;

    void Update()
    {
        Check();
    }

    void Check() {
        // 检测是否在地面
        isGround = Physics2D.OverlapCircle(transform.position, checkGroundRadius, groundLayer);
    }

}

然后在 PlayerController 中引用这个脚本,再根据 isGround 属性来判断是否跳跃。代码示例如下:

public class PlayerController : MonoBehaviour
{

    ...

    private PhysicsCheck physicsCheck;
    
    private void Awake() {
        
        physicsCheck = GetComponent<PhysicsCheck>();
        
        ...
    }

    private void Jump(InputAction.CallbackContext context)
    {   
        // 判断是否在地面,如果在则跳跃
        if (physicsCheck.isGround) {
            rb.AddForce(transform.up * jumpForce, ForceMode2D.Impulse);
        }
    }
}    

光滑的材质

由于默认情况下,物理物体存在摩擦,因此跳跃时会卡在墙壁上。此时,我们可以给人物设置光滑的物理材质。我们先创建一个 Materials 目录来存放材质,然后通过 右键 -> Create -> 2D -> Physics Material 2D 来创建一个 2D 的物理材质,然后设置它的 Friction (摩擦力)为 0

image.png

最后在 Rigidbody 2D 组件的 Material 属性中选择该材质。

image.png

动画

创建动画

在人物添加 Animator 组件,然后通过 鼠标右键 -> Create -> Animator Controller 来创建一个 PlayerAnimatorController.controller 文件。之后在人物的Animator 组件中引用这个文件

image.png

然后通过 window -> Animation -> Animator 打开Animator窗口,如下图所示

image.png

在通过 window -> Animation -> Animation 打开 Animation 窗口(Animation 是单个动画,Animator用来控制Animation)

image.png

选中人物对象,在 Animation 窗口点击 Create 来创建一个 Animation。创建完成后,把帧动画对应的图片拖进Animation 窗口,点击播放可以看到对应的效果。

image.png

其中 Samples 是指帧率(1s有多少帧),需要通过点击 Animation 窗口三个点 -> show sample rate 来显示出来。

动画切换

当需要多个动画切换时,比如空闲动画、走路动画、跑步动画之间互相切换,这时就需要 Animator。

  1. 首先分别创建空闲动画、走路动画、跑步动画,分别为 idle、walk、run
  2. 然后打开 Animator 窗口,选中 Parameters 选项,然后创建一个 velocityX 参数,如下图所示

image.png

注意:any 切换到其他动画时,不能 勾选 can transition to self,否则动画会卡住不动。具体见 【Unity游戏开发中的常见问题第二卷】AnyState小坑导致动画卡在第一帧_can transition to self-CSDN博客

  1. 然后在Animator 窗口选中动画状态,鼠标右键 -> Make Transition 创建 Transition,来连接不同的动画状态。

image.png

  1. 点击 Transition,对不同的 Transition 根据 velocityX 设置不同的变换条件

image.png

  1. 创建 PlayerAnimationController 脚本,根据当前人物对象更新 velocityX 的值,代码如下所示:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerAnimationController : MonoBehaviour
{
   private Animator animator;

   private Rigidbody2D rigidbody2D;

    void Awake()
    {
        animator = GetComponent<Animator>();
        rigidbody2D = GetComponent<Rigidbody2D>();
    }

    void Update()
    {
        SetAnimation();
    }

    void SetAnimation() {
        animator.SetFloat("velocityX", Mathf.Abs(rigidbody2D.velocity.x));
    }

}

动画混合

当一个动画有多个阶段时使用。首先分别创建各个阶段的 Animation,然后从 Animator 窗口移除自动创建好的状态。再通过 鼠标右键 -> Create State -> from new blend tree 创建 blend tree 状态。双击blend tree 状态,进入编辑界面,选中blend tree 状态右键,选中 add motion 添加之前创建的动画。效果如下图所示:

image.png

子状态机

这个状态机的作用是,他可以创建另外一套次级状态。举例来说,act游戏中角色站着的时候有站跑攻击防御,下蹲的时候则是另外一套站跑攻击防御。下蹲就是次级状态,因为下蹲跑,下蹲攻击这些动作都是基于下蹲这个状态,所以他们可以独立成为一个次级状态机。更多的比如,下蹲攻击防御被击,空中攻击防御被击,中毒状态下攻击防御被击,残血状态下攻击防御被击等等不同的次级状态。

Unity 动画系列九 子状态机Sub-State Machines - 简书

攻击判定

我们可以通过 Collider 碰撞体的触发器来实现攻击检测,原理如下:

物体A勾选 IsTrigger 后,如果有其他物体B组件触碰到了物体A,那么会调用物体B下面的方法

  • Collider.OnTriggerEnter:当触发器碰撞体首次与另一个碰撞体接触时,Unity 会调用此函数。
  • Collider.OnTriggerStay:如果检测到另一个碰撞体在触发器碰撞体内,Unity 会在每一帧上调用此函数。
  • Collider.OnTriggerExit:当触发器碰撞体停止与另一个碰撞体接触时,Unity 会调用此函数。

示例如下:

public class DoorObject : MonoBehaviour
{
    // “other” refers to the collider on the GameObject inside this trigger
    void OnTriggerEnter (Collider other)
    {
        Debug.Log ("A collider has entered the DoorObject trigger");
    }

    void OnTriggerStay (Collider other)
    {
        Debug.Log ("A collider is inside the DoorObject trigger");
    }
    
    void OnTriggerExit (Collider other)
    {
        Debug.Log ("A collider has exited the DoorObject trigger");
    }
}

如果触发器方法不调用,则需要修改 Rigidbody 2D 的 sleeping mode 为 never sleep。

敌人

射线检测

射线检测用于敌人发现人物的检测,示例如下:

using UnityEngine;

public class EnemyDetector : MonoBehaviour
{
    public Vector2 boxSize = new Vector2(2f, 1f); // 盒子的大小
    public float distance = 5f; // 投射的距离
    public LayerMask enemyLayer; // 敌人所在的图层

    private void Update()
    {
        DetectEnemies();
    }

    private void DetectEnemies()
    {
        // 进行 BoxCast
        RaycastHit2D[] hits = Physics2D.BoxCastAll(transform.position, boxSize, 0f, transform.right, distance, enemyLayer);

        // 遍历所有检测到的碰撞体
        foreach (RaycastHit2D hit in hits)
        {
            if (hit.collider != null)
            {
                // 这里可以处理检测到敌人后的逻辑
                Debug.Log("发现敌人: " + hit.collider.gameObject.name);
            }
        }
    }
}
    

让敌人之间不互相碰撞

屏幕截图 2025-05-04 175547.png

使用碰撞体的 exclude layers 可以排除不需要检测的图层。这样可以让敌人都在同一个 enemy 层,然后 exclude layers 这一层,这样敌人对象就不会互相碰撞了。

摄像机

从 window -> package manager -> Unity registry 中搜索 Cinemachine 并安装

image.png

  • tracked object offset

设置摄像头追随点的偏移

  • dead zone

设置该区域内的移动,不会触发摄像头的跟随

  • Cinemachine Pixel Perfect

用于改变虚拟摄像机的正交尺寸。 该扩展可以检测 Pixel Perfect Camera 组件是否存在,并使用相应的组件设置来计算虚拟摄像机的正确正交尺寸,从而以像素完美的分辨率完美保留精灵

  • Cinemachine Confiner 2D

限制摄像头移动的区域,防止摄像头拍摄到边界信息。

  • Cinemachine Impulse Listener

监听 Cinemachine Impulse source 发出的震动。Cinemachine Impulse source 用于摄像机震动

UI

为游戏创建一个 UI 界面,我们需要通过 鼠标右键 -> UI -> Canvas 来创建一个画布。再创建画布的同时会生成一个 EventSystem。它是 UI 的事件处理组件。

由于之前我们使用了最新的输入组件,我们需要点击 Replace with InputSystemUIInputModule 按钮,然后将 Action Assets 设置为我们之前创建的 PlayerInputController。

image.png

如下图所示:

image.png

音效

免费资源

音效组件

  • AudioSource:音效来源
  • AudioListener:音效监听

混音

window -> audio -> audio mixer 打开混音窗口

数据传递

跨代码,跨物体、跨场景的数据传递,使用 ScriptableObject ,具体可以看 Unity进阶:ScriptableObject使用指南-CSDN博客

数据存储

数据存储使用 com.unity.nuget.newtonsoft-json,具体见 Unity 数据读取|(四)Json文件解析

场景切换

Addressables 是 Unity的资源管理系统,我们可以使用它来进行场景切换。

DOTween 可以实现场景之间切换的动画,具体见 DOTween (HOTween v2) | 动画 工具 | Unity Asset Store

实现屏幕触碰

on-Screen Stick 模拟操纵杆

on-Screen Button

miro

光线

Unity3D-光源组件(Light)详解_unity light-CSDN博客

后处理

URP——后期处理特效——景深Depth Of Field_unity urp depth of field-CSDN博客

其他

Pixels Per Unit

如下图所示,这个 Pixels Per Unit 是指每个方格里面的像素个数

屏幕截图 2025-04-02 173116.png

Fiter mode 过滤器模式 

在UNITY3D中点开一张贴图,Fiter mode过滤器模式 ,下面有3个选项  point  , Bilinear ,Trilinear 

  • point :点采样模式,屏幕像素会需找最近的贴图像素点,来作为输出,这种比较生硬,但是性能好,不抗锯齿(像素游戏一般采用这种)
  • Bilinear :双线性采样模式, 采用最近的4个像素来做线性插值,有平缓的过渡。可以解决贴图放大的问题,但是贴图缩小依然有锯齿。 缩小可以用mip-map来解决。
  • Trilinear :三线性采样模式,可以比较好的解决贴图缩小和放大。

图层碰撞设置

image.png

脚本属性标签

Unity脚本常用的标签属性 []_unity 属性标签-CSDN博客

[HideInInspector] 隐藏脚本中的 public 属性,不在 unity 中显示

[RequireComponent(typeof(Rigidbody2D), typeof(Animator), typeof(PhysicsCheck))] 可以声明需要添加的组件,它会帮你自动添加进去

Gizmos

Gizmos 是指下图中辅助开关。我们可以在代码中通过 Gizmos.DrawWireSphere 方法来绘制自己的辅助线。

屏幕截图 2025-04-03 210229.png

快捷键

Unity 热键 - Unity 手册