Unity中无尽的2D背景

Tulenber 3 April, 2020 ⸱ Intermediate ⸱ 5 min ⸱ 2019.3.7f1 ⸱

在本文中,我们将研究如何应用暗物质引擎方法来创建无限的2D背景。

如果您使用公共交通工具,您可能已经注意到乘客之间最受欢迎的移动游戏类型之一是无尽的跑步者,例如Subway Surfers。这种体裁的主要特征是无限移动的元素(角色或其他物体),该元素通常独立地朝着障碍物前进,并且给出控制仅是为了避免这些障碍物。无限移动意味着玩家的移动具有无限的可用空间,创建它并非易事。在这篇文章中, 我们开启了一个循环,在循环中我们将考虑有助于创造无限世界的元素。

如果您在Internet上搜索有关Unity无限空间的信息,那么最流行的教程之一将是为Sidescrollers创建无限背景对象。在我们的案例中,我们将使任务复杂化一些,并尝试为自上而下的相机制作无尽的背景。例如,让我们实现一个在无限外层空间背景中移动的宇宙飞船模型。

问题

当有人要您制作在太空中移动的船时,想到的第一件事是添加船模型,为其设置速度矢量,安装摄像头,放置背景,然后沿乘以矢量的方向开始移动船通过Time.deltaTime。您可以丢弃背景大小有限的显而易见的东西,无论如何都必须解决这些问题。无限移动的主要问题将是Unity坐标空间,该空间与float类型的变量相关联,并且精度会随着坐标的增加而降低。有关问题的更准确描述,您可以阅读这篇文章"浮点错误和大规模世界"

暗物质引擎

要解决此问题,可以使用操作Futurama的暗物质引擎的方法。简要描述其作用原理,引擎不是移动船,而是移动船周围的空间。在我们的例子中,我们将沿相反的方向移动所有物体,而不是沿着速度矢量移动船。因此,如果我们的相机安装在太空飞船上,那么我们将始终停留在原点,并且精度不会因距离的增加而下降。另外,在背景无限的情况下,请选择我们的位置,因为原点看起来比绑定到空间中的任何其他点都更优雅。

目的

使宇宙飞船沿着无尽的2D背景移动。 单击屏幕的右侧/左侧,船可以绕Z轴旋转。 摄像机始终与船的运动矢量对齐。

我不会分发此示例中使用的资产。为您找到合适的资源也是很多工作,我建议您练习一下,如果您不知道从哪里开始,可以访问免费的itch.io或在Internet上搜索"免费游戏资产"

我们需要船的纹理和背景的纹理。背景纹理的主要要求是在垂直和水平方向上都可以重复。

  1. 将飞船和背景纹理添加到项目中,对于背景,您需要设置Mesh type - Full rectWrap mode - Repeat
    Add sprites
  2. 创建一个对象结构
    Object structure
  3. 为船添加纹理
    Spaceship
  4. 添加背景纹理,设置Draw mode - TiledOrder in Layer = -1
    Add scenes
  5. 创建一个UniverseHandler脚本,将其添加到Universe对象
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
using System;
using UnityEngine;

public class UnevirseHandler : MonoBehaviour
{
    // 对场景对象的引用
    [SerializeField] private Camera mainCamera = null;
    [SerializeField] private GameObject ship = null;
    [SerializeField] private GameObject space = null;
    
    // 可能的相机视图的半径
    private float _spaceCircleRadius = 0;

    // 原始背景对象尺寸
    private float _backgroundOriginalSizeX = 0;
    private float _backgroundOriginalSizeY = 0;

    // 行驶方向
    private Vector3 _moveVector;
    // 弧度旋转速度
    private float _rotationSpeed = 1f;

    // 辅助变量
    private bool _mousePressed = false;
    private float _halfScreenWidth = 0;
    
    void Start()
    {
        // 出发方向
        _moveVector = new Vector3(0, 1.5f, 0);
        // 用于确定旋转方向
        _halfScreenWidth = Screen.width / 2f;
        
        // 原始背景尺寸
        SpriteRenderer sr = space.GetComponent<SpriteRenderer>();
        var originalSize = sr.size;
        _backgroundOriginalSizeX = originalSize.x;
        _backgroundOriginalSizeY = originalSize.y;

        // 相机高度等于正射影像大小
        float orthographicSize = mainCamera.orthographicSize;
        // 相机宽度等于正投影尺寸乘以纵横比
        float screenAspect = (float)Screen.width / (float)Screen.height;
        // 描述相机的圆的半径
        _spaceCircleRadius = Mathf.Sqrt(orthographicSize * screenAspect * orthographicSize * screenAspect + orthographicSize * orthographicSize);

        // 最终的背景精灵大小应允许您在任何方向上移动一种基本的背景纹理大小,并且在所有方向上都与摄影机的半径重叠
        sr.size = new Vector2(_spaceCircleRadius * 2 + _backgroundOriginalSizeX * 2, _spaceCircleRadius * 2 + _backgroundOriginalSizeY * 2);
    }

    void Update()
    {
        // 通过单击鼠标按钮更改移动方向
        if (Input.GetMouseButtonDown (0)) {
            _mousePressed = true;
        }

        if (Input.GetMouseButtonUp(0))
        {
            _mousePressed = false;
        }
        
        if (_mousePressed)
        {
            // 根据发生点击的屏幕侧面确定旋转方向
            int rotation = Input.mousePosition.x >= _halfScreenWidth ? -1 : 1;

            // 方向向量旋转的计算
            float xComp = (float)(_moveVector.x * Math.Cos(_rotationSpeed * rotation * Time.deltaTime) - _moveVector.y * Math.Sin(_rotationSpeed * rotation * Time.deltaTime));
            float yComp = (float) (_moveVector.x * Math.Sin(_rotationSpeed * rotation * Time.deltaTime) + _moveVector.y * Math.Cos(_rotationSpeed * rotation * Time.deltaTime));
            _moveVector = new Vector3(xComp, yComp,0);

            // 沿方向矢量旋转船和摄像机的精灵
            float rotZ = Mathf.Atan2(_moveVector.y, _moveVector.x) * Mathf.Rad2Deg;
            ship.transform.rotation = Quaternion.Euler(0f, 0f, rotZ - 90);
            mainCamera.transform.rotation = Quaternion.Euler(0f, 0f, rotZ - 90);
        }

        // 反向移动背景
        space.transform.Translate(-_moveVector.x * Time.deltaTime, -_moveVector.y * Time.deltaTime, 0);

        // 当背景在任何方向上都达到与原始大小相等的偏移时,我们将其精确地返回到该方向上的原点
        if (space.transform.position.x >= _backgroundOriginalSizeX)
        {
            space.transform.Translate(-_backgroundOriginalSizeX, 0, 0);
        }
        if (space.transform.position.x <= -_backgroundOriginalSizeX)
        {
            space.transform.Translate(_backgroundOriginalSizeX, 0, 0);
        }
        if (space.transform.position.y >= _backgroundOriginalSizeY)
        {
            space.transform.Translate(0, -_backgroundOriginalSizeY, 0);
        }
        if (space.transform.position.y <= -_backgroundOriginalSizeY)
        {
            space.transform.Translate(0, _backgroundOriginalSizeY, 0);
        }
    }
    
    private void OnDrawGizmos()
    {
        // 描述相机的圆圈
        UnityEditor.Handles.color = Color.yellow;
        UnityEditor.Handles.DrawWireDisc(Vector3.zero , Vector3.back, _spaceCircleRadius);

        // 行驶方向
        UnityEditor.Handles.color = Color.green;
        UnityEditor.Handles.DrawLine(Vector3.zero, _moveVector);
    }
}
  1. 将Main camera,Ship和Space链接到Universe对象
    Univerce handler
  2. 享受结果
    Result

结果

正如您在生成的GIF中看到的那样,背景对象按坐标轴返回到原点,在该坐标轴上发生的偏移为初始大小,因此我们实现了无缝移动。

备择方案

在编程领域没有理想的解决方案。 为了解决任何任务,程序员必须在相对较大的方法之间进行选择。在我们的情况下,您还可以通过多种方式解决任务。例如,您可以制作一个Quad类型的对象而不是Sprite,然后设置一种材质,该材质也使用平铺结构控制其偏移,这将使我们完全不必移动对象。如果返回更为经典的版本,则可以在达到某些限制时将对象的通常移动与整个场景的坐标中心的移动结合起来。

结论

当我们考虑创建无限空间时,暗物质引擎方法是规避Unity精度约束的主要方法。坐标中心的平移与普通运动的组合也可以属于同一组。上面的示例非常适合创建背景和视差效果。但是,要开发真正的无限空间,需要使用更复杂的对象和机制。在循环世界的以下文章中,我们将考虑这些技术。下次见!^_^


如果您喜欢这篇文章,可以为它提供支持



Privacy policyCookie policyTerms of service
Tulenber 2020