Author Topic: Zelda-style pixel by pixel scrolling engine (uses GDK)  (Read 939 times)

Lachie Dazdarian

  • Sr. Member
  • ****
  • Posts: 280
    • http://games.freebasic.net
    • Email
Zelda-style pixel by pixel scrolling engine (uses GDK)
« on: September 24, 2011, 01:51:40 PM »
I've finished porting my QB engine to QB64/GDK, but it's still missing few features. It's heavily commented and highly educational. Hope someone can find it useful.

Code: [Select]
DECLARE SUB AINPC (currentNPC%) ' A sub that controls the movement of NPCs
DECLARE SUB Engine.DrawNPCs () ' A sub that draws NPCs
DECLARE SUB Engine.DrawPlayer (Player AS ANY) ' A sub that draws the player sprite
DECLARE SUB Engine.UpdatePlayer (Player AS ANY, Direction%) ' A sub that moves the player
DECLARE SUB Engine.UpdateCamera (Level AS ANY, Player AS ANY) ' A sub that moves the camera according to player position
DECLARE SUB Engine.DrawScreen () ' A sub that calls all other drawing subs
DECLARE SUB Engine.DrawMap () ' A sub that draws the map tiles
DECLARE SUB InitVariables () ' A sub that initiates game variables
DECLARE SUB InitMap (mapcon AS STRING) ' A sub that loads a map you specify
DECLARE SUB MainLoop () ' The main loop
DECLARE SUB MainScreen ()  ' Extra sub for intro screen (simple and boring)
DECLARE FUNCTION Engine.TileCollide% (Player AS ANY)
'============================================================================
DECLARE SUB Engine.DoCollision (currentNPC%)
' Sub to detect collison between
' Hero and any moving/static sprite
'============================================================================

DEFINT A-Z
'$DYNAMIC

'========Declare types here
TYPE SpriteType
    Typ AS INTEGER ' Used to flag the type of sprite (like for different NPCs)
    X AS SINGLE ' Used to flag the X position of the sprite
    Y AS SINGLE ' Used to flag the Y position of the sprite
    XV AS SINGLE ' Used to flag the X velocity (speed) of the sprite
    YV AS SINGLE ' Used to flag the Y velocity (speed) of the sprite
    Frame AS INTEGER ' Used to flag the sprite frame to draw
    Move AS INTEGER ' Used to flag the movement of a sprite (moving or not)
    AI AS INTEGER ' Used to flag AI mode of the sprite (like different NPCs)
    Active AS INTEGER ' Used to flag is a sprite is active or not
    Money AS INTEGER ' Various gameplay related variables not used
    HP AS INTEGER ' in this engine.
    Item AS INTEGER
    Direction AS INTEGER ' used to flag the direction of the sprite
    TileX AS INTEGER ' Use to flag the tile on which the sprite is
    TileY AS INTEGER
END TYPE

TYPE LevelType ' Our level (map) type
    Xmax AS INTEGER ' Maximum number of tiles a map has in x direction
    Ymax AS INTEGER ' Maximum number of tiles a map has in y direction
    CamX AS INTEGER ' Pixel*Pixel camera X position
    CamY AS INTEGER ' Pixel*Pixel camera Y position
    TileX AS INTEGER ' Tile postion of Camera
    TileY AS INTEGER ' Calculated by CamX\TileSize
    Xpos AS INTEGER ' Pixel position inside the tile
    Ypos AS INTEGER ' (used for Scrolling)
END TYPE


TYPE MapLayerType ' Map layers
    BaseL AS INTEGER ' Base Layer Implemented
    FringeL AS INTEGER ' Fringe     Not Implemented
    ObjectL AS INTEGER ' Object     Not Implemented
END TYPE


'========Declare constants here
CONST NULL = 0
CONST TRUE = 1
CONST FALSE = 0


' Screen constants
CONST ScrnXmax = 640, ScrnYmax = 480
CONST ScrnXmid = ScrnXmax / 2, ScrnYmid = ScrnYmax / 2
CONST ScrnXmin = 0, ScrnYmin = 0

' Tile dimensions
CONST TileW = 20
CONST TileH = 20

' Number of Tiles per screen 640\20=32,480\20=24
' Used to calculate the DrawMap sub
CONST ScrnTileXmax = ScrnXmax \ TileW
CONST ScrnTileYmax = ScrnYmax \ TileH

' Directional constants for easy sprite handling
' DN=Neutral(not Moving), DR=Right...........
CONST DN = 0, DR = 1, DU = 2, DL = 3, DD = 4

' Our map array is declared here (must be able to fit the largest map)
REDIM SHARED Map(50, 50) AS MapLayerType


DIM SHARED Level AS LevelType ' Our Level (map)
DIM SHARED Hero AS SpriteType ' Our Hero
DIM SHARED NPC(15) AS SpriteType ' Our Non-Player Controled Sprites

' Other shared variables
DIM SHARED OldX, OldY, mapcon$, OldDir
DIM SHARED FPS, FPS2, OldNPCX, OldNPCY
DIM SHARED MapXMax AS INTEGER, MapYMax AS INTEGER

' GDK related variables
DIM SHARED TSheet AS Tileset ' The tile sheet
DIM SHARED TileXY(1 TO 90) AS XY ' Tile positions on the sheet allways start from 1
DIM SHARED SprSheet AS Sprite ' The sprite sheet
DIM SHARED KB(1) AS KeyBoardState

' Sets up the screen
GDK_Screen_SetRes MainScrn&, ScrnXmax, ScrnYmax, 32, 0

' Loads the tile and sprites sheet, and sets the transparent color
' for sprites
GDK_Tileset_New TSheet, "tiles.bmp", TileXY(), 16, 2, _RGB(0, 0, 0), 1, 1
GDK_Sprite_New SprSheet, "sprites.bmp", 16, 1, 16, 1
GDK_Sprite_Show SprSheet
GDK_Sprite_SetAlpha SprSheet, &HFF00FF

DIM SHARED FNT AS SPRITEFONT
GDK_SpriteFont_New FNT, "24bitFNT3.BMP"
GDK_SpriteFont_SetAlpha FNT, _RGBA(0, 0, 0, 0)


MainScreen ' loads introducing screen

InitMap "MAP1.MAP" 'Read our map array (from a file)

InitVariables 'Initialize our variables

MainLoop ' Main Loop

END


SUB AINPC (currentNPC) STATIC
' This is a sub which moves the NPCs. Their direction
' is changed randomly). In a proper game you should create a
' real artificial smarts (AS) algorithm, where NPCs will
' react on the hero.

NPC(currentNPC).Move = TRUE ' in this example NPC are always moving.

RANDOMIZE TIMER
ChangeDirec = INT(RND * 80) + 1
' If a number randomized from 1 to 200 turns out to be 20
' current NPC direction is changed.
IF ChangeDirec = 20 THEN NPC(currentNPC).Direction = INT(RND * 4) + 1

' According to direction move the NPC and check if the NPC is out
' of map boundary.
SELECT CASE NPC(currentNPC).Direction
    CASE 4
        NPC(currentNPC).X = NPC(currentNPC).X + NPC(currentNPC).XV
        IF Engine.TileCollide(NPC(currentNPC)) THEN NPC(currentNPC).X = NPC(currentNPC).X - NPC(currentNPC).XV
        IF NPC(currentNPC).X > (Level.Xmax * TileW) - TileW THEN NPC(currentNPC).X = (Level.Xmax * TileW) - TileW
    CASE 2
        NPC(currentNPC).Y = NPC(currentNPC).Y - NPC(currentNPC).YV
        IF Engine.TileCollide(NPC(currentNPC)) THEN NPC(currentNPC).Y = NPC(currentNPC).Y + NPC(currentNPC).YV
        IF NPC(currentNPC).Y < ScrnYmin THEN NPC(currentNPC).Y = ScrnYmin
    CASE 3
        NPC(currentNPC).X = NPC(currentNPC).X - NPC(currentNPC).XV
        IF Engine.TileCollide(NPC(currentNPC)) THEN NPC(currentNPC).X = NPC(currentNPC).X + NPC(currentNPC).XV
        IF NPC(currentNPC).X < ScrnXmin THEN NPC(currentNPC).X = ScrnXmin
    CASE 1
        NPC(currentNPC).Y = NPC(currentNPC).Y + NPC(currentNPC).YV
        IF Engine.TileCollide(NPC(currentNPC)) THEN NPC(currentNPC).Y = NPC(currentNPC).Y - NPC(currentNPC).YV
        IF NPC(currentNPC).Y > (Level.Ymax * TileH) - TileH THEN NPC(currentNPC).Y = (Level.Ymax * TileH) - TileH
    CASE ELSE
END SELECT

END SUB

SUB Engine.DoCollision (currentNPC)

' NOT IMPLEMENTED YET

END SUB

SUB Engine.DrawMap STATIC

'Notes:
' 1.Mod our Cam with Tile size to get correct offset inside the tile
' 2.Uses the constants TileH, TileW, ScrnTileXmax, ScrnTileYmax
' 3.Constants are declared at the module level

' ==========BaseLayer====================
' Calculate the first tile to draw
' depending on the camera position.
Level.TileX = Level.CamX \ TileW
Level.TileY = Level.CamY \ TileH
                               
' Get the offset inside the tile
Level.Xpos = Level.CamX MOD TileW ' Position in the tile (0 to 19)
Level.Ypos = Level.CamY MOD TileH ' 20*20 tile size (change to fit your needs)

' Example:
' Formula for X (the loop below)
' (X*TileW) => screen coordinates (0 to 640 step 20)
' (X*TileW)-Level.Xpos => tile offset that we have to show from 0 to 20-Xpos
' Assuming Level.Xpos = 4
' So if X*TileW=0 then (X*TileW)-Level.Xpos=-4
' We start drawing from -4 to -4+20
' ie, first row of tiles is placed from pixel X = -4 to X = 16,
' then the next row of tiles is placed from X = 17 to X = 17+16
' Hope you understand.
' Level.XPos and Level.YPos change as you move throught the map, always
' calculating the right OFFSET!

' Loop through our visible screen and draw tiles on it.
' Always start from Level.TileX and Level.TileY (depends
' on the camera position).
FOR XT = 0 TO ScrnTileXmax ' 32 tiles (640/20)
    FOR YT = 0 TO ScrnTileYmax ' 24 tiles (480/20)

        ' We flag which tile to draw. Note how the Map array is used.
        tilep = Map(XT + Level.TileX, YT + Level.TileY).BaseL

        ' GDL drawing function (tilep > 0 condition is used for safety).
        IF tilep > 0 THEN GDK_Tile_Draw TSheet, tilep, TileXY(), (XT * TileW) - Level.Xpos, (YT * TileH) - Level.Ypos

    NEXT YT
NEXT XT

'==========FringeLayer====================
' NOT IMPLEMENTED

'==========ObjectLayer====================
' NOT IMPLEMENTED

END SUB

SUB Engine.DrawNPCs STATIC

' This is the main sub where we move our NPCs

' Every 7th loop we change Frame2 which
' animates the NPCs (with Frame1 we control
' the speed of animation.
Frame1 = (Frame1 MOD 7) + 1
IF Frame1 = 3 THEN Frame2 = (Frame2 MOD 2) + 1

FOR countNPC = 1 TO 15 ' We loop through all of our 15 NPC and move/paste them.

    ' 15 can be a variable (different for each level,
    ' different number of enemies for each level).

    OldNPCX = NPC(countNPC).X ' we save old position of NPCs for collision
    OldNPCY = NPC(countNPC).Y ' purposes

    Frame3 = 1

    IF NPC(countNPC).Move = TRUE THEN Frame3 = Frame2
    ' if the NPC is walking (Move = TRUE) then Frame3 variable
    ' is Frame2 which changes from 1 to 2.

    ' According to NPC direction we flag the proper
    ' frame (ie. for down, the Frame is either 9 or 10 (8 + 1 or 8 + 2).
    SELECT CASE NPC(countNPC).Direction
        CASE 1
            NPC(countNPC).Frame = 8 + Frame3
        CASE 2
            NPC(countNPC).Frame = 10 + Frame3
        CASE 3
            NPC(countNPC).Frame = 12 + Frame3
        CASE 4
            NPC(countNPC).Frame = 14 + Frame3
    END SELECT

    AINPC countNPC
    ' AI for NPCs. We use AI on the current NPC.
    ' Note how the AINPC sub is declared and how countNPC
    ' value is passed into the AINPC sub!

    Engine.DoCollision countNPC
    ' we check collision from a NPC toward
    ' other NPCs or Hero

    IF NPC(countNPC).Frame <> 0 THEN ' safety precaution
        GDK_Sprite_DrawXY SprSheet, (NPC(countNPC).X - Level.CamX), (NPC(countNPC).Y - Level.CamY), 0, NPC(countNPC).Frame
    END IF

NEXT countNPC

END SUB

SUB Engine.DrawPlayer (Player AS SpriteType) STATIC

'Notes:
'1. Uses the Directional Constants defined at module level (DN,DR...)


IF Player.Direction <> OldDir THEN ' if Player changes direction
    DirChanged = 1 ' glag it to change base frame
ELSE
    ' Delays frame rotation for sprite(Electric legs :))
    DirChanged = (DirChanged MOD 4) + 1 'Try to REM this :)
END IF

IF DirChanged = 1 THEN
    IF Player.Move = TRUE THEN ' Animate only if player moves.
        Frame = (Frame MOD 2) + 1
        SELECT CASE Player.Direction
            CASE DR
                'Calculate the Frame of the sprite to draw
                Player.Frame = Frame + 6 ' frame is 7 or 8
            CASE DU
                Player.Frame = Frame + 2 ' frame is 3 or 4
            CASE DL
                Player.Frame = Frame + 4 ' frame is 5 or 6
            CASE DD
                Player.Frame = Frame ' frame is 1 or 2
            CASE ELSE
        END SELECT

        Player.Move = FALSE
        ' Stops the Player from Swaying
        ' his arms while not moving.
    END IF
END IF

' Formula: X (same goes for Y)
' Player.X-Level.CamX
' Just puts the player at the Center of screen
' So if Player.X=500 then CamX=Player.X-ScrnYmid(ScrnYmid=Middle of 640=>320)
' CamX:500-320=180
' Player.X=500
' Xcenter:500-180=320 (ScrnXmid)
' Same goes for Y
' See Engine.UpdateCamera for more Details :)

IF Player.Frame <> 0 THEN 'Just to be sure to prevent errors
    GDK_Sprite_DrawXY SprSheet, (Player.X - Level.CamX), (Player.Y - Level.CamY), 0, Player.Frame
END IF

OldDir = Player.Direction

END SUB

SUB Engine.DrawScreen STATIC

Engine.DrawMap ' Draws our map onto screen
Engine.DrawPlayer Hero ' Draws our hero onto screen
Engine.DrawNPCs ' Pastes our NPCs onto screen

'RelFont256 VARSEG(Page(0)), 0, 0, "FPS:" + STR$(FPS2), FALSE, Font(), FontIndex()


END SUB

FUNCTION Engine.TileCollide (Player AS SpriteType) STATIC

' Crappy tile*tile collision detection ;)
' Returns TRUE if collision is detected, FALSE if not

' Init the function to be FALSE (no collision)
Engine.TileCollide = FALSE


' 4 checks are done to be sure ;)
' Up-Left corner of hero
X = Player.X + 1 'Add 1 for 20*20 Box Collision
Y = Player.Y + 10
GOSUB CheckForTile 'check for the tile

' Up-Right corner of hero
X = Player.X + 19
Y = Player.Y + 10
GOSUB CheckForTile

' Down-Right corner of hero
X = Player.X + 19
Y = Player.Y + 19
GOSUB CheckForTile

' Down-Left corner of hero
X = Player.X + 1
Y = Player.Y + 19
GOSUB CheckForTile

EXIT FUNCTION

' Check for collision:(Bounding BoxType)
CheckForTile:
TX = X \ TileW 'Player.X\TileW=TileX
TY = Y \ TileH 'Player.Y\TileH=TileH

SELECT CASE Map(TX, TY).BaseL ' base layer check
    CASE IS > 5 ' tiles above 5 in the tile sheet are collidable
        Engine.TileCollide = TRUE
    CASE ELSE ' No collision
END SELECT

RETURN ' Check for next Coord

END FUNCTION

SUB Engine.UpdateCamera (Level AS LevelType, Player AS SpriteType) STATIC

' Updates CamX, CamY in relation to Player.X, Player.Y to achieve
' ZELDA style scrolling engine.
' Sample Code:
' CODE: CASE DR
' Right Direction of movement
' CODE: Level.CamX = Player.X - ScrnXmid
' Center our player and Moves the camera to where
' the player is going.
' ie, Assume: Player.X = 1200, ScrnMid = 320 (constant, middle of the screen)
' Level.CamX: 1200-320=880
' to get the Level.TileX:
' Level.TileX = Level.CamX\TileW=1040\20=44 (This will be used with Engine.DrawMap)
' CODE: IF Level.CamX < ScrnXmin THEN Level.CamX = ScrnXmin
' Check if Level.CamX < 0, zero it if its negative to prevent errors.
' ScrnYmin = 0 (Constant)
' CODE: IF Level.CamX > (Level.Xmax * TileW) - ScrnXmax THEN Level.CamX = (Level.Xmax * TileW) - ScrnXmax
' Check if Level.CamX > (Level.Xmax * TileW) - ScrnXmax
' (Level.Xmax * TileW) - ScrnXmax => Maximum number of PIXELS the Map has
' Level.Xmax => max num of tile for map (shared variable Level.Element)
' TileW => width of tile (constant)
' ScrnXmax = 640 (constant, depends on screen resolution)
' To calculate: Level.Xmax = MapXmax
' Formula: (Level.Xmax * TileW) - ScrnXmax
' Level.Xmax = 37
' (37*20)-640 = 100
' Level.CamX = 100
' To calculate TileX:
' Level.CamX\TileW
' 100\20=5
' Level.TileX = 5 (start Drawing from 5 to 37)
' 5 is the first row of tiles to draw in X
' 37-5=32 (see we have to draw 32 tiles horizontally!!!)
' Same goes for Y.
' See Engine.DrawMap SUB for more details. ;)

SELECT CASE Player.Direction
    CASE DR
        Level.CamX = Player.X - ScrnXmid
        IF Level.CamX < ScrnXmin THEN Level.CamX = ScrnXmin
        IF Level.CamX > (Level.Xmax * TileW) - ScrnXmax THEN Level.CamX = (Level.Xmax * TileW) - ScrnXmax
    CASE DU
        Level.CamY = Player.Y - ScrnYmid
        IF Level.CamY < ScrnYmin THEN Level.CamY = ScrnYmin
        IF Level.CamY > (Level.Ymax * TileH) - ScrnYmax THEN Level.CamY = (Level.Ymax * TileH) - ScrnYmax
    CASE DL
        Level.CamX = Player.X - ScrnXmid
        IF Level.CamX < ScrnXmin THEN Level.CamX = ScrnXmin
        IF Level.CamX > (Level.Xmax * TileW) - ScrnXmax THEN Level.CamX = (Level.Xmax * TileW) - ScrnXmax
    CASE DD
        Level.CamY = Player.Y - ScrnYmid
        IF Level.CamY < ScrnYmin THEN Level.CamY = ScrnYmin
        IF Level.CamY > (Level.Ymax * TileH) - ScrnYmax THEN Level.CamY = (Level.Ymax * TileH) - ScrnYmax
    CASE ELSE
END SELECT

END SUB

SUB Engine.UpdatePlayer (Player AS SpriteType, Direction)

' Updates the player's position according to its direction
' ZELDA style pixel*pixel free movement.
' Sample Code:
' CODE: CASE DR
' Direction of Player movement
' CODE: Player.X = Player.X + Player.XV
' Add Xspeed to Player X position since we are moving right
' CODE: IF Engine.TileCollide(Hero) THEN Player.X = Player.X - Player.XV
' Check for collision. If collided with valid "collidable" tile
' Subtract Xspeed to return the player to its original place.
' Also prevents player sticking to tiles when changing direction.
' Flag that the player is not moving (if we don't want "pushing" tile effect)
' CODE: IF Player.X > (Level.Xmax * TileW) - TileW THEN Player.X = (Level.Xmax * TileW) - TileW
' checks if player is outside of World map boudaries.
' (Level.Xmax * TileW) - TileW=(3*20)-20
' Subracting 20 (TileW) is necessary to prevent errors if your speed
' is greater than 1. Also used for padding.

SELECT CASE Direction
    CASE DR
        Player.X = Player.X + Player.XV
        Player.Move = TRUE
        IF Engine.TileCollide(Hero) THEN
            Player.X = Player.X - Player.XV
            Player.Move = FALSE
        END IF
        IF Player.X > (Level.Xmax * TileW) - TileW THEN Player.X = (Level.Xmax * TileW) - TileW
    CASE DU
        Player.Y = Player.Y - Player.YV
        Player.Move = TRUE
        IF Engine.TileCollide(Hero) THEN
            Player.Y = Player.Y + Player.YV
            Player.Move = FALSE
        END IF
        IF Player.Y < ScrnYmin THEN Player.Y = ScrnYmin
    CASE DL
        Player.X = Player.X - Player.XV
        Player.Move = TRUE
        IF Engine.TileCollide(Hero) THEN
            Player.X = Player.X + Player.XV
            Player.Move = FALSE
        END IF
        IF Player.X < ScrnXmin THEN Player.X = ScrnXmin
    CASE DD
        Player.Y = Player.Y + Player.YV
        Player.Move = TRUE
        IF Engine.TileCollide(Hero) THEN
            Player.Y = Player.Y - Player.YV
            Player.Move = FALSE
        END IF
        IF Player.Y > (Level.Ymax * TileH) - TileH THEN Player.Y = (Level.Ymax * TileH) - TileH
    CASE ELSE
END SELECT

' Calculate what Tile the player is
' Add 10 to X and Y to get center of Player since tile size is 20*20
Player.TileX = (Player.X + 10) \ TileW ' Center of Sprite
Player.TileY = (Player.Y + 10) \ TileH

END SUB


SUB InitMap (mapcon AS STRING)

' This sub loads the map data from an external file. You are
' advised to use some map editing tool. Map loading is not glued
' to the very scrolling engine. This map loader first loads the
' map's x size and map's y size (in number of tiles) and then the
' very tiles. You should leave (create) one line of unused tiles
' horizontally and vertically on the right and down edge of the
' map to avoid errors.

OPEN mapcon FOR INPUT AS #2
INPUT #2, MapXMax
INPUT #2, MapYMax
FOR Y = 0 TO MapYMax
    FOR X = 0 TO MapXMax
        INPUT #2, Map(X, Y).BaseL ' Map is loaded from a file (note the INPUT #2
    NEXT X ' statement) and stored into the map array.
NEXT Y
CLOSE #2

END SUB

SUB InitVariables STATIC

' Hero Starting Variables
' Center-down
Hero.Typ = 1 'ID?????????
Hero.X = 15 * TileW ' Change this to where you want the player to
Hero.Y = 20 * TileH ' Start (be sure not to go over the Map dimensions)
Hero.XV = 2 ' XSpeed change this for faster/slower movement
Hero.YV = 2 ' Yspeed
Hero.Frame = 1 ' We start at frame 1 (looking-at-screen-sprite) :)
Hero.Move = FALSE ' Static(no movement) :)
Hero.Active = TRUE
Hero.AI = 1
Hero.Money = 200
Hero.HP = 20
Hero.Item = 1
Hero.Direction = DU
Hero.TileX = 0
Hero.TileY = 0

FOR countNPC = 1 TO 15 'we loop thur our all 15 NPCs

    NPC(countNPC).XV = 0.8 ' X and Y speeds of out NPCs
    NPC(countNPC).YV = 0.8

    RANDOMIZE TIMER ' We randomly choose the starting
    XRND = INT(RND * 30) + 2 ' positions of our NPCs. This is how
    YRND = INT(RND * 20) + 2 ' you randomize numbers. For example,
    ' for X position of a NPC we use a XRND
    ' variable which can be any number from
    ' 0 to 30 plus 2!
    ' NPC might be randomized into a
    ' solid tile so they will be stuck.
    ' Don't mind that since in your game
    ' you'll manualy put sprites on their
    ' positions or re-randomize sprites
    ' if the turn out to be on a solid
    ' tile.

    NPC(countNPC).Direction = 1
    NPC(countNPC).X = XRND * 20 ' We multiply XRND or YRND with 20 to
    NPC(countNPC).Y = XRND * 20 ' put a NPC on XRND and YRND tile!
NEXT countNPC

Level.Xmax = MapXMax
Level.Ymax = MapYMax
Level.CamX = 0
Level.CamY = 0
Level.TileX = 0
Level.TileY = 0
Level.Xpos = 0
Level.Ypos = 0

Hero.Direction = DU ' Two directions so that engine will
Engine.UpdateCamera Level, Hero ' know where level TileX and TileY are.
Hero.Direction = DR ' Positions from left to right.
Engine.UpdateCamera Level, Hero ' No warping ;)

END SUB

DEFINT A-Z
SUB MainLoop STATIC

DO

    _LIMIT 60

    GDK_Keyboard_GetState KB(0)

    Engine.DrawScreen

    IF KB(0).ESC THEN EXIT DO 'Emergency Exit

    IF KB(0).Up THEN ' Pressed UP
        Hero.Direction = DU ' Direction = Up
        OldX = Hero.X ' Store our old variables
        OldY = Hero.Y ' (can be used with collision routines)
        Engine.UpdatePlayer Hero, DU ' Update Player position
        Engine.UpdateCamera Level, Hero ' Update camera position
    END IF

    IF KB(0).Down THEN
        Hero.Direction = DD
        OldX = Hero.X
        OldY = Hero.Y
        Engine.UpdatePlayer Hero, DD
        Engine.UpdateCamera Level, Hero
    END IF
    IF KB(0).Right THEN
        Hero.Direction = DR
        OldX = Hero.X
        OldY = Hero.Y
        Engine.UpdatePlayer Hero, DR
        Engine.UpdateCamera Level, Hero
    END IF
    IF KB(0).Left THEN
        Hero.Direction = DL
        OldX = Hero.X
        OldY = Hero.Y
        Engine.UpdatePlayer Hero, DL
        Engine.UpdateCamera Level, Hero
    END IF

    _DISPLAY

LOOP UNTIL KB(0).ESC

END SUB

SUB MainScreen


DO

    _LIMIT 60

    GDK_Keyboard_GetState KB(0)

    Text$ = "Pixel by pixel scrolling engine"
    GDK_Print ScrnXmid - (GDK_PrintWidth(Text$, FNT) / 2), 40, Text$, FNT
    Text$ = "by R.Eric.Lope"
    GDK_Print ScrnXmid - (GDK_PrintWidth(Text$, FNT) / 2), 60, Text$, FNT
    Text$ = "Tiles by Ingmar Steen and Lachie D."
    GDK_Print ScrnXmid - (GDK_PrintWidth(Text$, FNT) / 2), 80, Text$, FNT
    Text$ = "Recoded for QB64 with GDK by Lachie D. in 2011"
    GDK_Print ScrnXmid - (GDK_PrintWidth(Text$, FNT) / 2), 100, Text$, FNT

    Text$ = "PRESS SPACE TO START"
    GDK_Print ScrnXmid - (GDK_PrintWidth(Text$, FNT) / 2), 160, Text$, FNT

    _DISPLAY

LOOP UNTIL KB(0).SPACE

END SUB

REM $INCLUDE:'UnseenGDK_alt.bm'

' GDK sprite functions
TYPE SPRITEFONT
    Image AS Sprite
    Spacing AS INTEGER
END TYPE


SUB GDK_SpriteFont_New (FNT AS SPRITEFONT, File$)
GDK_Sprite_New FNT.Image, File$, 13, 8, 13 * 8, 1
GDK_Sprite_Show FNT.Image
FNT.Spacing = 1
END SUB


SUB GDK_SpriteFont_SetAlpha (FNT AS SPRITEFONT, Alpha~&)
GDK_Sprite_SetAlpha FNT.Image, Alpha~&
END SUB


SUB GDK_SpriteFont_SetSpacing (FNT AS SPRITEFONT, Spacing%)
FNT.Spacing = Spacing%
END SUB


SUB GDK_SpriteFont_SetScale (FNT AS SPRITEFONT, Scale!)
FNT.Image.Scale = Scale!
END SUB


FUNCTION GDK_PrintWidth (Text$, FNT AS SPRITEFONT)
OldDest& = _DEST
OldSource& = _SOURCE
FONTSTRING$ = " ! #$%&'()*+ ,-./01234567 89:;<=>?@ABC DEFGHIJKLMNO PQRSTUVWXYZ[ \]^-`abcdefg hijklmnopqrs tuvwxyz{|}~ "
FOR i% = 1 TO LEN(Text$)
    LtrVal% = INSTR(FONTSTRING$, MID$(Text$, i%, 1))
    IF LtrVal% THEN
        IF LtrVal% > 1 THEN
            TmpImage& = _NEWIMAGE(((FNT.Image.Width / FNT.Image.XFrameCount) * FNT.Image.Scale), ((FNT.Image.Height / FNT.Image.YFrameCount) * FNT.Image.Scale), 32)
            _DEST TmpImage&
            GDK_Sprite_DrawXY FNT.Image, 0, 0, 0, LtrVal%
            H& = _HEIGHT(TmpImage&) - 1
            W& = _WIDTH(TmpImage&) - 1
            FirstY% = H&
            FirstX% = W&
            LastY% = 0
            LastX% = 0
            _SOURCE TmpImage&
            FOR k% = 0 TO H&
                FOR j% = 0 TO W&
                    C& = POINT(j%, k%)
                    IF C& <> FNT.Image.Alpha THEN
                        IF j% < FirstX% THEN FirstX% = j%
                        IF j% > LastX% THEN LastX% = j%
                        IF k% < FirstY% THEN FirstY% = k%
                        IF k% > LastY% THEN LastY% = k%
                    END IF
                NEXT
            NEXT
            TotalWidth% = TotalWidth% + (LastX% - FirstX%) + (FNT.Spacing * FNT.Image.Scale)
            _DEST OldDest&
            _SOURCE OldSource&
            _FREEIMAGE TmpImage&
        ELSE '// a space
            TotalWidth% = TotalWidth% + W& + (FNT.Spacing * FNT.Image.Scale)
        END IF
    END IF
NEXT
GDK_PrintWidth = TotalWidth%
END FUNCTION


FUNCTION GDK_PrintHeight (Text$, FNT AS SPRITEFONT)
OldDest& = _DEST
OldSource& = _SOURCE
FONTSTRING$ = " ! #$%&'()*+ ,-./01234567 89:;<=>?@ABC DEFGHIJKLMNO PQRSTUVWXYZ[ \]^-`abcdefg hijklmnopqrs tuvwxyz{|}~ "
FOR i% = 1 TO LEN(Text$)
    LtrVal% = INSTR(FONTSTRING$, MID$(Text$, i%, 1))
    IF LtrVal% THEN
        IF LtrVal% > 1 THEN
            TmpImage& = _NEWIMAGE(((FNT.Image.Width / FNT.Image.XFrameCount) * FNT.Image.Scale), ((FNT.Image.Height / FNT.Image.YFrameCount) * FNT.Image.Scale), 32)
            _DEST TmpImage&
            GDK_Sprite_DrawXY FNT.Image, 0, 0, 0, LtrVal%
            H& = _HEIGHT(TmpImage&) - 1
            W& = _WIDTH(TmpImage&) - 1
            FirstY% = H&
            FirstX% = W&
            LastY% = 0
            LastX% = 0
            _SOURCE TmpImage&
            FOR k% = 0 TO H&
                FOR j% = 0 TO W&
                    C& = POINT(j%, k%)
                    IF C& <> FNT.Image.Alpha THEN
                        IF j% < FirstX% THEN FirstX% = j%
                        IF j% > LastX% THEN LastX% = j%
                        IF k% < FirstY% THEN FirstY% = k%
                        IF k% > LastY% THEN LastY% = k%
                    END IF
                NEXT
            NEXT
            Height% = LastY% - FirstY%
            IF Height% > MaxHeight% THEN MaxHeight% = Height%
            _DEST OldDest&
            _SOURCE OldSource&
            _FREEIMAGE TmpImage&
        ELSE '// a space
            MaxHeight% = H&
            EXIT FOR
        END IF
    END IF
NEXT
GDK_PrintHeight = MaxHeight%
END FUNCTION



SUB GDK_Print (X%, Y%, Text$, FNT AS SPRITEFONT)
OldX% = X%
OldDest& = _DEST
OldSource& = _SOURCE
FONTSTRING$ = " ! #$%&'()*+ ,-./01234567 89:;<=>?@ABC DEFGHIJKLMNO PQRSTUVWXYZ[ \]^-`abcdefg hijklmnopqrs tuvwxyz{|}~ "
FOR i% = 1 TO LEN(Text$)
    LtrVal% = INSTR(FONTSTRING$, MID$(Text$, i%, 1))
    IF LtrVal% THEN
        TmpImage& = _NEWIMAGE(((FNT.Image.Width / FNT.Image.XFrameCount) * FNT.Image.Scale), ((FNT.Image.Height / FNT.Image.YFrameCount) * FNT.Image.Scale), 32)
        _DEST TmpImage&
        GDK_Sprite_DrawXY FNT.Image, 0, 0, 0, LtrVal%
        H& = _HEIGHT(TmpImage&) - 1
        W& = _WIDTH(TmpImage&) - 1
        FirstY% = H&
        FirstX% = W&
        LastY% = 0
        LastX% = 0
        _SOURCE TmpImage&
        FOR k% = 0 TO H&
            FOR j% = 0 TO W&
                C& = POINT(j%, k%)
                IF C& <> FNT.Image.Alpha THEN
                    IF j% < FirstX% THEN FirstX% = j%
                    IF j% > LastX% THEN LastX% = j%
                    IF k% < FirstY% THEN FirstY% = k%
                    IF k% > LastY% THEN LastY% = k%
                END IF
            NEXT
        NEXT
        WidthDif% = (W& - LastX%) + FirstX%
        HeightDif% = (H& - LastY%) + FirstY%
        _DEST OldDest&
        _SOURCE OldSource&
        IF LtrVal% = 1 THEN
            _PUTIMAGE (X%, Y%)-(X% + W&, Y% + H&), TmpImage&
            XDif% = 1 + W& + FNT.Spacing
        ELSE
            _PUTIMAGE (X%, Y%)-(X% + (W& - WidthDif%), Y% + (H& - HeightDif%)), TmpImage&, , (FirstX%, FirstY%)-(LastX%, LastY%)
            XDif% = 1 + (W& - WidthDif%) + FNT.Spacing
        END IF
        _FREEIMAGE TmpImage&
    END IF
    X% = X% + XDif%
NEXT
X% = OldX%
END SUB


Compiled with all the necessary files to run the code above: http://lachie.phatcode.net/ppengine_QB64.zip
Screenshot: http://lachie.phatcode.net/ppengine_QB64_scrn.png

Muffinman

  • Sr. Member
  • ****
  • Posts: 379
Re: Zelda-style pixel by pixel scrolling engine (uses GDK)
« Reply #1 on: September 28, 2011, 03:48:49 AM »
pretty programs like this get automatic endorsement from me.  the only thing I'd add is a string constant for the path to any resources.  instead of _loadimage ("bam") type loadimage (path$ + "bam").  everyone should do this!

kidfrommars

  • Full Member
  • ***
  • Posts: 215
    • C & Fortran Polyglot
Re: Zelda-style pixel by pixel scrolling engine (uses GDK)
« Reply #2 on: September 28, 2011, 03:44:42 PM »
But what if we don't know the path?

Clippy

  • Hero Member
  • *****
  • Posts: 16446
  • I LOVE π = 4 * ATN(1)    Use the QB64 WIKI >>>
    • Pete's Qbasic Site
    • Email
Re: Zelda-style pixel by pixel scrolling engine (uses GDK)
« Reply #3 on: September 28, 2011, 04:01:56 PM »
Better check that path with _DIREXISTS too!  ;D
QB64 WIKI: Main Page
Download Q-Basics Code Demo: Q-Basics.zip
Download QB64 BAT, IconAdder and VBS shortcuts: QB64BAT.zip
Download QB64 DLL files in a ZIP: Program64.zip

OlDosLover

  • Hero Member
  • *****
  • Posts: 3901
  • OlDosLover
    • Email
Re: Zelda-style pixel by pixel scrolling engine (uses GDK)
« Reply #4 on: September 28, 2011, 08:25:51 PM »
Hi all,
    Thanks Lachie for this valuable contribution to the community. I appreciate your efforts and commend you for this excellent work!
OlDosLover.