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.自动创建关闭
- Skybox is disabled for new scenes:天空盒默认关闭
- 使用静态精灵创建Player:
- 精灵 Sprite 是 Unity 中 2D 素材的默认存在形式,是 Unity 中的 2D 图形对象。
- 在 2D 游戏中,使用 2D 素材的过程: PNG(JPG 等)----> Sprite ----> GameObject
- 精灵 Sprite 是 Unity 中 2D 素材的默认存在形式,是 Unity 中的 2D 图形对象。
- 铺垫:
- Vector2 二维向量
- 在数学中,Vector 向量/矢量指的是带方向的线段
- 在 Unity 中,Transform 值使用 x 表示水平位置,使用 y 表示垂直位置,使用 z 表示深度。这 3 个数值组成一个坐标。由于此游戏是 2D 游戏,你无需存储 z 轴位置,因此你可以在此处使用 Vector2 来仅存储 x 和 y 位置。
- Transform 中 position 的类型,也是 Vector2。C# 这种强类型语言,赋值时,左右必须是同一类型才能进行
- 在数学中,Vector 向量/矢量指的是带方向的线段
- Unity 默认 Input Manager 设置
- 在 Unity 项目设置中,可以通过 Input Manager 进行默认的游戏输入控制设置 Edit > Project Settings > Input
- 键盘按键,以 2 个键来定义轴:
- 负值键 negative button,被按下时将轴设置为 -1
- 正值键 positive button ,被按下时将轴设置为 1
- 负值键 negative button,被按下时将轴设置为 -1
- Axis 轴 Axes 是它的负数形式
- Horizontal Axis: 水平轴 对应 X 轴
- 【Unity-2D】Vertical Axis:纵轴 对应 Y 轴
- Horizontal Axis: 水平轴 对应 X 轴
- 在 Unity 项目设置中,可以通过 Input Manager 进行默认的游戏输入控制设置 Edit > Project Settings > Input
- Input类
- 使用该类来读取传统游戏输入中设置的轴/鼠标/按键,以及访问移动设备上的多点触控/加速度计数据。若要使用输入来进行任何类型的移动行为,请使用 Input.GetAxis。 它为您提供平滑且可配置的输入 - 可以映射到键盘、游戏杆或鼠标。 请将 Input.GetButton 仅用于事件等操作。不要将它用于移动操作。Input.GetAxis 将使脚本代码更简洁。
- 使用该类来读取传统游戏输入中设置的轴/鼠标/按键,以及访问移动设备上的多点触控/加速度计数据。若要使用输入来进行任何类型的移动行为,请使用 Input.GetAxis。 它为您提供平滑且可配置的输入 - 可以映射到键盘、游戏杆或鼠标。 请将 Input.GetButton 仅用于事件等操作。不要将它用于移动操作。Input.GetAxis 将使脚本代码更简洁。
- 时间和帧率
- 当前的代码中,帧数越高,同一时间内,执行 Update 的次数越多,角色移动速度越快。如果游戏以每秒 60 帧的速度运行,那么 Ruby 将移动 0.1 _ 60,因此每秒移动 6 个单位。但是,如果游戏以每秒 10 帧的速度运行,就像刚刚让游戏运行的那样,那么 Ruby 仅移动 0.1 _ 10,因此每秒移动 1 个单位!
- 如果一个玩家的计算机非常陈旧,只能以每秒 30 帧的速度运行游戏,而另一个玩家的计算机能以每秒 120 帧的速度运行游戏,那么这两个玩家的主角的移动速度会有很大差异。这样就会使游戏的难易程度提高或降低,具体取决于运行游戏的计算机。
- 而帧数是由硬件水平影响的(越好越高),不同电脑中,会导致游戏效果完全不同
- 当前的代码中,帧数越高,同一时间内,执行 Update 的次数越多,角色移动速度越快。如果游戏以每秒 60 帧的速度运行,那么 Ruby 将移动 0.1 _ 60,因此每秒移动 6 个单位。但是,如果游戏以每秒 10 帧的速度运行,就像刚刚让游戏运行的那样,那么 Ruby 仅移动 0.1 _ 10,因此每秒移动 1 个单位!
- Vector2 二维向量
- 新建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游戏中瓦片地图的创建和使用:- 瓦片地图工作流程
- 预处理 sprite 资源:将图片资源拖拽到 project 中,生成 sprite;然后一般需要进行切割 slice ,将其配置成需要的各个 tile;
- 创建要在其上绘制瓦片的瓦片地图。此过程中还会自动创建 Grid 游戏对象作为瓦片地图的父级。
- 直接创建瓦片资源,或者通过将用作瓦片素材的精灵带入 Tile Palette 窗口自动生成瓦片。
- 创建一个包含瓦片资源的 Tile Palette,并使用各种笔刷来绘制到瓦片地图上。
- 预处理 sprite 资源:将图片资源拖拽到 project 中,生成 sprite;然后一般需要进行切割 slice ,将其配置成需要的各个 tile;
- 瓦片地图的高级使用
- 使用普通的瓦片地图,构建整个世界,一个一个格子用笔刷来填充,非常费时,Unity 在不断地升级中,添加了很多种快速构建瓦片地图的方式,掌握了这些方法,能够极大减少绘制地图所用的时间。
- 编程瓦片 Scriptable Tile
- Unity 支持用代码创建自己的 Tile 类,自己书写瓦片的绘制规则。还可以为瓦片创建自定义编辑器。这与脚本化对象的自定义编辑器的工作方式相同。创建一个继承自 TileBase(或 TileBase 的任何有用子类,如 Tile)的新类。重写新的 Tile 类所需的所有方法。
- Unity 支持用代码创建自己的 Tile 类,自己书写瓦片的绘制规则。还可以为瓦片创建自定义编辑器。这与脚本化对象的自定义编辑器的工作方式相同。创建一个继承自 TileBase(或 TileBase 的任何有用子类,如 Tile)的新类。重写新的 Tile 类所需的所有方法。
- 编程画笔 Scriptable Brush
- Unity 也支持创建自己的 Brush 类,设计适合自己游戏的网格画笔。
创建一个继承自 GridBrushBase(或 GridBrushBase 的任何有用子类,如 GridBrush)的新类。重写新的 Brush 类所需的所有方法。创建可编程画笔后,画笔将列在 Palette 窗口的 Brushes 下拉选单 中。默认情况下,可编程画笔脚本的实例将经过实例化并存储在项目的 Library 文件夹中。对画笔属性的任何修改都会存储在该实例中。如果希望该画笔有多个具备不同属性的副本,可在项目中将画笔实例化为资源。这些画笔资源将在 Brush 下拉选单中单独列出。
- Unity 也支持创建自己的 Brush 类,设计适合自己游戏的网格画笔。
- 编程瓦片 Scriptable Tile
- 使用普通的瓦片地图,构建整个世界,一个一个格子用笔刷来填充,非常费时,Unity 在不断地升级中,添加了很多种快速构建瓦片地图的方式,掌握了这些方法,能够极大减少绘制地图所用的时间。
- 2D Tilemap Extras (2D 瓦片地图扩展)
- Animated Tile 动画瓦片
- 动画瓦片在游戏运行时,按顺序显示 Sprite 列表以创建逐帧动画
- 动画瓦片在游戏运行时,按顺序显示 Sprite 列表以创建逐帧动画
- Rule Tile 规则瓦片
- 可以为每个瓦片创建规则,在绘制时,unity 会自动响应这些规则,绘制地图时更加智能
- RuleTile 使用步骤:
- 准备 Tile 素材,配置素材属性,分割素材;
- 新建 RuleTile,为其添加规则,设置每个 Tile 的相邻规则;
- 将设置好的 RuleTile 拖拽到 Tile Palette 中,就可以使用了。
- 准备 Tile 素材,配置素材属性,分割素材;
- 可以为每个瓦片创建规则,在绘制时,unity 会自动响应这些规则,绘制地图时更加智能
- Rule Override Tile / Advanced Rule Override Tile 规则覆盖瓦片
- 可以用已经生成好的 Rule Tile,作为 Rule Override Tile 的规则来源,只替换对应的瓦片素材,而沿用原先的规则,可以快速的创建规则瓦片的方法。
- 可以用已经生成好的 Rule Tile,作为 Rule Override Tile 的规则来源,只替换对应的瓦片素材,而沿用原先的规则,可以快速的创建规则瓦片的方法。
- Animated Tile 动画瓦片
- 瓦片地图工作流程
- 伪透视图
- 透视图指的是有深度、距离感的图,一般要三维中的深度轴来表现场景的深度,而二维游戏中没有这个深度,只能通过前后来仿造深度效果,称为“伪透视图”
- 先前通过调整瓦片的 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 轴上基于精灵的位置来绘制精灵。
- Edit > Project Settings > Graphics > Camera Settings > Transparency Sort Mode = Custom Axis > Transparency Sort Axis x = 0 / y = 1 / z = 0
文章图片
- 按 Play 以进入运行模式并测试你的更改。现在,你的角色比箱子高时,角色应该会绘制在箱子的后面;而角色比箱子低时,绘制在箱子的前面。
- Sprite 轴心 pivot
- 每个 Sprite 都有一个轴心(中心点),Unity 根据 pivot 对 sprite 进行定位,这个 pivot 可以在 sprite editor 中调整,可以将其设置到 sprite 上任意位置
- 在 2D Rpg 游戏场景中的游戏对象,如果想要实现较为真实的 “伪透视” 效果,最好将游戏对象的 sprite 中 pivot 都设置到素材的最下方正中。
- 然后将游戏对象的 Sprite Sort Point 由 Center 改为 Pivot 即可.
- 每个 Sprite 都有一个轴心(中心点),Unity 根据 pivot 对 sprite 进行定位,这个 pivot 可以在 sprite editor 中调整,可以将其设置到 sprite 上任意位置
- 透视图指的是有深度、距离感的图,一般要三维中的深度轴来表现场景的深度,而二维游戏中没有这个深度,只能通过前后来仿造深度效果,称为“伪透视图”
- 铺垫:
- Unity中内置的物理系统可以模仿地球上的物理系统,使Unity中创建的一切精灵都具有类似地球上的物理属性。
- Unity中内置的物理系统可以模仿地球上的物理系统,使Unity中创建的一切精灵都具有类似地球上的物理属性。
- 物理系统中比较重要的脚本组件:
- Rigidbody :项目中的刚体组件
- 定义了对象收到外力后,如何模拟其行为,如翻滚,掉落。当为一个对象添加了 Rigidbody 组件后,就会模拟受重力而掉落。如果再加上collider组件,则会响应外部的力,而运动。添加 Rigidbody后,我们就不能通过 transform 组件来移动物体了,只能由物理系统来模拟驱动。当然这不是绝对的,某些特定情境,需要关掉物理模拟,将物体摆放到指定位置,再重新打开物理模拟。有时,我们希望对象参与物理模拟,但是其行为还是由逻辑控制,比如,对于游戏内的NPC,我们需要由代码控制其运动,同时又需要添加rigidbody,这样才能被Trigger检测到,对于这种情况,可以将 Rigidbody 设置为动力学物体(Is Kinemiac)。
- 定义了对象收到外力后,如何模拟其行为,如翻滚,掉落。当为一个对象添加了 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这些简单碰撞体的效率相对都是较高的,建议使用这些简碰撞体。
- BoxCollider 立方体碰撞体 SphereCollider 球形碰撞体 CapsuleCollider 胶囊碰撞体 MeshCollider 从对象的网格创建碰撞体。MeshCollider之间不支持碰撞检测,效率太低。可以将MeshCollider的Convex选项打开,则能支持MeshCollider之间的碰撞检测,同时提高性能。 WheelCollider 创建载具的轮子的碰撞体 TerrainCollider 处理Unity地形系统的碰撞
- 当一个精灵比较复杂时(如创建的一个人物角色),可以采用组合碰撞体的方式,对角色不同的身体部位分别采用不同的碰撞体组件。
- 定义了物体的形状来进行碰撞模拟。物理碰撞体的形状不需要严格和物体一致,只要能表示其物理形状即可。比如一个复杂的人,我们可以用一个胶囊体来定义其碰撞形状。
- 触发器:
- 为碰撞体组件选中is Trigger复选框,会发现碰撞体不再组织移动了。触发器用于检测碰撞但不产生碰撞。在2D项目中要使用OnTriggerEnter2D方法。
- code:
-
private void OnTriggerEnter2D(Collider2D collision)
{
Debug.Log($"与{collision}发生碰撞了!");
}
- 为碰撞体组件选中is Trigger复选框,会发现碰撞体不再组织移动了。触发器用于检测碰撞但不产生碰撞。在2D项目中要使用OnTriggerEnter2D方法。
- OnColliderEnter(2D) 和OnTriggerEnter(2D):
- OnCollisionEnter方法必须是在两个碰撞物体都不勾选isTrigger的前提下才能进入,反之只要勾选一个isTrigger那么就能进入OnTriggerEnter方法。
- OnCollisionEnter和OnTriggerEnter是冲突的不能同时存在的。
- OnTriggerEnter和OnCollisionEnter的选择。
- 如果想实现两个刚体物理的实际碰撞效果时候用OnCollisionEnter,Unity引擎会自动处理刚体碰撞的效果。
- 如果想在两个物体碰撞后自己处理碰撞事件用OnTriggerEnter。
- 如果想实现两个刚体物理的实际碰撞效果时候用OnCollisionEnter,Unity引擎会自动处理刚体碰撞的效果。
- OnCollisionEnter方法必须是在两个碰撞物体都不勾选isTrigger的前提下才能进入,反之只要勾选一个isTrigger那么就能进入OnTriggerEnter方法。
- Rigidbody :项目中的刚体组件
- 可收集的对象:
- 例如玩家通过触发器实现回血或者扣血的操作:
-
//扣血的类
?
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
- https://learn.unity.com/tutorial/she-xiang-ji-cinemachine?uv=2020.3&projectId=5facf921edbc2a2003a58d3a#6073df91edbc2a001e1f55c2
- 现在package manager里面安装Cinemachine,新建一个camera对象,找到CinemachineVirtualCamera,在Extension中选择自己需要的脚本组件类型。在Hierarchy中新建一个游戏对象,为这个游戏对象添加Collider组件、新建layer,同时在这个游戏对象中选中新建的Layer,在Edit->project settings->Physics 2D中取消新建Layer与所有的物体的碰撞。
- 参考网页教程,百度资源。
- 创建的角色在于环境中的一些组件发生碰撞时角色发生旋转、抖动的问题:
- 旋转:在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);
}
}
- 旋转:在2D项目中,选中创建的角色,在Inspector面板中找到添加的Rigidbody 2D脚本,在Constrains勾选Freeze Rotation Z。