Unity-2D

1.Unity中的2D模式:

1)游戏在二维上展示

启用 2D 模式时将会设置正交(即无透视)视图:摄像机沿 Z 轴观察,而 Y 轴向上增加。因此可以轻松可视化场景并放置 2D 对象。

2)设置项目默认模式:Edit > Project Settings > Default Behavior Mode

在 2D 项目模式下:

  • 所有图像(images)都会被当做 2D 图片,并设置成 sprite mode 精灵模式
  • Sprite Packer 会被启动
  • Scene 视图默认为 2D
  • 默认游戏对象没有实时方向光。
  • 摄像机的默认位置为 0,0,–10。(在 3D 模式下为 0,1,–10。)
  • The camera projection is set to be Orthographic. (In 3D Mode it is Perspective.)摄像机投射模式被设置为正交(没有远小近大,没有距离之分),而在 3D 模式下,是透视(远小近大,有距离之分)
  • 在 Lighting 窗口中:
  • Skybox is disabled for new scenes:天空盒默认关闭
  • Ambient Source is set to Color. (With the color set as a dark grey: RGB: 54, 58, 66.) 保围光源设置为 color ,默认为灰色
  • Realtime Global Illumination (Enlighten) is set to off.关闭实时光照
  • Baked Global Illumination is set to off.关闭全局光照烘焙
  • Auto-Building set to off.自动创建关闭

2.在Unity中创建2D游戏

1)Player的创建与控制:
使用静态精灵创建Player:
  • 精灵 Sprite 是 Unity 中 2D 素材的默认存在形式,是 Unity 中的 2D 图形对象。
  • 在 2D 游戏中,使用 2D 素材的过程:PNG(JPG 等)----> Sprite ----> GameObject
2)Player的移动脚本:
  • 铺垫
Vector2 二维向量
  • 在数学中,Vector 向量/矢量指的是带方向的线段
  • 在 Unity 中,Transform 值使用 x 表示水平位置,使用 y 表示垂直位置,使用 z 表示深度。这 3 个数值组成一个坐标。由于此游戏是 2D 游戏,你无需存储 z 轴位置,因此你可以在此处使用 Vector2 来仅存储 x 和 y 位置。
  • Transform 中 position 的类型,也是 Vector2。C# 这种强类型语言,赋值时,左右必须是同一类型才能进行
Unity 默认 Input Manager 设置
  • 在 Unity 项目设置中,可以通过 Input Manager 进行默认的游戏输入控制设置 Edit > Project Settings > Input
  • 键盘按键,以 2 个键来定义轴:
  • 负值键 negative button,被按下时将轴设置为 -1
  • 正值键 positive button ,被按下时将轴设置为 1
  • Axis 轴 Axes 是它的负数形式
  • Horizontal Axis: 水平轴 对应 X 轴
  • Vertical Axis:纵轴 对应 Y 轴
Input类
  • 使用该类来读取传统游戏输入中设置的轴/鼠标/按键,以及访问移动设备上的多点触控/加速度计数据。若要使用输入来进行任何类型的移动行为,请使用 Input.GetAxis。 它为您提供平滑且可配置的输入 - 可以映射到键盘、游戏杆或鼠标。 请将 Input.GetButton 仅用于事件等操作。不要将它用于移动操作。Input.GetAxis 将使脚本代码更简洁。
时间和帧率
  • 当前的代码中,帧数越高,同一时间内,执行 Update 的次数越多,角色移动速度越快。如果游戏以每秒 60 帧的速度运行,那么 Ruby 将移动 0.1 _ 60,因此每秒移动 6 个单位。但是,如果游戏以每秒 10 帧的速度运行,就像刚刚让游戏运行的那样,那么 Ruby 仅移动 0.1 _ 10,因此每秒移动 1 个单位!
  • 如果一个玩家的计算机非常陈旧,只能以每秒 30 帧的速度运行游戏,而另一个玩家的计算机能以每秒 120 帧的速度运行游戏,那么这两个玩家的主角的移动速度会有很大差异。这样就会使游戏的难易程度提高或降低,具体取决于运行游戏的计算机。
  • 而帧数是由硬件水平影响的(越好越高),不同电脑中,会导致游戏效果完全不同
  • 新建Player后,选中Player,在Inspector窗口中Add Component,自定义一个脚本,移动脚本的代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RubyMover : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }
    public float speed = 0.1f;
    // speed访问权限设置为public,在Unity中可修改属性。
    // Update is called once per frame
    void Update()
    {
        float x = Input.GetAxis("Horizontal");
        float y = Input.GetAxis("Vertical");

        Vector2 position = transform.position;
        position.x += 0.1f * x * speed * Time.deltaTime;
        position.y += 0.1f * y * speed * Time.deltaTime;
        transform.position = position;

    }
}
3)2D游戏中瓦片地图的创建和使用:
瓦片地图工作流程
  1. 预处理 sprite 资源:将图片资源拖拽到 project 中,生成 sprite;然后一般需要进行切割 slice ,将其配置成需要的各个 tile;
  2. 创建要在其上绘制瓦片的瓦片地图。此过程中还会自动创建 Grid 游戏对象作为瓦片地图的父级。
  3. 直接创建瓦片资源,或者通过将用作瓦片素材的精灵带入 Tile Palette 窗口自动生成瓦片。
  4. 创建一个包含瓦片资源的 Tile Palette,并使用各种笔刷来绘制到瓦片地图上。
瓦片地图的高级使用
  • 使用普通的瓦片地图,构建整个世界,一个一个格子用笔刷来填充,非常费时,Unity 在不断地升级中,添加了很多种快速构建瓦片地图的方式,掌握了这些方法,能够极大减少绘制地图所用的时间。
  • 编程瓦片 Scriptable Tile
  • Unity 支持用代码创建自己的 Tile 类,自己书写瓦片的绘制规则。还可以为瓦片创建自定义编辑器。这与脚本化对象的自定义编辑器的工作方式相同。创建一个继承自 TileBase(或 TileBase 的任何有用子类,如 Tile)的新类。重写新的 Tile 类所需的所有方法。
  • 编程画笔 Scriptable Brush
  • Unity 也支持创建自己的 Brush 类,设计适合自己游戏的网格画笔。
    创建一个继承自 GridBrushBase(或 GridBrushBase 的任何有用子类,如 GridBrush)的新类。重写新的 Brush 类所需的所有方法。创建可编程画笔后,画笔将列在 Palette 窗口的 Brushes 下拉选单 中。默认情况下,可编程画笔脚本的实例将经过实例化并存储在项目的 Library 文件夹中。对画笔属性的任何修改都会存储在该实例中。如果希望该画笔有多个具备不同属性的副本,可在项目中将画笔实例化为资源。这些画笔资源将在 Brush 下拉选单中单独列出。
2D Tilemap Extras (2D 瓦片地图扩展)
  • Animated Tile 动画瓦片
  • 动画瓦片在游戏运行时,按顺序显示 Sprite 列表以创建逐帧动画
  • Rule Tile 规则瓦片
  • 可以为每个瓦片创建规则,在绘制时,unity 会自动响应这些规则,绘制地图时更加智能
  • RuleTile 使用步骤:
  • 准备 Tile 素材,配置素材属性,分割素材;
  • 新建 RuleTile,为其添加规则,设置每个 Tile 的相邻规则;
  • 将设置好的 RuleTile 拖拽到 Tile Palette 中,就可以使用了。
  • Rule Override Tile / Advanced Rule Override Tile 规则覆盖瓦片
  • 可以用已经生成好的 Rule Tile,作为 Rule Override Tile 的规则来源,只替换对应的瓦片素材,而沿用原先的规则,可以快速的创建规则瓦片的方法。
4)场景中的图形顺序:
伪透视图
  • 透视图指的是有深度、距离感的图,一般要三维中的深度轴来表现场景的深度,而二维游戏中没有这个深度,只能通过前后来仿造深度效果,称为“伪透视图”
  • 先前通过调整瓦片的 Order in Layer 属性来解决了瓦片地图的排序问题,但并非总是希望一个游戏对象在另一个游戏对象之上,比如,在同一个瓦片地图中,玩家角色在一个物体之前(比如一棵树)时,应该是玩家遮挡树,而玩家移动到树后时,应该是树遮挡玩家,这就需要“伪造”透视图。
  • 在 2D 游戏中,场景里的 “前后” 是由 Y 轴决定的,需要让 Unity 根据游戏对象的 y 坐标来绘制游戏对象Y 轴 y 坐标值越小,越靠前,应该遮挡 y 坐标值较大的游戏对象,也就是 y 坐标较小的游戏对象后绘制,就会位于上层
  • 在游戏中,如果要设置 2D 伪透视试图,需要在项目设置中进行更改:
  • Edit > Project Settings > Graphics > Camera Settings > Transparency Sort Mode = Custom Axis > Transparency Sort Axis x = 0 / y = 1 / z = 0
  • 此设置告诉 Unity 在 y 轴上基于精灵的位置来绘制精灵。
  • 按 Play 以进入运行模式并测试你的更改。现在,你的角色比箱子高时,角色应该会绘制在箱子的后面;而角色比箱子低时,绘制在箱子的前面。
  • Sprite 轴心 pivot
  • 每个 Sprite 都有一个轴心(中心点),Unity 根据 pivot 对 sprite 进行定位,这个 pivot 可以在 sprite editor 中调整,可以将其设置到 sprite 上任意位置
  • 在 2D Rpg 游戏场景中的游戏对象,如果想要实现较为真实的 “伪透视” 效果,最好将游戏对象的 sprite 中 pivot 都设置到素材的最下方正中。
  • 然后将游戏对象的 Sprite Sort Point 由 Center 改为 Pivot 即可.
5 )物理系统:
铺垫:
  • Unity中内置的物理系统可以模仿地球上的物理系统,使Unity中创建的一切精灵都具有类似地球上的物理属性。
物理系统中比较重要的脚本组件:
  • Rigidbody :项目中的刚体组件
  • 定义了对象收到外力后,如何模拟其行为,如翻滚,掉落。当为一个对象添加了 Rigidbody 组件后,就会模拟受重力而掉落。如果再加上collider组件,则会响应外部的力,而运动。添加 Rigidbody后,我们就不能通过 transform 组件来移动物体了,只能由物理系统来模拟驱动。当然这不是绝对的,某些特定情境,需要关掉物理模拟,将物体摆放到指定位置,再重新打开物理模拟。有时,我们希望对象参与物理模拟,但是其行为还是由逻辑控制,比如,对于游戏内的NPC,我们需要由代码控制其运动,同时又需要添加rigidbody,这样才能被Trigger检测到,对于这种情况,可以将 Rigidbody 设置为动力学物体(Is Kinemiac)。
  • ···collider : 项目中碰撞体组件:
  • 定义了物体的形状来进行碰撞模拟。物理碰撞体的形状不需要严格和物体一致,只要能表示其物理形状即可。比如一个复杂的人,我们可以用一个胶囊体来定义其碰撞形状。
  • Unity内建了很多碰撞体,以下时常用的碰撞体:
  • BoxCollider 立方体碰撞体SphereCollider 球形碰撞体CapsuleCollider 胶囊碰撞体MeshCollider 从对象的网格创建碰撞体。MeshCollider之间不支持碰撞检测,效率太低。可以将MeshCollider的Convex选项打开,则能支持MeshCollider之间的碰撞检测,同时提高性能。WheelCollider 创建载具的轮子的碰撞体TerrainCollider 处理Unity地形系统的碰撞
  • 2D中相应的BoxCollider2D,CircleCollider2D,CapsuleCollider2D,以及其它转为2D建立的碰撞体类型,如PolygonCollider2D。
  • Box,Sphere,Circle,Capsule这些简单碰撞体的效率相对都是较高的,建议使用这些简碰撞体。
  • 当一个精灵比较复杂时(如创建的一个人物角色),可以采用组合碰撞体的方式,对角色不同的身体部位分别采用不同的碰撞体组件。
触发器:
  • 为碰撞体组件选中is Trigger复选框,会发现碰撞体不再组织移动了。触发器用于检测碰撞但不产生碰撞。在2D项目中要使用OnTriggerEnter2D方法。
  • code:
private void OnTriggerEnter2D(Collider2D collision)
    {
        Debug.Log($"与{collision}发生碰撞了!");
    }
  • OnColliderEnter(2D) 和OnTriggerEnter(2D):
  • OnCollisionEnter方法必须是在两个碰撞物体都不勾选isTrigger的前提下才能进入,反之只要勾选一个isTrigger那么就能进入OnTriggerEnter方法。
  • OnCollisionEnter和OnTriggerEnter是冲突的不能同时存在的。
  • OnTriggerEnter和OnCollisionEnter的选择。
  • 如果想实现两个刚体物理的实际碰撞效果时候用OnCollisionEnter,Unity引擎会自动处理刚体碰撞的效果。
  • 如果想在两个物体碰撞后自己处理碰撞事件用OnTriggerEnter。
6)Unity中的世界交互:
可收集的对象:
  • 例如玩家通过触发器实现回血或者扣血的操作:
//扣血的类

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

public class collectibleHealth : MonoBehaviour
{
    public int healther = -1;

    private void OnTriggerEnter2D(Collider2D collision)
    {
        Debug.Log($"与{collision}发生碰撞了!");

        //获取主角游戏对象
        RubyMover RM = collision.GetComponent<RubyMover>();
        if(RM!=null)
        {
            //调用改变血量的方法时,建议再加一层判断,血满时无法调用这个方法。
            RM.ChangeHealth(healther);
            Destroy(gameObject);
        }
        
    }   
}

//玩家类
//玩家类中的一些属性(例如血量或者其他的一些私有属性)建议设置为私有,并提供get/set方法。
public class RubyMover : MonoBehaviour
{
    public float speed = 0.1f;
    Rigidbody2D rigidbody;
    float x;
    float y;
    int CurHealth;
    int MaxHealth;

    // Start is called before the first frame update
    void Start()
    {
        rigidbody = GetComponent<Rigidbody2D>();
        MaxHealth = CurHealth = 5;
    }
    
    // Update is called once per frame
    void Update()
    {
         x = Input.GetAxis("Horizontal");
         y = Input.GetAxis("Vertical");
    }

    private void FixedUpdate()
    {
        Vector2 position = rigidbody.position;
        position.x += 0.1f * x * speed * Time.deltaTime;
        position.y += 0.1f * y * speed * Time.deltaTime;
        rigidbody.MovePosition(position);
    }

    //有关血量更改的代码
    public void ChangeHealth(int HCer)
    {
        CurHealth = Mathf.Clamp(CurHealth + HCer, 0, MaxHealth);
        Debug.Log("当前生命值:" + CurHealth + "/" + MaxHealth);
    }
}
设置摄像机跟随Player移动(使用自带脚本的的方式):
  • 现在package manager里面安装Cinemachine,新建一个camera对象,找到CinemachineVirtualCamera,在Extension中选择自己需要的脚本组件类型。在Hierarchy中新建一个游戏对象,为这个游戏对象添加Collider组件、新建layer,同时在这个游戏对象中选中新建的Layer,在Edit->project settings->Physics 2D中取消新建Layer与所有的物体的碰撞。
  • 2D游戏详细教程可参考:
7)U2D中的精灵动画:
  • 参考网页教程,百度资源。
8)一些问题及解决方式:
创建的角色在于环境中的一些组件发生碰撞时角色发生旋转、抖动的问题:
  • 旋转:在2D项目中,选中创建的角色,在Inspector面板中找到添加的Rigidbody 2D脚本,在Constrains勾选Freeze Rotation Z。
  • 抖动:引起抖动的原因可能是你在角色移动脚本里写代码是利用的transform来移动角色。此时脚本移动角色的位置会和碰撞体矛盾从而引起抖动。(说白了就是你在脚本中强行将角色移到一个碰撞体的碰撞范围内,这个碰撞体又将角色弹了回来)。解决的方法是:利用刚体的移动代替transfoem的移动方式。代码脚本如下:
//原transform移动方式见上


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

public class RubyMover : MonoBehaviour
{
    public float speed = 0.1f;
    Rigidbody2D rigidbody;
    float x;
    float y;
    // Start is called before the first frame update
    void Start()
    {
        rigidbody = GetComponent<Rigidbody2D>();
    }
    
    // Update is called once per frame
    void Update()
    {
         x = Input.GetAxis("Horizontal");
         y = Input.GetAxis("Vertical");

        

    }
    //使物理计算保持稳定,定期更新。只要你想直接影响物体组件或刚体,就要使用这个函数。
    private void FixedUpdate()
    {
        Vector2 position = rigidbody.position;
        position.x += 0.1f * x * speed * Time.deltaTime;
        position.y += 0.1f * y * speed * Time.deltaTime;
        rigidbody.MovePosition(position);
    }
}