fpc/packages/libndsfpc/examples/gl2d/scrolling/scrolling.pp
Legolas 659b386f02 + Added Easy gl2d library for Nintendo DS and 6 new examples
* libndsfpc: Fixed glMaterialShinyness()

git-svn-id: trunk@19932 -
2011-12-31 14:14:12 +00:00

401 lines
11 KiB
ObjectPascal

(*
Easy GL2D
Relminator 2011
Richard Eric M. Lope BSN RN
Http://Rel.Phatcode.Net
A very small, simple, yet very fast DS 2D rendering lib using the DS' 3D core.
--
Translated in Object Pascal by Francesco Lombardi - 2012
http://itaprogaming.free.fr
*)
program scrolling;
{$mode objfpc}
{$H+}
{$L build/crono.o}
{$L build/tiles.o}
uses
ctypes, nds9, gl2d, uvcoord_crono;
const
cronoBitmapLen = 32768;
cronoPalLen = 512;
tilesBitmapLen = 65536;
tilesPalLen = 512;
var
cronoBitmap: array [0..0] of cuint; cvar; external;
cronoPal: array [0..0] of cushort; cvar; external;
tilesBitmap: array [0..0] of cuint; cvar; external;
tilesPal: array [0..0] of cushort; cvar; external;
const
MAP_WIDTH = 32;
MAP_HEIGHT = 32;
(*
I'm using the struct of player from the
Animate simple man/woman exmple in the
"nds/examples" folder
You might want to read up on that too to
see the differnce in handling sprites via OAM
and Easy GL2D.
*)
const
P_RIGHT = 0;
P_UP = 1;
P_DOWN = 2;
P_LEFT = 3;
type
TPlayer = record
x, y: integer;
gfx_frame: integer;
state: integer;
anim_frame: integer;
is_walking: boolean; // an animation flag whether crono is walking or not
end;
PPlayer = ^TPlayer;
// Our level struct
TLevel = record
width: integer; // dimensions of the map
height: integer;
camera_x: integer; // top-left cooordinates of our virtual camera
camera_y: integer; // Works almost the same the 2d BG scroller
tile_x: integer; // current tile the top-left coordinate of our
tile_y: integer; // camera occupies
pixel_x: integer; // scrolling tile offsets
pixel_y: integer;
end;
PLevel = ^TLevel;
TMapArray = array [0..MAP_WIDTH-1, 0..MAP_HEIGHT-1] of cushort;
// Animates crono
procedure AnimatePlayer(p: PPlayer);
const
FRAMES_PER_ANIMATION = 6; // 6 crono animations
frame: integer = 0; // a static frame counter
begin
// Only animate if crono is walking
if (p^.is_walking) then
begin
inc(frame);
// Animate only every 8th frame
// I used an if() block instead of % since % is slow
// on the DS (not that it would matter in this demo)
if ((frame and 7) = 0) then
begin
inc(p^.anim_frame);
if (p^.anim_frame >= (FRAMES_PER_ANIMATION)) then
p^.anim_frame := 0;
end;
end;
// P_RIGHT, P_UP and P_DOWN is calculated normally.
// P_LEFT is P_RIGHT flipped.
case (p^.state) of
P_RIGHT: p^.gfx_frame := p^.anim_frame + p^.state * FRAMES_PER_ANIMATION;
P_UP: p^.gfx_frame := p^.anim_frame + p^.state * FRAMES_PER_ANIMATION;
P_DOWN: p^.gfx_frame := p^.anim_frame + p^.state * FRAMES_PER_ANIMATION;
P_LEFT: p^.gfx_frame := p^.anim_frame + P_RIGHT * FRAMES_PER_ANIMATION;
else
p^.gfx_frame := p^.anim_frame + p^.state * FRAMES_PER_ANIMATION;
end;
end;
// Draws a full screen map
procedure DrawMap(lvl: PLevel; map: TMapArray; tiles: pglImage);
const
// tiles are 16x16 pixels
TILE_SIZE = 16;
// calculate number of tiles per row and column
SCREEN_TILE_X = SCREEN_WIDTH div TILE_SIZE;
SCREEN_TILE_Y = SCREEN_HEIGHT div TILE_SIZE;
var
x, y: integer; // counters
tile_x, tile_y: integer; // current tile to draw
screen_x, screen_y: integer; // actual screen position (in pixel)
i: integer; // tile index to draw
begin
// we need to draw an extra tile at the bottom and right
// since we are scrolling
for y := 0 to SCREEN_TILE_Y do
begin
for x := 0 to SCREEN_TILE_X do
begin
tile_x := lvl^.tile_x + x; // get relative tile positions
tile_y := lvl^.tile_y + y;
i := map[tile_x, tile_y]; // get map index
screen_x := (x * TILE_SIZE) - lvl^.pixel_x; //Calculate where to put a
screen_y := (y * TILE_SIZE) - lvl^.pixel_y; //particular tile
glSprite(screen_x, screen_y, GL_FLIP_NONE , @tiles[i]);
end;
end;
end;
// Update's the camera's position relative to the player
procedure CameraUpdate(lvl: PLevel; p: PPlayer);
const
// set constants for middle of screen
SCREEN_MID_WIDTH = SCREEN_WIDTH div 2;
SCREEN_MID_HEIGHT = SCREEN_HEIGHT div 2;
TILE_SIZE = 16;
begin
// update the camera
lvl^.camera_x := p^.x - SCREEN_MID_WIDTH;
lvl^.camera_y := p^.y - SCREEN_MID_HEIGHT;
// limit camera X values
if ( lvl^.camera_x < 0 ) then lvl^.camera_x := 0;
if ( lvl^.camera_x > ((lvl^.width-2) * TILE_SIZE ) - SCREEN_WIDTH ) then
lvl^.camera_x := ((lvl^.width-2) * TILE_SIZE ) - SCREEN_WIDTH;
// limit camera Y values
if ( lvl^.camera_y < 0 ) then lvl^.camera_y := 0;
if ( lvl^.camera_y > ((lvl^.height-2) * TILE_SIZE ) - SCREEN_HEIGHT ) then
lvl^.camera_y := ((lvl^.height-2) * TILE_SIZE ) - SCREEN_HEIGHT;
// calculate level starting tiles
lvl^.tile_x := lvl^.camera_x div TILE_SIZE;
lvl^.tile_y := lvl^.camera_y div TILE_SIZE;
// calculate tile pixel offsets
// Only works with power of 2 tilesize
// use "%" for non-power of 2 sizes
lvl^.pixel_x := lvl^.camera_x and (TILE_SIZE - 1);
lvl^.pixel_y := lvl^.camera_y and (TILE_SIZE - 1);
end;
// Just a simple map
// A real engine should use a map editor
procedure InitMap(var map: TMapArray);
var
x, y: integer;
begin
for y := 0 to MAP_HEIGHT - 1 do
for x := 0 to MAP_WIDTH - 1 do
map[x, y] := ((y and 15)*16 + (x and 15)) and 255;
end;
var
// This imageset would use our texture packer generated coords so it's kinda
// safe and easy to use
// CRONO_NUM_IMAGES is a value from "uvcoord_crono.h"
crono_images: array [0..CRONO_NUM_IMAGES-1] of glImage;
// This tileset won't make use of our texture packer generated coords
// messy, manual and prone to errors
// BMP is 256x256 and tiles are 16x16 so.. (256/16) * (256 /16) = 16 * 16
tiles_images: array [0..((256 div 16) * (256 div 16)) - 1] of glImage;
// Our level map
// I used shorts since we would be able to reference 65535
// uinique tiles with shorts.
// You should use malloc() or new[] to dimension
// your maps for a real game though.
level_map: TMapArray;
// Our crono guy
crono: TPlayer;
// the level
lvl: TLevel;
crono_textureID: cint;
tiles_textureID: cint;
TextureSize: integer;
frame: integer = 0; // ever present frame counter
key: integer; // for key input
i: integer;
begin
// crono starting positions
crono.x := 16 * 5; // 5th tile
crono.y := 16 * 5;
crono.state := P_RIGHT; // facing right
crono.anim_frame := 0; // starting frame
lvl.width := MAP_WIDTH; // init map dimesions
lvl.height := MAP_HEIGHT;
InitMap(level_map); // load a randomized map (too lazy to make a proper one)
videoSetMode(MODE_5_3D); // favorite mode
consoleDemoInit();
// Initialize GL in 3d mode
glScreen2D();
// set Bank A to texture (128 kb)
vramSetBankA( VRAM_A_TEXTURE );
vramSetBankE(VRAM_E_TEX_PALETTE); // Allocate VRAM bank for all the palettes
// Our texture handle for crono
// I used glLoadSpriteSet since the texture was made
// with my texture packer.
crono_textureID := glLoadSpriteSet(crono_images, // pointer to glImage array
CRONO_NUM_IMAGES, // Texture packer auto-generated #define
@crono_texcoords, // Texture packer auto-generated array
GL_RGB256, // texture type for glTexImage2D() in videoGL.h
TEXTURE_SIZE_256, // sizeX for glTexImage2D() in videoGL.h
TEXTURE_SIZE_128, // sizeY for glTexImage2D() in videoGL.h
GL_TEXTURE_WRAP_S or GL_TEXTURE_WRAP_T or TEXGEN_OFF or GL_TEXTURE_COLOR0_TRANSPARENT, // param for glTexImage2D() in videoGL.h
256, // Length of the palette to use (256 colors)
@cronoPal, // Load our 256 color crono palette
@cronoBitmap // image data generated by GRIT
);
// Our texture handle for our tiles
// I used glLoadTileSet since the texture
// is just a bunch of 16x16 tiles in a 256x256
// tileset so we don't need a texture packer for this.
tiles_textureID := glLoadTileSet(tiles_images, // pointer to glImage array
16, // sprite width
16, // sprite height
256, // bitmap width
256, // bitmap height
GL_RGB256, // texture type for glTexImage2D() in videoGL.h
TEXTURE_SIZE_256, // sizeX for glTexImage2D() in videoGL.h
TEXTURE_SIZE_256, // sizeY for glTexImage2D() in videoGL.h
GL_TEXTURE_WRAP_S or GL_TEXTURE_WRAP_T or TEXGEN_OFF or GL_TEXTURE_COLOR0_TRANSPARENT, // param for glTexImage2D() in videoGL.h
256, // Length of the palette to use (256 colors)
@tilesPal, // Load our 256 color tiles palette
@tilesBitmap // image data generated by GRIT
);
iprintf(#$1b'[1;1HSCROLLING TEST');
iprintf(#$1b'[3;1HArrow Keys to move');
iprintf(#$1b'[6;1HRelminator');
iprintf(#$1b'[7;1HHttp://Rel.Phatcode.Net');
iprintf(#$1b'[9;1HCrono = %i', crono_textureID);
iprintf(#$1b'[10;1HTiles = %i', tiles_textureID);
iprintf(#$1b'[13;1HTiles by unknown');
iprintf(#$1b'[14;1HCrono by Square Enix');
// calculate the amount of
// memory uploaded to VRAM in KB
TextureSize := cronoBitmapLen + tilesBitmapLen;
iprintf(#$1b'[17;1HTotal Texture size= %i kb', TextureSize div 1024);
while true do
begin
// increment frame counter
inc(frame);
crono.is_walking := false; // crono is lazily standing to the right
scanKeys();
key := keysHeld();
// process input and move crono
if (key and KEY_RIGHT) <> 0 then
begin
inc(crono.x);
crono.state := P_RIGHT;
crono.is_walking := true;
end;
if (key and KEY_LEFT)<>0 then
begin
dec(crono.x);
crono.state := P_LEFT;
crono.is_walking := true;
end;
if (key and KEY_UP) <> 0 then
begin
dec(crono.y);
crono.state := P_UP;
crono.is_walking := true;
end;
if (key and KEY_DOWN) <> 0 then
begin
inc(crono.y);
crono.state := P_DOWN;
crono.is_walking := true;
end;
// Update player animations
AnimatePlayer(@crono);
// Update level camera relative to crono's position
CameraUpdate(@lvl, @crono);
glBegin2D();
// Draw our map layer
DrawMap( @lvl, level_map, @tiles_images );
// Process crono
// Left and right share the same frames
// I just flipped the sprite depending on where crono faces.
if (crono.state < P_LEFT) then
glSpriteRotate(crono.x - lvl.camera_x, crono.y - lvl.camera_y, 0,GL_FLIP_NONE , @crono_images[crono.gfx_frame])
else
glSpriteRotate(crono.x - lvl.camera_x, crono.y - lvl.camera_y, 0,GL_FLIP_H , @crono_images[crono.gfx_frame]);
// Draw a translucent gradient box to emulate dialogboxes
// giving it a unique Polygon ID
glPolyFmt(POLY_ALPHA(16) or POLY_CULL_NONE or POLY_ID(1));
glBoxFilledGradient( 0, 150, 255, 191,
RGB15( 31, 0, 0 ),
RGB15( 0, 31, 0 ),
RGB15( 31, 0, 31 ),
RGB15( 0, 31, 31 )
);
//back to opaque mode
// and draw the border of the "dialog box"
glPolyFmt(POLY_ALPHA(31) or POLY_CULL_NONE );
for i := 0 to 4 do
glBox(i, 150 + i, 255 - i , 191 - i,
RGB15( 31-i*5, i*5, 31 - i * 3 )
);
glEnd2D();
glFlush(0);
swiWaitForVBlank();
end;
end.