![]() |
GX Platformer Tutorial - Printable Version +- QB64 Phoenix Edition (https://qb64phoenix.com/forum) +-- Forum: QB64 Rising (https://qb64phoenix.com/forum/forumdisplay.php?fid=1) +--- Forum: Code and Stuff (https://qb64phoenix.com/forum/forumdisplay.php?fid=3) +---- Forum: Works in Progress (https://qb64phoenix.com/forum/forumdisplay.php?fid=9) +---- Thread: GX Platformer Tutorial (/showthread.php?tid=3514) Pages:
1
2
|
RE: GX Platformer Tutorial - dbox - 03-17-2025 Part 6 - Refining the Controls The feel of player movement and the responsiveness of the controls are two of the most important aspects of a platformer. Before we get too much further building out the levels of our game we want to dial in our player controls and movement as much as possible. Otherwise, if we spend a bunch of time now building out our level and then find later that the jump is too low or too high or too "floaty" or we want to change the walk/run speed of our character this could cause us a lot of re-work to make the levels work with the new mechanics. That being said, depending on the type of game we are making, the player movement that we have at the end of Part 5 might be totally serviceable, it might even be preferred. In this particular example, however, we want to aim for movement that is more like Mario's from Super Mario Bros on the NES. The first thing we want to do to improve the feel of the player movement is to incorporate some acceleration. In our current logic, when the user presses left or right, the player character is immediately at full velocity. When the key is released, the movement immediately stops. This can leave the movement feeling a bit flat. To incorporate acceleration, we can change our current implementation so that instead of full velocity being applied immediately when the direction key is applied, we instead increase the velocity a bit more each frame that the button is pressed and decrease the velocity a bit each frame after the key is released. Let's start by adding some new constants for acceleration and max velocity: Code: (Select All)
Let's also add a new global variable to track the current player direction:Code: (Select All)
Next, we need to make a number of changes to our logic which handles player movement. Let's take the opportunity to move this logic from the OnUpdate method into a dedicated method named HandlePlayerEvents:Code: (Select All)
Now, let's add some variables to use for our velocity adjustments and initialize them to the current player velocity:Code: (Select All)
Then, we'll incorporate the velocity changes, starting with the right movement. We'll replace our original simple algorithm...Code: (Select All)
...with the following:Code: (Select All)
With this change, we will increase the player's horizontal velocity by our acceleration constant (ACCEL). We'll add a condition to make sure that the velocity doesn't exceed our MAX_VELOCITY constant. Then we'll update our variable that is tracking the direction our player is facing.Next, we need to make the same changes to our left movement logic: Code: (Select All)
Now we can add the logic to decelerate when the player is no longer pressing the left or right arrow keys. Code: (Select All)
Finally, after the end of our If..ElseIf..Else block, we can add a call to set the horizontal velocity of our player based on our updated vx variable: Code: (Select All)
If you try out the program now you will see that our player character takes a little bit to get up to full speed and when you release the arrow keys he'll continue moving for a step or two before he comes to a stop. You can experiment with the acceleration constant to get just the right feel. For example, if you set it lower to a value of 3, it will feel more like we're trying to run on a slipper surface like ice. (You can see an example of this type of movement in Sleighless which has Santa running around on the ice and snow.) Alright, the horizontal movement is looking better, now let's look at adjusting a few more things to improve the feel of the player movement:
We can address the "floaty-ness" of the jump by increasing the speed of the jump and adding some new logic to juice up the effect of gravity a bit: Code: (Select All)
Finally, let's add some logic to strike a jump pose when our player is in the air and return to an idle pose when he has stopped moving: Code: (Select All)
If we run the program now, we will see that the controls and movement feel much more responsive. However, there is one issue that still remains. If we walk just a little past the edge of the center platform and stop, we seem to be levitating. The reason for this has to do with the way collision detection works in GX. By default, it uses the entity's height and width to determine the bounding box when checking for collisions. Our player character has a default collision boundary that looks like this: We can adjust this default bounding box with a call to the GXEntityCollisionOffset method which allows us to tell GX how many pixels in from the left, top, right and bottom sides of the entity we should offset the collision bounding box Code: (Select All)
Here now are all of the changes we covered in Part 6: RE: GX Platformer Tutorial - dbox - 03-19-2025 Part 7 - Using the Map Maker In Part 4 we looked at how to create a map programmatically using the GX API. In this section we'll look at how to create the same simple map as an introduction to using the GX Map Maker. This tool aims to make it easier to create and maintain the maps used in your game projects. This will set us up to build out the rest of our level. To get started, you can launch the GX Map Maker from this URL: https://boxgaming.github.io/gx-mapmaker Let's create a new map by selecting File -> New. This will show us the new map dialog. We'll initially use the same tile dimensions (16 columns by 9 rows) and start with one layer. We'll also want to select the same tileset.png image we used in Part 4 and leave the default values of 16 x 16 for the size of the tiles. This will create a new map and load the tileset in our tile palette in the right panel. This tileset is pretty small so we may want to zoom in the tileset and map views using the Tileset -> Zoom In and Map -> Zoom In menu options. Click a tile in the tileset panel to select it as the active brush. The selected tile will be framed with a yellow square and the tile's id will be displayed at the bottom of the panel. Once a tile is selected it can be placed on the map at the desired location by clicking on the map. You can either place a single tile at a time in this manner, or, hold down the left button and move the mouse to paint continuously with the selected tile. Multiple tiles can be selected as a block. Click and drag the mouse to select a block of tiles. The map cursor will now be resized to the size of the tile selection. You can also copy tiles from a selection on the map. Hold the Shift key and drag the mouse down and to the right to select the tiles you wish to copy. A yellow border will be displayed around the block of tiles to copy. The cursor will change to the size of the tile selection. Clicking on the map will paste a copy of the selected tiles at the current cursor location. If you place a block in the wrong place, you can delete it by pressing either the X or Delete key. Using these basic techniques let's recreate the first layer from Part 4. Then we can create a new layer by pressing the "+" button on the far right side of the Layers panel. This will add a "Layer 2" to the list below. To make this the active layer for editing click the on the "Layer 2" label. We can now add our tree and barrel from Part 4 to the map. Clicking the eye icon on a given row in the Layers panel will toggle the visibility of that layer. You can also lock a layer for edit so you don't accidently make changes to it when you don't intend to. Once you've got your masterpiece complete you can save it with the File -> Save menu option. This will prompt you to download the file. Let's name it "platformer.gxm". Loading the Map Let's look at using this map in our work-in-progress. First, we'll create a new folder in our Files tab named "map" and then drop in our "platformer.gxm" file. We can now remove all of our map Data sections as well as our LoadMap function from our code. We can also remove the tileset.png from our img folder as it is now contained within our map file. Then we can replace our call to the LoadMap function with the GXMapLoad method: Code: (Select All)
Here's the complete set of changes. This is the first step where we actually have less code than the last: RE: GX Platformer Tutorial - dbox - 03-20-2025 Part 8 - Collision Layer In Part 5 we implemented our original collision detection. It is based on simple logic that looks to see if our character is intersecting with any tile in layer 1. If so, then we have a collision, and the player should not be allowed to move through that tile space. This method works fine for simple maps with simple collision rules. However, as you add complexity to your map you may run into scenarios where you want to separate the collision rules from the art placement and/or you may want to support different types of collision. One common approach is to create a dedicated collision layer in your map. Let's look at creating that for our game. We can open the map we were working on in part 6 from the File -> Open menu in the GX Map Maker. Let's create a new layer by selecting the "+" button in the right corner of the Layers Panel: This will create a new layer with the label "Layer 3". Let's select that layer in the list to make it the active layer. In the bottom right corner of the tilesheet there is a tile that looks like a square with a dotted outline. This was included to use in our collision layer. You could actually use any tile you wanted, but it is useful to create one that allows you to see the tiles underneath. With that tile selected, let's add collision tiles around the borders of our map like so: Then save the changes with the File -> Save menu item. Let's incorporate these changes into our game. Copy the updated platformer.gxm file into map folder of the project. Let's add a couple of constants: Code: (Select All)
Now let's rework our OnTileCollision method to use the new method:Code: (Select All)
If we run our game now, we will see that we can now walk right past the little hill in the center of the screen since we did not add any collision tiles in that area. Unfortunately, we can see the collision tiles on the screen. No problem, we can remove this from view with a call to the GXMapLayerVisible method:Code: (Select All)
Ok, now let's look at a more advanced scenario for collision detection. Suppose we want our player to be able to walk in front of the little hill in the center but still allow him to jump on top of it. Well, first we'll need a way to indicate that we should use a different kind of collision detection in our collision layer. If we go back to our GX Map Maker, we can see that next to the collision tile we just used is another similar tile in position 71. Let's select that tile and then, with our map layer (3) selected, place that collision tile at the top of our little hill: Save the changes and copy the map into the game project. Now, let's add a constant for the new collision tile and name it something wildly creative like "COLLISION_TILE2": Code: (Select All)
Then we can update our OnTileCollision method with the new logic for this collision type: Code: (Select All)
Our logic here when intersecting with our new collision tile basically checks to see if the player entity is falling from above the tile, then prevent movement, otherwise, let them pass through.With these changes we have all of the player movement and collision mechanics in place: RE: GX Platformer Tutorial - Unseen Machine - 03-21-2025 Hello there fellow engine designer! Not bad bro, not bad at all! KUDOS! I started building my game engine UnseenGDK in QB64 way back in 2010/2011 and have worked on it on and off ever since, currently I am working on UnseenGDK2, which is both a headache and a pleasure as I am sure you are well aware! If you ever want to add models to your entity type then let me know and i'll drop you the code...GDK2's entity type only uses an index into the Asset array whos corresponding ID tag defines what type of asset the entity is...this way even if you draw a model or sprite a thousand times, you only ever load it once! Keep it up! Unseen RE: GX Platformer Tutorial - dbox - 03-21-2025 (03-21-2025, 02:12 AM)Unseen Machine Wrote: Hello there fellow engine designer! Thanks @Unseen Machine! I'd be curious to learn more about your engine. Do you have a github repo or documentation page? RE: GX Platformer Tutorial - dbox - 03-26-2025 Part 9 - Side Scrolling Well at this point we have some pretty decent platforming mechanics, but we're missing one important element - side scrolling. The first thing we need is a map that is larger than the visible screen. Let's copy the full map from the original post into our work in progress. If we run the game now, well... it's not a lot different. There's a new platform we can jump on and exit the current screen, but the view doesn't follow our player so we can't see what's happening. We need to scroll the screen. We could implement the logic ourselves to change the scene position (GXScenePos) as the player moves through the level. However, GX has a couple of built in methods that can make this easy for us. First, we can call GXSceneFollowEntity to tell GX to keep our player character centered on the screen. Then we can call GXSceneConstrain to keep the scene from moving past the edges of our map. Code: (Select All)
If we run our program again, we will now see the scene stay centered on our player as he moves through the map.Backgrounds Up to now we've just been rendering our map on top of a plain black background. We can make this a bit more interesting. GX has support for multiple background layers. Let's start with a static background with a blue sky and some wispy clouds. Code: (Select All)
We pass to the GXBackgroundAdd method the path to our background image. The second parameter indicates the background mode. In this case we will tell it to just stretch the background image to fill the scene.Ok this is more interesting, but we can do more. If we want to add more of a feeling of depth, we can add additional layers that scroll at different rates (parallax scrolling). Let's add our mountain background and use this method: Code: (Select All)
This time we have specified the wrap mode. This tells GX to scroll the background image in the direction the scene is moving and to wrap the image so that when the end of the image is reached it repeats the image from the beginning. This gives the appearance of a continuous image that doesn't have a start or end. The GXBackgroundWrapFactor indicates how fast the image will scroll. The default value of 1 will cause the background to scroll in sync with the scene. We are passing in a value of .25 which will cause this background to scroll at 1/4th the speed of the scene.Here is the updated project up to this point: And with that we're almost done, just one more part to go... RE: GX Platformer Tutorial - dbox - 03-26-2025 Part 10 - Game Over Well, all good things must come to an end, and so it is with our game (and this tutorial). All that's left from what we set out to cover is to detect when the game has ended and show a "Game Over" message. This message should be shown if the player falls off the screen or when he successfully makes it to the end of the level. In order to draw text on the screen we need to implement a new event handler for the "Draw Screen" event. So let's add a new Case statement to our GXOnGameEvent and call a new OnDrawScreen method. For testing purposes let's just have it use the GXDrawText method to display a test message on the screen: Code: (Select All)
The first parameter to the GXDrawText method indicates the bitmap font to use. There are two default fonts included with the engine GXFONT_DEFAULT and GXFONT_DEFAULT_BLACK. The next two parameters indicate the screen coordinates of where to place the text and the final parameter is the text to display.If we run the program now, we'll see "TEST" displayed in the upper left corner of the screen. Let's adjust this to show instead the current x coordinate of our player. We'll switch to use the default black font as it should show up better against the light sky: Code: (Select All)
Now we can just run to the end of the board and find the x coordinate which should trigger that the player has made it to the end of the level.Let's add a new variable to track our game over state: Code: (Select All)
Now, let's add the logic to our OnUpdate method to detect the end of game Code: (Select All)
Finally, let's update our OnDrawScreen method to remove our test message and show a "Game Over" message when our gameOver variable has been set to true: Code: (Select All)
If we run the game now, we'll see our "Game Over" message anytime we fall off the screen or reach the end. Bitmap Fonts However, it might be nice to make the message stand out a bit more. Let's load a custom bitmap font from the following image: We can create our font with the GXFontCreate method: Code: (Select All)
The first parameter specifies the path to the file containing the font image. The next two parameters are the character width and height in pixels. The last parameter is a string which defines the character map.With the font loaded we can now use it instead of the default font in our OnDrawScreen method: Code: (Select All)
If we run our program again, we'll now see the new font in action:And with that we've come to the end of our platformer tutorial. We've even made a few enhancements along the way to our original target program: Miscellaneous I thought I'd mention a couple of additional items that didn't make it into this tutorial but might be useful to you:
Final Thoughts Just to reiterate what I said at the outset, I'd be very curious to hear of any feedback on the tutorial or game engine, positive or negative. Would there be other topics of interest to see (e.g. sound, device input, enemies, projectiles)? Did anyone even make it this far? Or was it more of a case of TL;DR and I should have just made a YouTube video? |