tasting on MonoGame
[tastes.git] / Platformer2D / Documentation / 3_adding_a_scrolling_level.md
blob099717e170669141f161af77091a985c55ec6895
1 # Platformer: Adding a Scrolling Level
3 > 8 minutes to read
5 Extends the base Platformer starter kit code by adding a scrolling level. Specifically, it provides parallax scrolling.
7 One of the more impressive features of platformer games in the 80s was the scrolling level. This topic details the process for adding this feature to the Platformer game. You'll implement multiple scrolling backgrounds, with the back layer scrolling the slowest, and the front layer scrolling the fastest. This is called parallax scrolling. It provides an illusion of depth in the game.
9 > Tip
10
11 > It is highly recommended that you are already familiar with the structure and features of the Platformer starter kit. This extension involves modifications to several files in the Platformer starter kit. For more information on the Platformer starter kit, see Starter Kit: Platformer.
13 This extension modifies two areas of the Platformer starter kit, and it adds a new class. It is recommended that you use the base Platformer starter kit solution as the starting point for your modifications.
15 Adding parallax scrolling involves the following major steps:
17 * Modifying PlatformerGame (game.cs) to call SpriteBatch.Begin and SpriteBatch.End at a different time.
19 * Modifying Level.cs to use a new object type for the background textures instead of Texture2D. In addition, a camera is implemented and used to draw a portion of the background instead of the entire background.
21 * Adding a new class called Layer that replaces the usage of Texture2D for background textures.
23 ## Modifying the PlatformerGame Class (game.cs)
25 The only modification for this class involves modifying the Draw method. You'll move the SpriteBatch.Begin and SpriteBatch.End calls to the DrawHud method. This allows the Level.Draw method to set up its own batch for drawing the scrolling backgrounds.
27 In PlatformerGame.Draw, remove the spriteBatch.Begin(); and spriteBatch.Begin(); lines of code.
29 In the PlatformerGame.DrawHud method, add the following line before any existing code:
31 ```csharp
32 spriteBatch.Begin();
33 ```
35 At the end of the same method, add:
37 ```csharp
38 spriteBatch.End();
39 ```
41 The DrawHud method now implements a single batch.
43 That completes the modifications for PlatformerGame.cs. The next step adds support for a new class, called Layer to the Level class.
45 ## Modifying the Level Class
47 The main point of these modifications is to support the usage of a new kind of texture class (Layer) that enables parallax scrolling. Each of the three background textures will use the new class; therefore, the surrounding code also needs to accommodate the new class.
49 First, change the type used by the layers array from Texture2D[] to Layer[]. This is the new background texture class, added later.
51 ```csharp
52 private Layer[] layers;
53 ```
55 Now, look for a variable block, commented as "Level game state," and add a new variable called cameraPosition.
57 ```csharp
58 layers = new Texture2D[3];
59     for (int i = 0; i < layers.Length; ++i)
60     {
61       // Choose a random segment if each background layer for level variety.
62       int segmentIndex = random.Next(3);
63       layers[i] = Content.Load<Texture2D>("Backgrounds/Layer" + i + "_" + segmentIndex);
64     }
65 ```
67 with the following:
69 ```csharp
70 layers = new Layer[3];
71     layers[0] = new Layer(Content, "Backgrounds/Layer0", 0.2f);
72     layers[1] = new Layer(Content, "Backgrounds/Layer1", 0.5f);
73     layers[2] = new Layer(Content, "Backgrounds/Layer2", 0.8f);
74 ```
76 The new code initializes the array with three new Layer objects. Each of these objects loads a different texture, and has a different scrolling speed (the third parameter of the Layer constructor).
78 > Note
79
80 > Platformer assumes that scrolling speed values have a range between 0 and 1. A value of 0 means no scrolling and 1 means scrolling at the same pace as the level tiles.
82 It's now time to modify the drawing code for the level. Locate the Level.Draw method, and replace it with the following method declaration:
84 ```csharp
85 public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
86     {
87       spriteBatch.Begin();
88       for (int i = 0; i <= EntityLayer; ++i)
89         layers[i].Draw(spriteBatch, cameraPosition);
90       spriteBatch.End();
92       ScrollCamera(spriteBatch.GraphicsDevice.Viewport);
93       Matrix cameraTransform = Matrix.CreateTranslation(-cameraPosition, 0.0f, 0.0f);
94       spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, transformMatrix: cameraTransform);
96       DrawTiles(spriteBatch);
98       foreach (Gem gem in gems)
99         gem.Draw(gameTime, spriteBatch);
101       Player.Draw(gameTime, spriteBatch);
103       foreach (Enemy enemy in enemies)
104         enemy.Draw(gameTime, spriteBatch);
106       spriteBatch.End();
108       spriteBatch.Begin();
109       for (int i = EntityLayer + 1; i < layers.Length; ++i)
110         layers[i].Draw(spriteBatch, cameraPosition);
111       spriteBatch.End();
112     }
115 Now, let's go over what just changed. Initially, the first sprite batch draws all three background layers. Then, instead of moving a camera throughout the world, you'll move the world backwards such that the camera is always at the origin. This greatly simplifies the drawing logic because you can now call a specialized SpriteBatch.Begin overload that uses a transform matrix (calculated earlier in the method).
117 You'll recognize the next chunk of code because it is unchanged from the original implementation. It draws the level elements: tiles, gems, enemies, and the player character. The last batch does nothing in the base implementation of Platformer. It is left in for drawing foreground textures. For example, if a foreground texture (such as trees or bushes) was drawn, it would obscure the player character when he "walked" behind the texture.
119 Because the scrolling extension draws tiles off-screen, you should be aware that this could impact the frame rate. To avoid any slowdown you'll need to implement a simple culling feature that limits the amount of tiles drawn to only those on the screen at the time. This reduces the drawing load, speeding up the game.
121 At the beginning of the DrawTiles method, add the following code:
123 ```csharp
124 // Calculate the visible range of tiles.
125     int left = (int)Math.Floor(cameraPosition / Tile.Width);
126     int right = left + spriteBatch.GraphicsDevice.Viewport.Width / Tile.Width;
127     right = Math.Min(right, Width - 1);
130 Below this modification, modify the for line of the inner loop (the one that loops '**x**' from 0 to Width) to match the following:
132 ```csharp
133 for (int x = left; x <= right; ++x)
136 Now, only visible tiles are drawn but note that other items, such as gems and enemies, are still drawn even when off screen. The culling of non-tiles is another excellent place for extending Platformer!
138 The last modification in this file adds the new ScrollCamera method. This method calculates how much background is scrolled when the player reaches the screen's edge. When the begin scrolling is platform-dependent. Because mobile screens are the narrowest, it looks the farthest ahead. Desktop platforms don't look ahead as much. This factor is used to calculate the edges of the screen and how far to scroll when the player reaches that edge. Scrolling continues until either end of the level is reached. At that point, the camera position is clamped.
140 Add the following code, after the Draw method:
142 ```csharp
143 private void ScrollCamera(Viewport viewport)
144     {
145 #if MOBILE
146       const float ViewMargin = 0.45f;
147 #else
148       const float ViewMargin = 0.35f;
149 #endif
151       // Calculate the edges of the screen.
152       float marginWidth = viewport.Width * ViewMargin;
153       float marginLeft = cameraPosition + marginWidth;
154       float marginRight = cameraPosition + viewport.Width - marginWidth;
156       // Calculate how far to scroll when the player is near the edges of the screen.
157       float cameraMovement = 0.0f;
158       if (Player.Position.X < marginLeft)
159         cameraMovement = Player.Position.X - marginLeft;
160       else if (Player.Position.X > marginRight)
161         cameraMovement = Player.Position.X - marginRight;
163       // Update the camera position, but prevent scrolling off the ends of the level.
164       float maxCameraPosition = Tile.Width * Width - viewport.Width;
165       cameraPosition = MathHelper.Clamp(cameraPosition + cameraMovement, 0.0f, maxCameraPosition);
166     }
169 The final step adds the new Layer class.
171 ## Implementing the Layer Class
173 Because the backgrounds will be scrolling during gameplay, you'll need something more specialized than a Texture2D class to draw these textures. The background textures provided are divided into three segments that tile seamlessly into one scrolling background.
175 Using the Add Class dialog, add a new C# class, called Layer, to the PlatformerWindows solution. At the top of the file, add some useful XNA Framework references:
177 ```csharp
178     using System;
179     using Microsoft.Xna.Framework;
180     using Microsoft.Xna.Framework.Graphics;
181     using Microsoft.Xna.Framework.Content;
184 Depending on how your project is setup, make sure the namespace is set the same as the rest of the classes in the project as follows:
186 ```csharp
187     namespace Platformer2D
190 In this new class, add the following properties:
192 ```csharp
193     class Layer
194     {
195         public Texture2D[] Textures { get; private set; }
196         public float ScrollRate { get; private set; }
197     }
200 These properties store the background texture of the layer and its scroll speed.
202 Now add the constructor:
204 ```csharp
205 public Layer(ContentManager content, string basePath, float scrollRate)
206     {
207       // Assumes each layer only has 3 segments.
208       Textures = new Texture2D[3];
209       for (int i = 0; i < 3; ++i)
210         Textures[i] = content.Load<Texture2D>(basePath + "_" + i);
212       ScrollRate = scrollRate;
213     }
216 This constructor accepts a content manager, a base path to the background asset, and the scroll speed of the background layer. Note that each layer has only three segments.
218 It loads each segment of the background in the Textures array, and then sets the scroll speed.
220 The final method to add is the Draw method. Add this code after the constructor method:
222 ```csharp
223 public void Draw(SpriteBatch spriteBatch, float cameraPosition)
224     {
225       // Assume each segment is the same width.
226       int segmentWidth = Textures[0].Width;
228       // Calculate which segments to draw and how much to offset them.
229       float x = cameraPosition * ScrollRate;
230       int leftSegment = (int)Math.Floor(x / segmentWidth);
231       int rightSegment = leftSegment + 1;
232       x = (x / segmentWidth - leftSegment) * -segmentWidth;
234       spriteBatch.Draw(Textures[leftSegment % Textures.Length], new Vector2(x, 0.0f), Color.White);
235       spriteBatch.Draw(Textures[rightSegment % Textures.Length], new Vector2(x + segmentWidth, 0.0f), Color.White);
236     }
239 This method first calculates which of the background segments to draw, and then draws them offset by the previously calculated amount. It is assumed that two segments are enough to cover any screen.
241 ## Modifying the Level Structure File
243 At this point, the parallax scrolling extension is completely coded. However, if you recompile and run the game, you will see no difference. Like the Power-Up Gem extension, you'll need to modify an existing map to enable the scrolling. However, it won't be as easy as modifying an extra character or two. You would need to come up with a lot of new content, past the default edge of the level, to fully illustrate the scrolling. However, since the format of the level structure file is text-based, you can just use the following text block as a demonstration case.
245 ```csharp
246 ..............................................................................
247 ..............................................................................
248 ...........................G..............................................X...
249 ..........................###.....................................############
250 ......................G.......................................................
251 .....................###................G.GDG.............G.G.G...............
252 .................G....................#########..........#######..............
253 ................###...........................................................
254 ............G...................G.G...............G.G.........................
255 ...........###.................#####.............#####........................
256 .......G......................................................................
257 ......###...............................GDG.G.............G.G.G.G.G.G.G.G.G.G.
258 ......................................#########.........##.G.G.G.G.G.G.G.G.G..
259 .1........................................................GCG.G.G.GCG.G.G.GCG.
260 ####################......................................####################
263 Copy this text and then open the 0.txt file (located in the HighResolutionContent content project). Select all text in the level structure file, and then paste the new text in. Save the file and do the same for the equivalent low-resolution level structure file. Once you have made changes to both maps, recompile and run the game. You can now run far to the right in the first level, and the three background layers scroll at different speeds. Pretty cool effect, eh?!