Graphics 640 , 480
;
;----
;
; ;--------------------------------------------------------------------------;
; ; Define and load tile and player images ;
; ;,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,;
;
;,,,,
;
Const TileImage_Width = 32
Const TileImage_Height = 32
Const TileImage_Count = 4 ; Number of images
TileImages = LoadAnimImage ( "Tiles.BMP" , TileImage_Width , TileImage_Height , 0 , TileImage_Count )
PlayerImage = LoadImage ( "Player.BMP" )
MidHandle PlayerImage ; Player world coordinates is in center of image.
;
;----
;
; ;--------------------------------------------------------------------------;
; ; Define tile map ;
; ;,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,;
;
;,,,,
;
.Tilemap
Data 10 ; Width
Data 10 ; Height
Data 2,2,2,2,2,2,2,2,2,2
Data 2,0,0,0,0,0,0,0,0,2
Data 2,0,0,0,0,0,0,0,0,2
Data 2,0,0,3,1,1,3,0,0,2
Data 2,0,0,1,0,0,1,0,0,2
Data 2,0,0,1,0,0,1,0,0,2
Data 2,0,0,3,1,1,3,0,0,2
Data 2,0,0,0,0,0,0,0,0,2
Data 2,0,0,0,0,0,0,0,0,2
Data 2,2,2,2,2,2,2,2,2,2
Global Tilemap_Width ; Using globals instead of constants,
Global Tilemap_Height ; so we can load a tilemap with any size.
Dim Tilemap( 0 , 0 ) ; Can be redimensioned later on.
;
;----
;
; ;--------------------------------------------------------------------------;
; ; Load tile map ;
; ;,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,;
;
;,,,,
;
Restore Tilemap
Read Tilemap_Width
Read Tilemap_Height
Dim Tilemap( Tilemap_Width , Tilemap_Height )
For TileY = 1 To Tilemap_Height
For TileX = 1 To Tilemap_Width
Read Tilemap( TileX , TileY )
Next
Next
; Imagine that our tilemap begins at coordinate (0,0) in our world.
;
; (0,0)
; \
; O---------------+
; | | - Tilemap
; | |
; | |
; | |
; | |
; | |
; | |
; +---------------+
;----
;
; ;--------------------------------------------------------------------------;
; ; Define and set 'player' ;
; ;,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,;
;
;,,,,
;
Global PlayerX
Global PlayerY
; And we want to place our player in the center of the tilemap.
; Meaning in the center in world coordinates.
;
; As we know the size of each tile, we know the *real* size of
; our tilemap on screen, right?
;
; O---+---+---+- -
; | | | |
; | | | |
; +---+---+---+- -
; | | | |
; . . . .
;
; So, for the horizontal axis:
;
; Horizontal number of tiles * Horizontal tile image size =
; Horizontal tile map screen size (World)
;
; So.. 10 * 32 = 320 pixels wide
; Naturally, the center would be 160, so divided by 2.
PlayerX = Tilemap_Width * TileImage_Width / 2
PlayerY = Tilemap_Height * TileImage_Height / 2
;
;----
;
; ;--------------------------------------------------------------------------;
; ; The game ;
; ;,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,;
;
;,,,,
;
SetBuffer BackBuffer ()
Repeat
;,,,,
; Draw tile map
;----
For TileY = 1 To Tilemap_Height
For TileX = 1 To Tilemap_Width
Image = TileImages
Frame = Tilemap( TileX , TileY )
; What we are doing here is converting the
; tilemap coordinate
; to
; world/real/screen coordinate
; right?
;
; The tile alignment in the array:
;
; 1 2 3 4 ...
;
; For example, the tiles are 32 pixels wide,
; so if we want our tiles to start at (0,0) on screen,
; the alignment on screen/in our world would be:
;
; 0 31 63 95 ...
Screen_PosX = ( TileX - 1 ) * TileImage_Width
Screen_PosY = ( TileY - 1 ) * TileImage_Width
DrawImage Image , Screen_PosX , Screen_PosY , Frame
Next
Next
;,,,,
; Draw player
;----
DrawImage PlayerImage , PlayerX , PlayerY
;,,,,
; Player control
;----
; Suppose the player presses the 'up' key.
; We have to know if the player can go there.
; We could check this by remembering where the player would have been.
; And testing for a collision between the tilemap
; and that player position.
FutureX = PlayerX ; Player is not moving, by default
FutureY = PlayerY
If KeyDown ( 200 ) ; Up
FutureY = PlayerY - 1 ; Moving 1 pixel up
End If
If KeyDown ( 208 ) ; Down
FutureY = PlayerY + 1 ; Moving 1 pixel down
End If
If KeyDown ( 203 ) ; Left
FutureX = PlayerX - 1 ; Moving 1 pixel left
End If
If KeyDown ( 205 ) ; Right
FutureX = PlayerX + 1 ; Moving 1 pixel right
End If
; If the player can be moved 1 pixel at a time,
; and consists of only 4 (2x2) pixels (or more),
; the image could overlap 4 tiles at once.
; If the player image is larger than the size of a tile,
; which it usually is, multiple tiles need to be checked.
; This can be done similarly to drawing the tilemap.
; But instead of drawing the tiles, testing for collision.
;
; To know which tile, for example, is directly under the player,
; you can use basically the same equation as before.
;
; O-------------+-------------+-------------+--- -- - -
; | | | |
; | | | |
; | | | |
; | | | |
; | | | |
; +-------------+-------------+-------------+--- -- - -
; | | | |
; | | | P |
; | | | \ |
; | | | (71,45) |
; | | | |
; +-------------+-------------+-------------+--- -- - -
; | | | |
; | | | |
;
; | | | |
; . . . .
;
; So, suppose our player is at world coordinate (71,45).
; We can see that he's on the third horizontal tile,
; and the second vertical tile.
;
; If we would divide this position by the tile's dimensions;
; 71 / 32 = 2.21875
; 45 / 32 = 1.40625
; Wouldn't that give us what we need?
;
; A handy thing about Blitz is that it uses rounded numbers by default.
; Rounded down, to be exact. So, 2 and 1, respectively.
; Technically; automatically typecasted integers; Forget this *LoL* :)
; So if we were to do the above in code to find out which tile (number)
; is directly under the player;
;
; Tile under Player = Player pos / Tile image size
;
; We would be converting from
; world/real/screen coordinates
; to
; tilemap coordinates
; right?
;
; Only thing is, as you can see in above results, is that our resulting
; tile number, when rounded down, starts at zero, instead of one.
; I mean, we were using a 1-based tilemap, so starting from one.
; So, just add 1 :)
TileUnderPlayerX = PlayerX / TileImage_Width + 1
TileUnderPlayerY = PlayerY / TileImage_Height + 1
; Now, I'm gonna assume you're using a player image that's slightly
; larger than the size of a tile, say 48 by 48 pixels.
; So the player could overlap 9 tiles at a time.
;
; Effectively, we could check all surrounding tiles and the tile
; directly under the player.
;
; O----+----+----+----+-- -
; | |1 |2 |3 |
; | | | | |
; +----+----+----+----+-- -
; | |4 |5 |6 |
; | | |P | |
; +----+----+----+----+-- -
; | |7 |8 |9 |
; | | | | |
; +----+----+----+----+-- -
; | | | | |
; . . . . .
;
; In the pic above you can see which tiles should be checked.
; The pic below shows an easy way to do this with 2 For loops.
; Yes, just like drawing the tilemap.
;
; O----+----+----+----+-- -
; | |1,1 |2,1 |3,1 |
; | | | | |
; +----+----+----+----+-- -
; | |1,2 |2,2 |3,2 |
; | | |P | |
; +----+----+----+----+-- -
; | |1,3 |2,3 |3,3 |
; | | | | |
; +----+----+----+----+-- -
; | | | | |
; . . . . .
TileStartX = TileUnderPlayerX - 1 ; Upper left of tile under player
TileStartY = TileUnderPlayerY - 1
TileEndX = TileUnderPlayerX + 1 ; Lower right of tile under player
TileEndY = TileUnderPlayerY + 1
For TileX = TileStartX To TileEndX
For TileY = TileStartY To TileEndY
; For the sake of clarity:
PlayImg = PlayerImage
PlayFrame = 0
PlayPosX = FutureX ; Test for collision with future player position
PlayPosY = FutureY
TileImg = TileImages
TileFrame = Tilemap( TileX , TileY )
; We're gonna need the world/real/screen coordinates
; of this tile again to test for a collision.
;
; 1 2 3 4 ...
;
; | | | |
; v v v v
;
; 0 31 63 95 ...
TilePosX = ( TileX - 1 ) * TileImage_Width
TilePosY = ( TileY - 1 ) * TileImage_Height
; Ofcourse we only want to test for a collision with tiles that
; the player may not walk on.
; Suppose any tile number above 1 is 'impassible'.
;--(Debug)
; For debug purposes, so you can see this algorithm in action,
; I'm drawing hollow rectangles over the tilemap where we:
; (1) are testing for a collision (green).
; (2) have found an 'impassible' tile (orange).
; (3) have found a collision (red).
Color 0 , 255 , 0 ; By default -> green.
;--(Debug)
If TileFrame > 1 ; 2 or higher, e.g. the 3rd or 4th frame.
;--(Debug)
Color 255 , 128 , 0 ; Found 'impassible' tile -> orange.
;--(Debug)
If ImagesCollide ( PlayImg , PlayPosX , PlayPosY , PlayFrame , TileImg , TilePosX , TilePosY , TileFrame )
; If player collides, set only a flag.
; Because the player might collide with more than 1 tile.
;
; There are many ways to do this ofcourse.
; For example, if you were using a function for this you could use 'Return'.
Collision = True
;--(Debug)
Color 255 , 0 , 0 ; Collision -> red.
;--(Debug)
End If
End If
;--(Debug)
; Draw a hollow rectangle over the current tile
; using the currently selected color.
Rect TilePosX , TilePosY , TileImage_Width , TileImage_Height , False
;--(Debug)
Next
Next
; After testing for a collision between player and tilemap (or tiles 'possibly' under player),
; see if there was indeed a collision.
If Collision
Collision = False ; Reset the flag, so we don't automatically have a collision next time.
; The player may not move, so leaving the current position unchanged.
Else ; There was no collision
PlayerX = FutureX ; Move the player to where 'it would have been'.
PlayerY = FutureY
End If
;,,,,
; Show on screen
;----
Flip
Cls
Until KeyHit ( 1 )
End
;
;----
;
; ;--------------------------------------------------------------------------;
; ; Things to look out for ;
; ;,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,;
;
;,,,,,,,,
; 0-based/1-based orientation
;----
;
; It's a pesky thingy that always comes back at ya.
; Heck, even some Blitz commands start counting from 1,
; and others start counting from 0.
; Even though, easily solved by a +1 or -1,
; it can cause bedazzling misalignments.
;
;,,,,
; X/Y order
;----
;
; For example, when using 2 For loops to iterate through
; a 2-dimensional array, the values could be read in, respectively,
; from left to right, and from top to bottom, like in the above code.
; When mixing X with Y a lot you could even rotate the entire tilemap
; 90 degrees clockwise or counter-clockwise.
;----
;
; ;--------------------------------------------------------------------------;
; ; Notes ;
; ;,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,;
;
;,,,,,,,,
; Improvement
;----
;
; Yes! I've left a lot of room for optimization to keep the code
; relatively small and easy to read.
;
; For example in the collision detection routine,
; a call to ImagesOverlap could speed up the process immensely.
; E.g. checking if the images overlap (bounding box checking)
; is a lot faster than checking each pixel (!).
;
; Another great speed-up is checking for collisions
; only when the player is actually moving.
;
; But wait - there's more! :P
; The code can be easily modified to feature the screen following
; the player world coordinates instead of a static position (0,0).
;
; Well, I'll leave all of these up to you :o)
;
; Any probs, just ask ;)
;
;,,,,
; Coordinate conversion
;----
;
; You could also use a set of functions to convert a position from one
; coordinate system to another. For example:
Function WorldX_To_TileX ( World_PosX )
Return World_PosX / TileImage_Width
End Function
Function TileX_To_WorldX ( Tile_PosX )
Return Tile_PosX * TileImage_Width
End Function
; Personally, I do this a lot to make the code look a lot more readable.
; And above all - no more maths! :P
; (Maths give me headaches)
;----