mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-12-24 03:00:32 +01:00
401 lines
11 KiB
ObjectPascal
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.
|