Unity-2D

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 轴
        • 【Unity-2D】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 轴上基于精灵的位置来绘制精灵。
    Unity-2D
    文章图片

    • 按 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();
      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();
      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游戏详细教程可参考:
      • https://learn.unity.com/tutorial/she-xiang-ji-cinemachine?uv=2020.3&projectId=5facf921edbc2a2003a58d3a#6073df91edbc2a001e1f55c2
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();
      }

      // 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);
      }
      }

    推荐阅读