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游戏中瓦片地图的创建和使用:
-
瓦片地图工作流程
-
预处理 sprite 资源:将图片资源拖拽到 project 中,生成 sprite;然后一般需要进行切割 slice ,将其配置成需要的各个 tile;
-
创建要在其上绘制瓦片的瓦片地图。此过程中还会自动创建 Grid 游戏对象作为瓦片地图的父级。
-
直接创建瓦片资源,或者通过将用作瓦片素材的精灵带入 Tile Palette 窗口自动生成瓦片。
-
创建一个包含瓦片资源的 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移动方式见上
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);
}
}
-