Replying to:rando
nice, just gunna show support, good job on rbl, i havnt read through this yet but im sure i will find it useful.
Thanks; I hope if you make something out of this it works nicely.
Root / Submissions / [.]
'Initial setup. You have to EXEC my library (any slot other than 0 will do) ACLS EXEC "PRG3:RNDLIBFULL" VAR SP=SPSET(796) 'Define our sprite. Use dynamic sprite ID allocation for ease of use SPSCALE SP,2,2 'Scale sprite up to 2X size GAMEPREPAREPLSIMPLE SP,72,72,1,&HFFFFFFFF GAMESETWALKDATA SP,796,4,8To load my library, you EXEC it in any slot other than the currently running one. I like 3 because it's the last slot and I'm least likely to use it. We use the dynamic ID assignment version of SPSET to create our player sprite. The sprite ID is stored in SP. We then scale it (this is all normal). Now the library code: GAMEPREPAREPLSIMPLE prepares a sprite for player use. It attaches controls and sets internal data so the sprite you gave IS the player. Only call this for ONE sprite at a time: if two sprites are prepared as players, you'll probably have issues. The parameters are: SpriteID, SpawnX, SpawnY, Collision Lenience, and Collision Mask. SpawnX and SpawnY are in BG pixel units: 72,72 will put us in the middle of the 5th BG tile over and down (even though there's no BG yet). Collision lenience is how much perimeter to shave off the player's hitbox. Removing the outer 1 pixel is usually a good idea: it lets you get closer to tiles. Collision mask is the same value used in SPHITSP and SPHITRC: if a bit is set in both colliding objects' masks, they are allowed to collide. We set it to the full 32 bits to be safe. GAMESETWALKDATA prepares the sprite as a "walker". A "walker" is any sprite which can face in the 4 directions and has the same amount of animation frames per direction. It doesn't necessarily have to walk: it could fly or have just 1 frame for each direction and it would still work. The parameters are: SpriteID, Base Definition #, Animation FramesPer Direction, and Step Distance. Base definition is the sprite definition # for the first frame of the 4 direction animation set (that starts with right-facing). For instance, the King's base definition would be 576. Use the Smile Tool to see how the base definition lines up with the sprites (it's easy). Animation Frames is how many frames of walking there are per direction: the standard SB sprites all have 4 frames. Step Distance is how far the sprite has to move before it moves to the next animation frame (takes a "step"). With 8, the sprite will make 2 steps per tile (which is pretty decent). You can play around with that last parameter until you like the animation speed. Note: Any sprite (not just the player) can be a "walker". For instance, NPCs can set walk data and have the walker function applied. Walkers automatically adjust their position on screen based on their world coordinates. Walkers have collision detection which can be disabled by setting the mask to 0 (maybe you want a ghost or something).
XSCREEN 2,100,2 'Use 100 sprites and 2 BG layers (first 2 is screen mode) VAR I FOR I=0 TO 1 BGSCREEN I,100,100 'Set layer size to 100x100 BGSCALE I,2,2 NEXT BGFILL 0,0,0,99,99,99 'Fill first layer with grass lol (grass is ID 99; first two 99's are the ends) FOR I=0 TO 99 'Draw perimeter (use um... IDK, ugly rocks 101) BGPUT 1,I,0,101 BGPUT 1,I,99,101 BGPUT 1,0,I,101 BGPUT 1,99,I,101 NEXT GAMEPREPAREBG 3,1 'Use layers 0 and 1 (bitfield), and layer 1 is the collision layer VAR("3:GAMECLSNSTART")=1 'Any tile with ID 1 and above will trigger collisionFirst two sections are just us setting up the map. We fill the bottom layer with grass (boring) and then draw a perimeter of rocks around the edges of layer 1 (the collision layer). GAMEPREPAREBG takes two parameters: A layers bitfield and the layer used for collision. The layers bitfield is a 4 bit value that tells the engine which layers should be automatically offset when the player moves. You don't necessarily want all the layers to move: maybe you're using a layer for rain or something and it moves independently from the map. In our case, we're using layers 0 and 1, which means we set bits 0 and 1 (comes out to 3). Setting all the layers would be 15. The engine can only use one layer for collision, so the second parameter is the collision layer ID (0-3). The last line sets a global variable in my library: normally, I expect you to use the "repeating tile highest bit" trick, so only tiles with ID>1024 will cause collision. However, to make life simpler, we'll say ANY nonzero tile causes collision. Replace the 3: in the string with whatever slot you loaded my library into.
'Initial setup. You have to EXEC my library (any slot other than 0 will do) ACLS EXEC "PRG3:RNDLIBFULL" XSCREEN 2,100,2 'Use 100 sprites and 2 BG layers (first 2 is screen mode) '--Map Preparation-- VAR I FOR I=0 TO 1 BGSCREEN I,100,100 'Set layer size to 100x100 BGSCALE I,2,2 NEXT BGFILL 0,0,0,99,99,99 'Fill first layer with grass lol (grass is ID 99; first two 99's are the ends) FOR I=0 TO 99 'Draw perimeter (use um... IDK, ugly rocks 101) BGPUT 1,I,0,101 BGPUT 1,I,99,101 BGPUT 1,0,I,101 BGPUT 1,99,I,101 NEXT GAMEPREPAREBG 3,1 'Use layers 0 and 1 (bitfield), and layer 1 is the collision layer VAR("3:GAMECLSNSTART")=1 'Any tile with ID 1 and above will trigger collision '--Player Preparation-- VAR SP=SPSET(796) 'Define our sprite. Use dynamic sprite ID allocation for ease of use SPSCALE SP,2,2 'Scale sprite up to 2X size GAMEPREPAREPLSIMPLE SP,72,72,1,&HFFFFFFFF GAMESETWALKDATA SP,796,4,8 WHILE TRUE VSYNC CALL SPRITE 'Run all sprite attached functions. Our player is one WENDThat last WHILE loop is the entire game loop. That's literally it. The game library uses sprite callbacks to make everything tick, so calling GAMEPREPAREPLSIMPLE is actually attaching a complex function to your player sprite. CALL SPRITE will run every defined sprite's callback, so that's... all you need to do. Now run it. If I didn't make any typos (and you didn't either), you should be able to walk around a big empty field with a wall of rocks around it. Wow! OK it's not that cool, so let's make it like... do something.
'The path clearing function. It HAS to be COMMON so my library can use it COMMON DEF CLEARPATH X,Y BGPUT 1,X,Y,0 'Remove the tile from X/Y (SNAKEPATH will call this for each X/Y on the path) END BGFILL 1,1,1,98,98,100 'Fill the inner map with trees (exclude perimeter) 'Yes, these have to be two lines. This is just how SB works. VAR SNAKEP$[0] 'The snakepath parameter dictionary SNAKEP$=NEWSNAKEPATH$() 'Create the snakepath parameter dictionary VAR X[0],Y[0] 'The locations for each point (we'll use these later) FOR I=0 TO 15 PUSH X,10+RND(80) 'Random point at least 10 inside the walls PUSH Y,10+RND(80) IF I>0 THEN SNAKEPATH X[I-1],Y[I-1],X[I],Y[I],SNAKEP$,"CLEARPATH" NEXTFirst, we define the callback function. We won't be using it ourselves: the library will use it to generate the path the way you want it to. The function MUST be COMMON defined so my library can read it. It also must have two parameters which SNAKEPATH will pass the current path location to. We simply want to remove trees along the path, so we BGPUT tile 0 there. If you're confused about the idea of callback functions, see https://en.wikipedia.org/wiki/Callback_(computer_programming) Next, we fill the collision layer with trees using BGFILL. Now we start snaking paths. Remember, SNAKEPATH requires a bundle of parameters in one of my "dictionaries". My dictionaries are string arrays: create a 0 length string array first, then generate the parameter bundle with NEWSNAKEPATH$() as you see above. Now SNAKEP$ contains a bundle of parameters which defines path generation. We'll use all defaults here. Also, you only need to generate the parameters once: you can continue using the same parameters by using the same bundle/dictionary. We create two arrays to hold the locations of all the points we're snaking between. Later, we'll use these to place items and spawn the player (since they can't necessarily spawn at BG tile 5,5 anymore). Then we create 16 points at least 10 within the edges of the map (to account for path straying) and connect each point. The parameters for SNAKEPATH are: X1, Y1, X2, Y2, Path Generation Parameters, Callback. The X/Y are the two locations to connect with a path. The generation parameters are the dictionary we made earlier. Finally, the callback is the name of the function used to create paths as a string. Ours is "CLEARPATH".
'Initial setup. You have to EXEC my library (any slot other than 0 will do) ACLS EXEC "PRG3:RNDLIBFULL" XSCREEN 2,100,2 'Use 100 sprites and 2 BG layers (first 2 is screen mode) '--Map Preparation-- VAR I FOR I=0 TO 1 BGSCREEN I,100,100 'Set layer size to 100x100 BGSCALE I,2,2 NEXT BGFILL 0,0,0,99,99,99 'Fill first layer with grass lol (grass is ID 99; first two 99's are the ends) BGFILL 1,0,0,99,99,101 'Fill the collision layer with rocks (will become perimeter) BGFILL 1,1,1,98,98,100 'Fill the inner map with trees (exclude perimeter) '-Path Generation- VAR SNAKEP$[0] 'The snakepath parameter dictionary SNAKEP$=NEWSNAKEPATH$() 'Create the snakepath parameter dictionary VAR X[0],Y[0] 'The locations for each point (we'll use these later) FOR I=0 TO 15 PUSH X,10+RND(80) 'Random point at least 10 inside the walls PUSH Y,10+RND(80) IF I>0 THEN SNAKEPATH X[I-1],Y[I-1],X[I],Y[I],SNAKEP$,"CLEARPATH" NEXT GAMEPREPAREBG 3,1 'Use layers 0 and 1 (bitfield), and layer 1 is the collision layer VAR("3:GAMECLSNSTART")=1 'Any tile with ID 1 and above will trigger collision '--Player Preparation-- VAR SP=SPSET(796) 'Define our sprite. Use dynamic sprite ID allocation for ease of use SPSCALE SP,2,2 'Scale sprite up to 2X size GAMEPREPAREPLSIMPLE SP,16*X[0]+8,16*Y[0]+8,1,&HFFFFFFFF GAMESETWALKDATA SP,796,4,8 WHILE TRUE VSYNC CALL SPRITE 'Run all sprite attached functions. Our player is one WEND 'The path clearing function. It HAS to be COMMON so my library can use it COMMON DEF CLEARPATH X,Y BGPUT 1,X,Y,0 'Remove the tile from X/Y (SNAKEPATH will call this for each X/Y on the path) ENDNotice the player spawn location is multiplied by 16: the locations we generated were BG tile locations, but the player's spawn location is in world coordinates. Each tile is 16 wide, so we multiply by 16. We add 8 so we're in the middle of the tile. If you run this code, you should be able to walk around a much more interesting map. Use the circle pad if it's hard to fit into tight spaces.
COMMON DEF CLEARPATH X,Y BGFILL 1,X,Y,X+1,Y+1,0 ENDNext, we'll need to place objects around the map. Hmmm... let's collect treasure (tile 864). Remember: SNAKEPATH destroys tiles to make the paths, so we should place our items AFTER the paths are generated:
FOR I=1 TO 15 STEP 2 'Start at 1 so we don't put a treasure right under the poor old lady BGPUT 1,X[I],Y[I],864 'See, the locations were useful NEXT
VAR CHESTS=8 'Remember: COMMON for callbacks COMMON DEF PLPROCESS SP,BGX,BGY,X#,Y#,VX#,VY#,FACING,STEPPED 'Player is standing on BGX/BGY. Together with FACING, we figure out which tile we're looking at. IF BUTTON(1) AND #A THEN 'They pressed the A button VAR IX,IY GAMEVECTOR FACING OUT IX,IY 'Convert FACING into unit offsets IX=BGX+IX IY=BGY+IY 'Now IX/IY hold the location the player is facing IF BGGET(1,IX,IY)==864 THEN BEEP 3 'Might as well give feedback CHESTS=CHESTS-1 BGPUT 1,IX,IY,0 IF CHESTS<=0 THEN PRINT "YOU WON!" ENDIF ENDIF END 'This is how you'd register the callback so the library calls your function VAR("3:GAMEPLPROCESS$")="PLPROCESS"Your callback will be called every time the engine updates the player with the following information: The ID of the player sprite, the BG tile location of the player, the absolute X and Y world position of the player, the velocity of the player, the direction they're facing (as an #UP/#DOWN/#LEFT/#RIGHT bitfield) and whether or not they stepped this frame (useful for footstep sound effects). You don't have to use all this data, but you MUST accept it all anyway. In your callback, you handle player actions like button presses. In ours, we check to see if the player pressed A, then we see if they pressed A on a chest. If they did, we decrement the chest count and remove the item. If they found them all... they win! We use another library function called GAMEVECTOR to calculate which BG tile the player is facing. They can't be standing ON the chest, so we have to look in front of them. GAMEVECTOR converts the FACING bitfield into X and Y tile offsets. If we add these to the player's BGX/BGY location, we get the location of the tile they're looking at. The rest is simple. Just check if the tile is a chest, then remove. Also, check if they won and print a lame message.
'Initial setup. You have to EXEC my library (any slot other than 0 will do) ACLS EXEC "PRG3:RNDLIBFULL" XSCREEN 2,100,2 'Use 100 sprites and 2 BG layers (first 2 is screen mode) '--Map Preparation-- VAR I FOR I=0 TO 1 BGSCREEN I,100,100 'Set layer size to 100x100 BGSCALE I,2,2 NEXT BGFILL 0,0,0,99,99,99 'Fill first layer with grass lol (grass is ID 99; first two 99's are the ends) BGFILL 1,0,0,99,99,101 'Fill the collision layer with rocks (will become perimeter) BGFILL 1,1,1,98,98,100 'Fill the inner map with trees (exclude perimeter) '-Path and Chest Generation- VAR SNAKEP$[0] 'The snakepath parameter dictionary SNAKEP$=NEWSNAKEPATH$() 'Create the snakepath parameter dictionary VAR X[0],Y[0] 'The locations for each point (we'll use these later) FOR I=0 TO 15 PUSH X,10+RND(80) 'Random point at least 10 inside the walls PUSH Y,10+RND(80) IF I>0 THEN SNAKEPATH X[I-1],Y[I-1],X[I],Y[I],SNAKEP$,"CLEARPATH" NEXT FOR I=1 TO 15 STEP 2 'Start at 1 so we don't put a treasure right under the poor old lady BGPUT 1,X[I],Y[I],864 'See, the locations were useful NEXT GAMEPREPAREBG 3,1 'Use layers 0 and 1 (bitfield), and layer 1 is the collision layer VAR("3:GAMECLSNSTART")=1 'Any tile with ID 1 and above will trigger collision VAR("3:GAMEPLPROCESS$")="PLPROCESS" 'This is how you'd register the callback so the library calls your function '--Player Preparation-- VAR SP=SPSET(796) 'Define our sprite. Use dynamic sprite ID allocation for ease of use SPSCALE SP,2,2 'Scale sprite up to 2X size GAMEPREPAREPLSIMPLE SP,16*X[0]+8,16*Y[0]+8,1,&HFFFFFFFF GAMESETWALKDATA SP,796,4,8 VAR CHESTS=8 WHILE TRUE VSYNC LOCATE 0,0 PRINT FORMAT$("%D chests left ",CHESTS) 'Tell player how many are left. CALL SPRITE 'Run all sprite attached functions. Our player is one 'The main loop is the place to put win condition checking. Sprite callbacks can't halt this loop. IF CHESTS<=0 THEN ACLS BGMPLAY 5 PRINT "Found all chests!" 'Hooray, you won! BREAK ENDIF WEND 'The path clearing function. It HAS to be COMMON so my library can use it COMMON DEF CLEARPATH X,Y BGFILL 1,X,Y,X+1,Y+1,0 END 'Player processing function COMMON DEF PLPROCESS SP,BGX,BGY,X#,Y#,VX#,VY#,FACING,STEPPED 'Player is standing on BGX/BGY. Together with FACING, we figure out which tile we're looking at. IF BUTTON(1) AND #A THEN 'They pressed the A button VAR IX,IY GAMEVECTOR FACING OUT IX,IY 'Convert FACING into unit offsets IX=BGX+IX IY=BGY+IY 'Now IX/IY hold the location the player is facing IF BGGET(1,IX,IY)==864 THEN 'Player clicked A on chest BEEP 3 'Might as well give feedback CHESTS=CHESTS-1 BGPUT 1,IX,IY,0 'Remove chest ENDIF ENDIF ENDWe moved the "win" stuff out into the main game loop so we can stop the loop if they win. I guess we could call STOP in the sprite callback, but I didn't want to. I also added a little counter at the top left so we always know how many chests are left. Run it and check it out: you have a working game (if you and I didn't make any mistakes). In the next tutorial, we're going to make the game a bit nicer and go over more library functionality.