Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
356 lines
9.5 KiB
C
356 lines
9.5 KiB
C
// ____________________________
|
|
// ██▀▀█▀▀██▀▀▀▀▀▀▀█▀▀█ │ ▄▄▄ ▄▄
|
|
// ██ ▀ █▄ ▀██▄ ▀ ▄█ ▄▀▀ █ │ ▀█▄ ▄▀██ ▄█▄█ ██▀▄ ██ ▄███
|
|
// █ █ █ ▀▀ ▄█ █ █ ▀▄█ █▄ │ ▄▄█▀ ▀▄██ ██ █ ██▀ ▀█▄ ▀█▄▄
|
|
// ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀────────┘ ▀▀
|
|
// Game module sample
|
|
//─────────────────────────────────────────────────────────────────────────────
|
|
|
|
//=============================================================================
|
|
// INCLUDES
|
|
//=============================================================================
|
|
#include "msxgl.h"
|
|
#include "game/state.h"
|
|
#include "game/pawn.h"
|
|
#include "math.h"
|
|
#include "debug.h"
|
|
|
|
//=============================================================================
|
|
// DEFINES
|
|
//=============================================================================
|
|
|
|
// Physics values
|
|
#define FORCE 24
|
|
#define GRAVITY 1
|
|
#define GROUND 192
|
|
|
|
// Library's logo
|
|
#define MSX_GL "\x02\x03\x04\x05"
|
|
|
|
// Function prototypes
|
|
bool State_Initialize();
|
|
bool State_Game();
|
|
bool State_Pause();
|
|
|
|
//=============================================================================
|
|
// READ-ONLY DATA
|
|
//=============================================================================
|
|
|
|
// Font
|
|
#include "font/font_mgl_sample8.h"
|
|
// Sprites data by GrafxKid (https://opengameart.org/content/super-random-sprites)
|
|
#include "content/data_sprt_layer.h"
|
|
#include "content/data_bg.h"
|
|
|
|
// Sign-of-life character animation data
|
|
const c8 g_ChrAnim[] = { '|', '\\', '-', '/' };
|
|
|
|
// Pawn sprite layers
|
|
const Pawn_Sprite g_SpriteLayers[] =
|
|
{// X Y Pattern Color Option
|
|
{ 0, 0, 0, COLOR_BLACK, PAWN_SPRITE_BLEND }, // Black sprite alternated each frame
|
|
{ 0, 0, 4, COLOR_WHITE, 0 },
|
|
{ 0, 0, 8, COLOR_LIGHT_RED, 0 },
|
|
};
|
|
|
|
// Idle animation frames
|
|
const Pawn_Frame g_FramesIdle[] =
|
|
{ // Pattern Time Function
|
|
{ 6*16, 48, NULL },
|
|
{ 7*16, 24, NULL },
|
|
};
|
|
|
|
// Move animation frames
|
|
const Pawn_Frame g_FramesMove[] =
|
|
{
|
|
{ 0*16, 4, NULL },
|
|
{ 1*16, 4, NULL },
|
|
{ 2*16, 4, NULL },
|
|
{ 3*16, 4, NULL },
|
|
{ 4*16, 4, NULL },
|
|
{ 5*16, 4, NULL },
|
|
};
|
|
|
|
// Jump animation frames
|
|
const Pawn_Frame g_FramesJump[] =
|
|
{
|
|
{ 3*16, 4, NULL },
|
|
{ 8*16, 4, NULL },
|
|
};
|
|
|
|
// Fall animation frames
|
|
const Pawn_Frame g_FramesFall[] =
|
|
{
|
|
{ 9*16, 4, NULL },
|
|
};
|
|
|
|
// Actions id
|
|
enum ANIM_ACTION_ID
|
|
{
|
|
ACTION_IDLE = 0,
|
|
ACTION_MOVE,
|
|
ACTION_JUMP,
|
|
ACTION_FALL,
|
|
};
|
|
|
|
// List of all player actions
|
|
const Pawn_Action g_AnimActions[] =
|
|
{ // Frames Number Loop? Interrupt?
|
|
{ g_FramesIdle, numberof(g_FramesIdle), TRUE, TRUE },
|
|
{ g_FramesMove, numberof(g_FramesMove), TRUE, TRUE },
|
|
{ g_FramesJump, numberof(g_FramesJump), TRUE, TRUE },
|
|
{ g_FramesFall, numberof(g_FramesFall), TRUE, TRUE },
|
|
};
|
|
|
|
//=============================================================================
|
|
// MEMORY DATA
|
|
//=============================================================================
|
|
|
|
Pawn g_PlayerPawn; // Player's pawn data structure
|
|
bool g_bFlicker = TRUE; // Activate sprite colorflickering
|
|
bool g_bMoving = FALSE; // Is player moving?
|
|
bool g_bJumping = FALSE; // Is player jumping?
|
|
i8 g_VelocityY; // Current player velocity
|
|
u8 g_PrevRow8 = 0xFF; // Previous keyboard 8th row value
|
|
i8 g_DX = 0; // Current X movement
|
|
i8 g_DY = 0; // Current Y movement
|
|
u8 g_LastEvent = 0; // Last triggered event
|
|
u16 g_Level = 0; // Current map level
|
|
bool g_bEnable = TRUE; //
|
|
|
|
//=============================================================================
|
|
// FUNCTIONS
|
|
//=============================================================================
|
|
|
|
// Physics callback
|
|
void PhysicsEvent(u8 event, u8 tile)
|
|
{
|
|
tile;
|
|
switch (event)
|
|
{
|
|
case PAWN_PHYSICS_BORDER_DOWN:
|
|
case PAWN_PHYSICS_BORDER_RIGHT:
|
|
g_LastEvent = event;
|
|
break;
|
|
|
|
case PAWN_PHYSICS_COL_DOWN: // Handle downward collisions
|
|
g_bJumping = FALSE;
|
|
break;
|
|
|
|
case PAWN_PHYSICS_COL_UP: // Handle upward collisions
|
|
g_VelocityY = 0;
|
|
break;
|
|
|
|
case PAWN_PHYSICS_FALL: // Handle falling
|
|
if (!g_bJumping)
|
|
{
|
|
g_bJumping = TRUE;
|
|
g_VelocityY = 0;
|
|
}
|
|
break;
|
|
};
|
|
}
|
|
|
|
// Collision callback
|
|
bool PhysicsCollision(u8 tile)
|
|
{
|
|
return (tile < 8);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Draw the current level
|
|
void DrawLevel()
|
|
{
|
|
Print_SetPosition(25, 0);
|
|
Print_DrawInt(g_Level);
|
|
|
|
// Background
|
|
loop (i, 24-2)
|
|
VDP_FillVRAM((i == 12) ? 9 : (i < 12) ? 16 : 8, g_ScreenLayoutLow + (i+2) * 32, 0, 32);
|
|
// Ground
|
|
Math_SetRandomSeed16(g_Level);
|
|
loop (i, 8)
|
|
{
|
|
if (i == 7)
|
|
Math_SetRandomSeed16(g_Level + 1);
|
|
u8 y = Math_GetRandom16() & 0x07 ;
|
|
loop (j, y)
|
|
{
|
|
VDP_FillVRAM((j == (y - 1)) ? 1 : 3, g_ScreenLayoutLow + ((23 - j) * 32) + (i * 4), 0, 4);
|
|
}
|
|
}
|
|
// Plateforms
|
|
loop (i, 12)
|
|
{
|
|
u8 rnd = Math_GetRandom16();
|
|
VDP_FillVRAM(1, g_ScreenLayoutLow + ((rnd & 0x0F) + 2) * 32 + (rnd >> 3), 0, 2);
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Initialize the game
|
|
bool State_Initialize()
|
|
{
|
|
// Initialize display
|
|
VDP_EnableDisplay(FALSE);
|
|
VDP_SetColor(COLOR_BLACK);
|
|
|
|
// Initialize pattern
|
|
VDP_FillVRAM(0x00, g_ScreenPatternLow, 0, 256*8); // Clear pattern
|
|
VDP_WriteVRAM(g_DataBackground, g_ScreenPatternLow, 0, 24*8);
|
|
Print_SetTextFont(g_Font_MGL_Sample8, 32);
|
|
Print_SetColor(0xF, 0x1);
|
|
|
|
// Initialize color
|
|
VDP_FillVRAM(0xF0, g_ScreenColorLow, 0, 32); // Clear color
|
|
VDP_Poke_16K(0xF7, g_ScreenColorLow + 0);
|
|
VDP_Poke_16K(0x54, g_ScreenColorLow + 1);
|
|
VDP_Poke_16K(0xF5, g_ScreenColorLow + 2);
|
|
VDP_Poke_16K(0x99, g_ScreenColorLow + 3);
|
|
|
|
// Initialize sprite
|
|
VDP_SetSpriteFlag(VDP_SPRITE_SIZE_16);
|
|
VDP_LoadSpritePattern(g_DataSprtLayer, 0, 13*4*4);
|
|
VDP_DisableSpritesFrom(3);
|
|
|
|
// Init player pawn
|
|
Pawn_Initialize(&g_PlayerPawn, g_SpriteLayers, numberof(g_SpriteLayers), 0, g_AnimActions);
|
|
Pawn_SetPosition(&g_PlayerPawn, 16, 16);
|
|
Pawn_SetColorBlend(&g_PlayerPawn, g_bFlicker);
|
|
Pawn_InitializePhysics(&g_PlayerPawn, PhysicsEvent, PhysicsCollision, 16, 16);
|
|
|
|
// Initialize text
|
|
Print_SetPosition(0, 0);
|
|
Print_DrawCharX(' ', 32);
|
|
Print_SetPosition(0, 0);
|
|
Print_DrawText(MSX_GL " GAME SAMPLE Lvl");
|
|
Print_DrawLineH(0, 1, 32);
|
|
|
|
// Initialize layout
|
|
DrawLevel();
|
|
|
|
VDP_EnableDisplay(TRUE);
|
|
|
|
Game_SetState(State_Game);
|
|
return FALSE; // Frame finished
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Update the gameplay
|
|
bool State_Game()
|
|
{
|
|
// VDP_SetColor(COLOR_DARK_GREEN);
|
|
// Update player animation & physics
|
|
u8 act = ACTION_IDLE;
|
|
if (g_bJumping && (g_VelocityY >= 0))
|
|
act = ACTION_JUMP;
|
|
else if (g_bJumping)
|
|
act = ACTION_FALL;
|
|
else if (g_bMoving)
|
|
act = ACTION_MOVE;
|
|
Pawn_SetAction(&g_PlayerPawn, act);
|
|
Pawn_SetMovement(&g_PlayerPawn, g_DX, g_DY);
|
|
// VDP_SetColor(COLOR_DARK_BLUE);
|
|
Pawn_Update(&g_PlayerPawn);
|
|
// VDP_SetColor(COLOR_DARK_RED);
|
|
Pawn_Draw(&g_PlayerPawn);
|
|
// VDP_SetColor(COLOR_BLACK);
|
|
|
|
// Character animation
|
|
Print_SetPosition(31, 0);
|
|
Print_DrawChar(g_ChrAnim[g_PlayerPawn.Counter & 0x03]);
|
|
|
|
// Background horizon blink
|
|
if (g_bFlicker)
|
|
VDP_FillVRAM(g_PlayerPawn.Counter & 1 ? 9 : 10, g_ScreenLayoutLow + (12+2) * 32, 0, 32);
|
|
|
|
// Handle collision events
|
|
switch (g_LastEvent)
|
|
{
|
|
case PAWN_PHYSICS_BORDER_DOWN:
|
|
// @todo: handle game over...
|
|
break;
|
|
case PAWN_PHYSICS_BORDER_RIGHT:
|
|
g_LastEvent = 0;
|
|
Pawn_SetPosition(&g_PlayerPawn, 0 + 4, g_PlayerPawn.PositionY);
|
|
g_Level++;
|
|
DrawLevel();
|
|
break;
|
|
}
|
|
|
|
// Update movement
|
|
g_DX = 0;
|
|
g_DY = 0;
|
|
u8 row8 = Keyboard_Read(8);
|
|
if (IS_KEY_PRESSED(row8, KEY_RIGHT))
|
|
{
|
|
g_DX++;
|
|
g_bMoving = TRUE;
|
|
}
|
|
else if (IS_KEY_PRESSED(row8, KEY_LEFT))
|
|
{
|
|
g_DX--;
|
|
g_bMoving = TRUE;
|
|
}
|
|
else
|
|
g_bMoving = FALSE;
|
|
|
|
if (g_bJumping)
|
|
{
|
|
g_DY -= g_VelocityY / 4;
|
|
|
|
g_VelocityY -= GRAVITY;
|
|
if (g_VelocityY < -FORCE)
|
|
g_VelocityY = -FORCE;
|
|
|
|
}
|
|
else if (IS_KEY_PRESSED(row8, KEY_SPACE) || IS_KEY_PRESSED(row8, KEY_UP))
|
|
{
|
|
g_bJumping = TRUE;
|
|
g_VelocityY = FORCE;
|
|
}
|
|
|
|
if (IS_KEY_PUSHED(row8, g_PrevRow8, KEY_HOME))
|
|
{
|
|
g_bFlicker = 1 - g_bFlicker;
|
|
Pawn_SetColorBlend(&g_PlayerPawn, g_bFlicker);
|
|
}
|
|
|
|
if (IS_KEY_PUSHED(row8, g_PrevRow8, KEY_DEL))
|
|
{
|
|
g_bEnable = !g_bEnable;
|
|
Pawn_SetEnable(&g_PlayerPawn, g_bEnable);
|
|
}
|
|
|
|
g_PrevRow8 = row8;
|
|
|
|
if (Keyboard_IsKeyPressed(KEY_ESC))
|
|
Game_Exit();
|
|
|
|
|
|
return TRUE; // Frame finished
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Pause the game
|
|
bool State_Pause()
|
|
{
|
|
return TRUE; // Frame finished
|
|
}
|
|
|
|
//=============================================================================
|
|
// MAIN LOOP
|
|
//=============================================================================
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Programme entry point
|
|
void main()
|
|
{
|
|
BIOS_SetKeyClick(FALSE);
|
|
|
|
Game_SetState(State_Initialize);
|
|
Game_Start(VDP_MODE_GRAPHIC1, FALSE);
|
|
|
|
BIOS_Exit(0);
|
|
} |