模型分析
视差滚动的本质就是不同深度(即transform.position.z,以下简写)的图片移动速率不同。那么不同层级的移动速率有什么样的特点呢?
想象一下,摄像机拍摄玩家在正中心位置,当玩家往右移动个单位距离时,摄像机也移动相同的距离。考虑3种极限情况:
-
和玩家的相同
这一层的图片不需要移动,在摄像机画面中,它自然而然地会相对往左移动个单位距离,因此我们可以把这一层的设为。
-
处于摄像机的远裁切面
我们把处于远裁切面定义为无穷远处,很明显,处于无穷远处的物体在摄像机看来似乎是定格在画面中的,因此该层的图片需要相同的往右移动个单位距离以保持相对静止,这一层的设为。
-
处于摄像机的近裁切面
与远裁切面正好相反,这一层的图片需要与角色移动方向相反,我们人为地定义这一层的为。
因此,我们可以得出结论,在摄像机近、远裁切面之间不同的区间为,与玩家所在的系数是,与正相关。所以,我们只需要把背景层的值映射到这个区间即可。
计算公式
:摄像机的transform.position.z
:玩家的transform.position.z
: 当前物体的transform.position.z
:计算得到的摄像机近裁切面的值,是摄像机的nearClipPlane
:计算得到的摄像机远裁切面的值,是摄像机的farClipPlane
-
当处于前景层时
-
当处于背景层时
代码形式
using UnityEngine;
public class Parallax : MonoBehaviour
{
[SerializeField] private Transform player;
private Vector3 startPosition;
private Vector3 cameraStartPosition;
private float cameraStartX => cameraStartPosition.x;
private float cameraTravelX => Camera.main.transform.position.x - cameraStartX;
private float distanceFromPlayer => transform.position.z - player.position.z;
private float playerToClippingPlaneLength => distanceFromPlayer > 0
? Camera.main.transform.position.z + Camera.main.farClipPlane - player.position.z
: player.position.z - Camera.main.transform.position.z - Camera.main.nearClipPlane;
private float parallaxFactor => distanceFromPlayer / playerToClippingPlaneLength;
private void Awake()
{
startPosition = transform.position;
cameraStartPosition = Camera.main.transform.position;
}
private void Update()
{
float newX = startPosition.x + cameraTravelX * parallaxFactor;
transform.position = new Vector3(newX, startPosition.y, startPosition.z);
}
}
在Unity编辑器中实操
- 设置相机为正交相机,设置近、远裁切面
- 导入背景图,为每一层背景设置不同的
transform.position.z,需要确保能被摄像机渲染出来。图层的Draw Mode设置为Tiled,把Width调整为较大的值(这个要根据你实际的场景和需求设置,若不够长,当玩家走到边缘时会穿帮)。
-
为了方便测试,这里简单地把主摄像机设为
Player的子物体以快速实现跟随效果,同时添加了玩家移动脚本以展示效果。 -
运行后,效果如图:
优化
我们注意到,我们需要手动设置Tiled模式下宽度的值,导致场景中有一长串图片很影响开发模式下的观感。有没有方法使图片保持简洁且效果不变呢?只需通过Shader修改纹理采样的偏移即可!
这里通过Shader Graph实现,暴露OffsetX变量在代码中修改即可。
调整步骤
- 使用创建的Shader Graph生成数个Material,将Texture修改为背景图
- Hierarchy窗口中创建Plane,修改Material为上步创建的材质球;修改Plane的Rotation为使得能在画面中正确显示;Scale属性中的
x设置为你的背景图片的,Scale属性中的z设置为 - 将这些背景层的对象移动成为Player的子物体
- 修改Parallax脚本为如下,并添加到创建的Plane上(修改部分做了注释说明,此处不再赘述)
using UnityEngine;
public class Parallax : MonoBehaviour
{
[SerializeField] private Transform player;
private MeshRenderer meshRenderer;
private Vector3 cameraStartPosition;
// 计算得到图片的宽度
private float imageWidth => Camera.main.orthographicSize * 2 * transform.lossyScale.x;
private float cameraStartX => cameraStartPosition.x;
private float cameraTravelX => Camera.main.transform.position.x - cameraStartX;
private float distanceFromPlayer => transform.position.z - player.position.z;
private float playerToClippingPlaneLength => distanceFromPlayer > 0
? Camera.main.transform.position.z + Camera.main.farClipPlane - player.position.z
: player.position.z - Camera.main.transform.position.z - Camera.main.nearClipPlane;
private float parallaxFactor => distanceFromPlayer / playerToClippingPlaneLength;
private void Awake()
{
meshRenderer = GetComponent<MeshRenderer>();
cameraStartPosition = Camera.main.transform.position;
}
private void Update()
{
// 偏移量是移动距离与图片宽度的比值
// 注意这里乘的系数是1 - parallaxFactor
meshRenderer.material.SetFloat("_OffsetX", (1 - parallaxFactor) * cameraTravelX / imageWidth);
}
}
我们看一下运行效果
最后,这里只实现了方向的视差滚动,如果需要或只要方向(比如飞机大战)视差滚动,那么只需修改少量代码、shader稍作修改即可,这里不再演示。