Зацикленный фон/Loopable Background (MonoGame/XNA)

Для имитации неба, дождя, дороги и других различных объектов на заднем фоне, можно использовать со всех сторон зацикленные текстуры(loopable texture).

Loopable Background

Ниже приведена реализация использования зацикленных текстур в качестве фона, который непрерывно двигаются снизу вверх. При комбинирование нескольких таких фонов, можно добиться интересных parallax-эффектов.

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
 
    //зацикленный фон
    class LoopableBackground
    {
        //зацикленная текстура
        private readonly Texture2D _texture;
 
        //начальные точки всех кусков
        private readonly List<Vector2> _startPoints;
 
        //скорость пикселей в секнду
        private readonly float _speed;
 
        //пределы начальных точек по Y
        private readonly float _minY;
        private readonly float _maxY;
 
        //накопленное смещение
        private float _keepOffset;
 
        //видимое смещение
        private const float VisibleOffset = 0.5f;
 
        public LoopableBackground(Texture2D texture, int screenWidth, int screenHeight, float speed)
        {
            _texture = texture;
            _speed = speed;
            _keepOffset = 0;
 
            _startPoints = new List<Vector2>();
 
            //нижний предел
            _minY = -texture.Height;
 
            //создаем фрагменты по все высоте экрана
            var y = _minY;
 
            while (y <= screenHeight)
            {
                //создаем фрагменты по все ширине экрана
                var x = 0;
                while (x <= screenWidth)
                {
                    _startPoints.Add(new Vector2(x, y));
                    x += texture.Width;
                }
 
                y += texture.Height;
            }
 
            //верхний предел
            _maxY = y;
        }
 
        public void Update(GameTime gameTime)
        {
            var elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
 
            _keepOffset += _speed * elapsed;
 
            //ждем пока смещение не будет видимым
            if (_keepOffset < VisibleOffset) return;
 
            //смещаем все начальные точки на константу
            for (var i = 0; i < _startPoints.Count; i++)
            {
                _startPoints[i] = new Vector2(_startPoints[i].X, _startPoints[i].Y + VisibleOffset);
 
                //если оказались за передлом, то делаем перенос
                if (_startPoints[i].Y >= _maxY)
                {
                    _startPoints[i] = new Vector2(_startPoints[i].X, _startPoints[i].Y - (_maxY - _minY));
                }
            }
 
            _keepOffset = 0;
        }
 
        public void Draw(SpriteBatch spriteBatch)
        {
            //отрисовка фрагментов
            foreach (var startPoint in _startPoints)
            {
                spriteBatch.Draw(_texture, startPoint, Color.White);
            }
        }
    }
 
    //зацикленный фон
    class LoopableBackground
    {
        //зацикленная текстура
        private readonly Texture2D _texture;

        //начальные точки всех кусков
        private readonly List<Vector2> _startPoints;

        //скорость пикселей в секнду
        private readonly float _speed;

        //пределы начальных точек по Y
        private readonly float _minY;
        private readonly float _maxY;

        //накопленное смещение
        private float _keepOffset;

        //видимое смещение
        private const float VisibleOffset = 0.5f;

        public LoopableBackground(Texture2D texture, int screenWidth, int screenHeight, float speed)
        {
            _texture = texture;
            _speed = speed;
            _keepOffset = 0;

            _startPoints = new List<Vector2>();

            //нижний предел
            _minY = -texture.Height;

            //создаем фрагменты по все высоте экрана
            var y = _minY;

            while (y <= screenHeight)
            {
                //создаем фрагменты по все ширине экрана
                var x = 0;
                while (x <= screenWidth)
                {
                    _startPoints.Add(new Vector2(x, y));
                    x += texture.Width;
                }

                y += texture.Height;
            }

            //верхний предел
            _maxY = y;
        }

        public void Update(GameTime gameTime)
        {
            var elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;

            _keepOffset += _speed * elapsed;

            //ждем пока смещение не будет видимым
            if (_keepOffset < VisibleOffset) return;

            //смещаем все начальные точки на константу
            for (var i = 0; i < _startPoints.Count; i++)
            {
                _startPoints[i] = new Vector2(_startPoints[i].X, _startPoints[i].Y + VisibleOffset);

                //если оказались за передлом, то делаем перенос
                if (_startPoints[i].Y >= _maxY)
                {
                    _startPoints[i] = new Vector2(_startPoints[i].X, _startPoints[i].Y - (_maxY - _minY));
                }
            }

            _keepOffset = 0;
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            //отрисовка фрагментов
            foreach (var startPoint in _startPoints)
            {
                spriteBatch.Draw(_texture, startPoint, Color.White);
            }
        }
    }

Пример (необходим MonoGame 3.0)