目录
3.2 Order in Layer 图层中Renderer对象的顺序
Unity入门教程
【unity2021入门教程】60-2D游戏开发教程系列-03-RubyAdventure2DRpg官方教程-08-伪透视效果实现_哔哩哔哩_bilibili
入门的学习进程是跟着这个教程来的。
我们在进行游戏制作时,不可避免地需要处理一些场景中物体之间的前后关系。比如以下情形
这个情景就体现了Camera视角中前后关系从“玩家在前,建筑物在后”变成了“玩家在后,建筑物在前”。因此在游戏运行过程中,在动的游戏对象与静态、动态的其他游戏对象之间遮挡关系是会变化的。合理并有效得处理处理这种“谁挡住谁”的关系在游戏制作中是十分重要的!既保证了游戏体验感和也提升了游戏画面的真实感。
需要了解的是,遮挡关系是2D和3D游戏中都需要处理的问题,但由于2D和3D游戏本身的区别,二者的处理方法截然不同。
从3D游戏角度来看,由于它是3D的,可以直接通过透视用深度来表达物体的前后关系,利用深度轴处理遮挡,这根本不算是一个需要着重解决的问题。
但对于2D游戏,不管玩家是什么视角,游戏本身就是在一个平面上进行绘制的,如果存在所谓的深度,画面中所有物体的深度都应该相同!为了处理2D中的遮挡问题,就提出了这样一个伪透视的概念。(伪透视这个词是B站教程的UP主那学到的)
在继续进行之前,还要插入一个概念,就是关于Unity中图层的一些特性。
在之前的概念学习中,精灵渲染器Sprite Renderer和瓦片渲染器Tilemap Renderer中都提到了关于图层的属性:
需要注意的一点是:渲染器之间的图层关系都是共用的!也就是说Tilemap和Sprite渲染器共用同一套图层系统。
跟Sorting Layer中的关系一样,数字越大,位置越在前面;数字越小,位置越靠后。
注意,为了方便操作,这个Order数字可以为负数,也就是可以是-1,-100....
例如,拿RubyAdventure中的资源举例:
当前创建了一个瓦片地图,它在图层0(Default),图层中的顺序是-1.
再创建两个精灵游戏对象, 也在图层0,图层中的顺序都是0
遮挡结果:
如果此时我把瓦片地图放在Layer 1中,发现:
如果把瓦片地图放在Layer0但顺序是0:
相信上述例子就能体会到不同图层的顺序和不同对象在图层中的顺序之间的关系。
2D游戏中通过前后覆盖关系来伪造3D中的深度效果,把这种关系称为伪透视图。
2D游戏中,上面提到的游戏对象间的前后关系是由Y轴决定的。在Y轴上:
Unity根据游戏对象的y坐标值来绘制游戏对象,y坐标值较大的优先绘制,y坐标值较小的后绘制,就能保证前后的覆盖关系。
实现伪透视第一步,利用在同一图层下的顺序关系。
场景中加入Tilemap、一棵树和一个箱子,加入玩家,其中:玩家Order=-1,树Order=0.
最终的运行结果
确实体现了遮挡情况,但是会出现一个问题:无论怎么移动,玩家始终被树遮挡。
这并不是我们想要的遮挡关系会根据移动变化的实时遮挡的效果。
下面是一些实现实时遮挡解决方案的介绍。
引擎为我们提供了一个便捷的方法,按优先级划分的透明队列排列顺序。
菜单栏Edit -> Project Settings -> Graphics -> Camera Settings:
透明队列排列方式有以下几种:
这个才是这个方法的主导,前面介绍了2D游戏中判断前后的通常是y坐标值,因此这里会选择自定义轴排列并将轴设置成y轴。也就是Transparency Sort Axis中Y设置成1(默认是Z轴=1)
这里已经有可以改变遮挡的效果了,但是并没有那么的精准,问题出在了Sprite中心位置的选择上。
这是精灵的其中一个属性,表示精灵旋轴心点。为了更精确地实现,这里应该把玩家和树对应精灵的轴心都移动到底部。
有两种改变方式,一种是直接选择Pivot属性为Buttom,一种可以通过Sprite Editor修改Pivot
将Sprite的Pivot都改成Buttom后:
这就是引擎自带的处理方法。
这个方法是我在b站看到的一个UP主介绍的方法:Unity 2D游戏中的遮挡问题的解决方案(人物与建筑物、Tilemap的先后关系)以及3D游戏中被遮挡部分的特效
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- public class OrderController : MonoBehaviour
- {
- // Start is called before the first frame update
- void Start()
- {
-
- }
-
- private List
SpriteDynamic; - private List
SpriteStatic;//让树、箱子等类型为static -
- private void Awake()
- {
- SpriteRenderer[] spriteRenderers = GetComponentsInChildren
(); - SpriteDynamic = new List
(); - SpriteStatic = new List
(); - for(int i = 0; i < spriteRenderers.Length; i++)
- {
- if (spriteRenderers[i].gameObject.isStatic)
- {
- //sorting order=y*-100
- spriteRenderers[i].sortingOrder = Mathf.RoundToInt(spriteRenderers[i].transform.position.y * -100);
- SpriteStatic.Add(spriteRenderers[i]);
- }
- else //isDynamic
- {
- SpriteDynamic.Add(spriteRenderers[i]);
- }
- }
- }
-
- // Update is called once per frame
- void Update()
- {
- //动态的需要一直更新,因为玩家是在移动的
- for(int i = 0; i < SpriteDynamic.Count; i++)
- {
- SpriteDynamic[i].sortingOrder = Mathf.RoundToInt(SpriteDynamic[i].transform.position.y * -100);
- }
- }
- }
同时把树和箱子的游戏对象都勾上Static:
给玩家和箱子都挂上脚本后:
发现这里跟树的交互还是不怎么流畅,这里可以改树和玩家精灵分别对应的Pivot Point,改为Buttom,修改后的效果:
对比发现,脚本这个方法,在处理边界遮挡上的效果看起来会比只用引擎的看上去舒服。
这个方法也是方案2的视频评论里看到的:
除了上面提到的两种方案外,这个思路也是可行的,之后如果有时间会尝试给他实现出来。
这也是那个视频评论中看到的思路,目前项目制作学习还没到碰撞体设置那一块儿,所以这里先放个坑,之后回来补上。
对比几种处理方案,其实方案1使用引擎来已经能解决大部分的问题了,遇到比较多的建筑物就可以拆分成几个小的,也能规避建筑物过大中心点太偏移带来的问题。
但是当遇到一个难以拆分的大物体(比如文章中我用到的很大一棵树的例子,把一棵树拆分也不太现实),这个时候用方案2是不是更好一点呢?
基于我的简单尝试做了以下归纳:
关于2D游戏中遮蔽问题的处理方案暂时就总结到这里,后续会加以补充。