Skip to content

Instantly share code, notes, and snippets.

@etscrivner
Created February 1, 2020 19:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save etscrivner/23aad5e2457a005f2f1fe495313d0e3d to your computer and use it in GitHub Desktop.
Save etscrivner/23aad5e2457a005f2f1fe495313d0e3d to your computer and use it in GitHub Desktop.
#include <SDL.h>
#include "language_layer.h"
#include "memory_arena.h"
#define STB_TRUETYPE_IMPLEMENTATION
#include "stb_truetype.h"
#define WINDOW_WIDTH 1920
#define WINDOW_HEIGHT 1080
#define TOP_BAR_SIZE 100
#define TILES_PER_WINDOW_WIDTH 20
#define FONT_FILE_NAME "manaspc.ttf"
// Map structure
#define MAP_TILE_TYPE_ground '_'
#define MAP_TILE_TYPE_wall 'X'
#define MAP_TILE_TYPE_water '~'
// Text rendering
#define RENDER_TEXT_X_CENTERED (1 << 0)
#define RENDER_TEXT_Y_CENTERED (1 << 1)
const char* Dialog[] = {
"You awaken in a dungeon\n\nThe air is humid...\n\nNearby you hear something shuffle across\nthe cold floor",
0
};
typedef struct font_t {
u8* Data;
stbtt_fontinfo Font;
i32 Ascent;
i32 Descent;
i32 LineGap;
} font;
typedef enum direction_t {
DIRECTION_right,
DIRECTION_up,
DIRECTION_left,
DIRECTION_down,
DIRECTION_MAX
} direction;
typedef struct map_t {
v2i Dim;
char Tiles[20][20];
} map;
// Anything with a key press:
// Up => Down => Repeat => Up
// The transition from Up => Down adds 1 to HalfTransitionCount
// The transition from Down => Up adds 1 to HalfTransitionCount
//
// State | HalfTransitionCount | Meaning
// ==================================================
// Up | 0 | Still unpressed
// Up | 1 | Was pressed
// Down | 0 | Still pressed
// Down | 1 | Just pressed
typedef struct key_state_t {
b32 EndedDown;
u32 HalfTransitionCount;
} key_state;
typedef struct enemy_t {
i32 HP;
i32 MaxHP;
v2i Pos;
v4 Rect;
f32 MoveTimeSecs;
f32 MoveTimer;
} enemy;
typedef struct app_t {
v2 Window;
b32 Running;
memory_arena PermArena;
memory_arena TempArena;
struct {
v2 Pos;
v2 Rem;
v4 Color;
v4 WeaponColor;
v2 WeaponDim;
direction Dir;
} Player;
u32 NumEnemies;
enemy Enemies[10];
v2i TileSize;
map Map;
u32 NumCollisionRects;
v4 CollisionRects[150];
font Font;
f32 FadeInSecs;
f32 TotalFadeInSecs;
u32 DialogIndex;
} app;
typedef struct input_t {
f32 DeltaTimeSecs;
union {
struct {
key_state MoveLeft;
key_state MoveRight;
key_state MoveUp;
key_state MoveDown;
key_state ActionDown;
key_state ActionRight;
key_state Shift;
};
key_state Keys[6];
};
} input;
internal bool FontLoad(const char* FileName, memory_arena* Arena, font* Result) {
FILE* TTFFile = fopen(FileName, "r");
if (!TTFFile) {
return(false);
}
fseek(TTFFile, 0, SEEK_END);
long FileSize = ftell(TTFFile);
fseek(TTFFile, 0, SEEK_SET);
Result->Data = (u8*)ArenaAlloc(Arena, FileSize);
fread(Result->Data, 1, FileSize, TTFFile);
fclose(TTFFile);
stbtt_InitFont(&Result->Font, Result->Data, stbtt_GetFontOffsetForIndex(Result->Data, 0));
stbtt_GetFontVMetrics(&Result->Font, &Result->Ascent, &Result->Descent, &Result->LineGap);
return(true);
}
internal f32 FontLineHeight(app* App, f32 FontSizePixels) {
// Calculate line height
float Scale = stbtt_ScaleForPixelHeight(&App->Font.Font, FontSizePixels);
return(Scale * App->Font.Ascent);
}
internal f32 FontTextWidthPixels(app* App, f32 FontSizePixels, const char* Text, u32 StartCh, u32 EndCh) {
char* Ch = (char*)Text + StartCh;
float XPos = 0;
int Advance, LSB;
float Scale = stbtt_ScaleForPixelHeight(&App->Font.Font, FontSizePixels);
while (*Ch && StartCh < EndCh) {
stbtt_GetCodepointHMetrics(&App->Font.Font, *Ch, &Advance, &LSB);
XPos += (Advance * Scale);
if (StartCh + 1 < EndCh) {
XPos += Scale * stbtt_GetCodepointKernAdvance(&App->Font.Font, *Ch, *(Ch+1));
}
++StartCh;
++Ch;
}
return(XPos);
}
internal f32 FontTextWidthPixels(app* App, f32 FontSizePixels, const char* Text) {
char* Ch = (char*)Text;
float XPos = 0;
int Advance, LSB;
float Scale = stbtt_ScaleForPixelHeight(&App->Font.Font, FontSizePixels);
while (*Ch) {
stbtt_GetCodepointHMetrics(&App->Font.Font, *Ch, &Advance, &LSB);
XPos += (Advance * Scale);
if (*(Ch + 1)) {
XPos += Scale * stbtt_GetCodepointKernAdvance(&App->Font.Font, *Ch, *(Ch+1));
}
Ch++;
}
return(XPos);
}
inline direction RandomDirection() {
switch (rand() % 4)
{
case 0:
return(DIRECTION_right);
case 1:
return(DIRECTION_left);
case 2:
return(DIRECTION_up);
case 3:
return(DIRECTION_down);
default:
return(DIRECTION_right);
}
}
inline b32 IsPressed(key_state State)
{
return (State.EndedDown);
}
inline b32 WasPressed(key_state State)
{
return (!State.EndedDown && State.HalfTransitionCount > 0);
}
internal void LinuxProcessKeyDown(key_state* State, b32 IsDown)
{
Assert(IsDown != State->EndedDown);
State->EndedDown = IsDown;
++State->HalfTransitionCount;
}
void SetDrawColor(SDL_Renderer* Renderer, v4 Color)
{
SDL_SetRenderDrawColor(Renderer, 255 * Color.R, 255 * Color.G, 255 * Color.B, 255 * Color.A);
}
void DrawTextRevised(SDL_Renderer* Renderer, app* App, const char* Text, float FontSizePixels, v2i Pos) {
// Iterate through the text and find all of the newlines.
u32 NewlineCount = 0;
u32 NewlinePos[100]; // NOTE(eric): No more than 100 newlines
for (u32 I = 0; I < strlen(Text); ++I)
{
if (Text[I] == '\n') {
NewlinePos[NewlineCount++] = I;
}
}
// Add one final virtual newline for the end of the text.
NewlinePos[NewlineCount++] = strlen(Text);
// Set up rendering constants
f32 Scale = stbtt_ScaleForPixelHeight(&App->Font.Font, FontSizePixels);
int BitmapHeight = FontSizePixels - (Scale * App->Font.Descent);
int Ascent = (int)round(Scale * App->Font.Ascent);
u32 StartPos = 0;
for (u32 I = 0; I < NewlineCount; ++I)
{
int BitmapWidth = (int)FontTextWidthPixels(App, FontSizePixels, Text, StartPos, NewlinePos[I]);
u8* Bitmap = (u8*)calloc(1, BitmapWidth * BitmapHeight);
// Render characters into bitmap
int FinalWidth = 0;
for (u32 Pos = StartPos; Pos < NewlinePos[I]; ++Pos) {
int CX1, CY1, CX2, CY2;
stbtt_GetCodepointBitmapBox(&App->Font.Font, Text[Pos], Scale, Scale, &CX1, &CY1, &CX2, &CY2);
int ChY = Ascent + CY1;
int ByteOffset = FinalWidth + (ChY * BitmapWidth);
stbtt_MakeCodepointBitmap(&App->Font.Font, Bitmap + ByteOffset, CX2 - CX1, CY2 - CY1, BitmapWidth, Scale, Scale, Text[Pos]);
int XOffs;
stbtt_GetCodepointHMetrics(&App->Font.Font, Text[Pos], &XOffs, 0);
FinalWidth += Scale * XOffs;
int Kern;
Kern = stbtt_GetCodepointKernAdvance(&App->Font.Font, Text[Pos], Text[Pos + 1]);
FinalWidth += Scale * Kern;
}
// Copy bitmap into SDL-appropriate bmp
u8* Pixels = (u8*)calloc(1, sizeof(u32) * BitmapWidth * BitmapHeight);
u8* Src = Bitmap;
u8* DstRow = Pixels;
for (i32 Y = 0; Y < BitmapHeight; ++Y) {
u32* Dest = (u32*)DstRow;
for (i32 X = 0; X < BitmapWidth; ++X) {
u8 Alpha = *Src++;
*Dest++ = ((Alpha << 24) | (Alpha << 16) | (Alpha << 8) | Alpha);
}
DstRow += (sizeof(u32) * FinalWidth);
}
// Copy to SDL texture and render
SDL_Texture* FontTexture = SDL_CreateTexture(
Renderer,
SDL_PIXELFORMAT_RGBA32,
SDL_TEXTUREACCESS_STREAMING,
BitmapWidth,
BitmapHeight
);
SDL_UpdateTexture(FontTexture, NULL, Pixels, 4 * FinalWidth);
SDL_SetTextureBlendMode(FontTexture, SDL_BLENDMODE_BLEND);
SDL_Rect SrcR = { .x = 0, .y = 0, .w = FinalWidth, .h = BitmapHeight };
SDL_Rect DstR = { .x = Pos.X, .y = Pos.Y, .w = FinalWidth, .h = BitmapHeight };
SDL_RenderCopy(Renderer, FontTexture, &SrcR, &DstR);
SDL_DestroyTexture(FontTexture);
free(Pixels);
free(Bitmap);
StartPos = NewlinePos[I] + 1;
Pos.Y += BitmapHeight;
}
}
// Largely based on this code:
// https://github.com/justinmeiners/stb-truetype-example/blob/master/main.c
void DrawText(SDL_Renderer* Renderer, app* App, const char* Text, float FontSizePixels, v2i Pos) {
float scale = stbtt_ScaleForPixelHeight(&App->Font.Font, FontSizePixels);
// NOTE(eric): Total height in pixels will be the selected pixel font size
// plus additional room for chars that descend below the line.
int bitmap_height = FontSizePixels - (scale * App->Font.Descent);
int bitmap_width = FontTextWidthPixels(App, FontSizePixels, Text);
uint8_t* bitmap = (unsigned char*)malloc(bitmap_height * bitmap_width);
memset(bitmap, 0, bitmap_height * bitmap_width);
int ascent = (int)round(scale * App->Font.Ascent);
int final_width = 0;
for (char* at = (char*)Text; *at; ++at) {
int c_x1, c_y1, c_x2, c_y2;
stbtt_GetCodepointBitmapBox(&App->Font.Font, *at, scale, scale, &c_x1, &c_y1, &c_x2, &c_y2);
int ch_y = ascent + c_y1;
int byte_offset = final_width + (ch_y * bitmap_width);
stbtt_MakeCodepointBitmap(&App->Font.Font, bitmap + byte_offset, c_x2 - c_x1, c_y2 - c_y1, bitmap_width, scale, scale, *at);
int ax;
stbtt_GetCodepointHMetrics(&App->Font.Font, *at, &ax, 0);
final_width += scale * ax;
int kern;
kern = stbtt_GetCodepointKernAdvance(&App->Font.Font, *at, *(at + 1));
final_width += kern * scale;
}
uint8_t pixels[sizeof(uint32_t) * bitmap_width * bitmap_height];
uint8_t* src = bitmap;
uint8_t* dst_row = pixels;
for (int by = 0; by < bitmap_height; ++by) {
uint32_t* dest = (uint32_t*)dst_row;
for (int bx = 0; bx < bitmap_width; ++bx) {
u8 alpha = *src++;
*dest++ = ((alpha << 24) | (alpha << 16) | (alpha << 8) | alpha);
}
dst_row += (sizeof(uint32_t) * final_width);
}
SDL_Texture* font_texture = SDL_CreateTexture(
Renderer,
SDL_PIXELFORMAT_RGBA32,
SDL_TEXTUREACCESS_STREAMING,
bitmap_width,
bitmap_height
);
SDL_UpdateTexture(font_texture, NULL, pixels, 4 * final_width);
SDL_SetTextureBlendMode(font_texture, SDL_BLENDMODE_BLEND);
SDL_Rect srcr = { .x = 0, .y = 0, .w = final_width, .h = bitmap_height };
SDL_Rect dstr = { .x = Pos.X, .y = Pos.Y, .w = final_width, .h = bitmap_height };
SDL_RenderCopy(Renderer, font_texture, &srcr, &dstr);
SDL_DestroyTexture(font_texture);
free(bitmap);
}
void DrawText(SDL_Renderer* Renderer, app* App, const char* Text, unsigned int Flags, float FontSizePixels, v2i Pos) {
float LineHeight = FontLineHeight(App, FontSizePixels);
if (Flags & RENDER_TEXT_Y_CENTERED) {
Pos.Y += LineHeight / 2;
}
if (Flags & RENDER_TEXT_X_CENTERED) {
float TextWidthPixels = FontTextWidthPixels(App, FontSizePixels, Text);
Pos.X -= TextWidthPixels / 2;
}
DrawText(Renderer, App, Text, FontSizePixels, Pos);
}
void DrawPlayer(SDL_Renderer* Renderer, app* App)
{
SDL_Rect PlayerRect = {};
PlayerRect.x = App->Window.Width/2 - App->TileSize.Width/2;//App->Player.Pos.X;
PlayerRect.y = App->Window.Height/2 - App->TileSize.Height/2;//App->Player.Pos.Y;
PlayerRect.w = App->TileSize.Width;
PlayerRect.h = App->TileSize.Height;
// Player weapon
SetDrawColor(Renderer, App->Player.WeaponColor);
SDL_Rect WeaponRect = {};
WeaponRect.w = App->Player.WeaponDim.Width;
WeaponRect.h = App->Player.WeaponDim.Height;
switch (App->Player.Dir)
{
case DIRECTION_right:
{
WeaponRect.x = PlayerRect.x + App->TileSize.Width / 2;
WeaponRect.y = PlayerRect.y + App->TileSize.Height / 2 - App->Player.WeaponDim.Height / 2;
}
break;
case DIRECTION_left:
{
WeaponRect.x = PlayerRect.x - App->TileSize.Width / 2;
WeaponRect.y = PlayerRect.y + App->TileSize.Height / 2 - App->Player.WeaponDim.Height / 2;
}
break;
case DIRECTION_up:
{
WeaponRect.h = App->Player.WeaponDim.Width;
WeaponRect.w = App->Player.WeaponDim.Height;
WeaponRect.x = PlayerRect.x + App->TileSize.Width / 2 - App->Player.WeaponDim.Height / 2;
WeaponRect.y = PlayerRect.y - App->TileSize.Height / 2;
}
break;
case DIRECTION_down:
{
WeaponRect.h = App->Player.WeaponDim.Width;
WeaponRect.w = App->Player.WeaponDim.Height;
WeaponRect.x = PlayerRect.x + App->TileSize.Width / 2 - App->Player.WeaponDim.Height / 2;
WeaponRect.y = PlayerRect.y + App->TileSize.Height / 2;
}
break;
default: break;
}
SDL_RenderFillRect(
Renderer,
&WeaponRect
);
// Player body
SetDrawColor(Renderer, App->Player.Color);
SDL_RenderFillRect(
Renderer,
&PlayerRect
);
}
internal void PrintRect(v4* Rect)
{
printf("(%f, %f, %f, %f)\n", Rect->X, Rect->Y, Rect->Width, Rect->Height);
}
void DrawMap(SDL_Renderer* Renderer, app* App)
{
SDL_Rect TileRect = {};
TileRect.w = App->TileSize.Width;
TileRect.h = App->TileSize.Height;
for (u32 Y = 0; Y < App->Map.Dim.H; ++Y)
{
TileRect.y = Y * App->TileSize.H - App->Player.Pos.Y;
for (u32 X = 0; X < App->Map.Dim.W; ++X)
{
TileRect.x = X * App->TileSize.W - App->Player.Pos.X;
switch (App->Map.Tiles[Y][X])
{
case MAP_TILE_TYPE_wall:
SetDrawColor(Renderer, V4(0.3, 0.3, 0.3, 1));
break;
case MAP_TILE_TYPE_ground:
SetDrawColor(Renderer, V4(0.6, 0.6, 0.6, 1));
break;
case MAP_TILE_TYPE_water:
SetDrawColor(Renderer, V4(0, 0, 1, 1));
break;
default:
SetDrawColor(Renderer, V4(1.0, 0.8, 0.9, 1));
break;
}
SDL_RenderFillRect(
Renderer,
&TileRect
);
}
}
}
void DrawRect(SDL_Renderer* Renderer, v4 InRect, v4 Color = V4(1, 1, 1, 1))
{
SDL_Rect Rect = {};
Rect.x = InRect.X;
Rect.y = InRect.Y;
Rect.w = InRect.Width;
Rect.h = InRect.Height;
SetDrawColor(Renderer, Color);
#if 0
SDL_RenderDrawLine(
Renderer,
Rect.x,
Rect.y,
Rect.x + Rect.w,
Rect.y
);
SDL_RenderDrawLine(
Renderer,
Rect.x + Rect.w,
Rect.y,
Rect.x + Rect.w,
Rect.y + Rect.h
);
SDL_RenderDrawLine(
Renderer,
Rect.x + Rect.w,
Rect.y + Rect.h,
Rect.x,
Rect.y + Rect.h
);
SDL_RenderDrawLine(
Renderer,
Rect.x,
Rect.y + Rect.h,
Rect.x,
Rect.y
);
#else
SDL_RenderFillRect(
Renderer,
&Rect
);
#endif
}
void DrawEnemies(SDL_Renderer* Renderer, app* App)
{
for (u32 I = 0; I < App->NumEnemies; ++I)
{
enemy* Enemy = App->Enemies + I;
SDL_Rect EnemyRect = {};
EnemyRect.w = App->TileSize.W;
EnemyRect.h = App->TileSize.H;
f32 FilledPercent = Enemy->HP / (f32)Enemy->MaxHP;
EnemyRect.x = Enemy->Pos.X * EnemyRect.w - App->Player.Pos.X;
EnemyRect.y = Enemy->Pos.Y * EnemyRect.h - App->Player.Pos.Y;
// Enemy health background
SetDrawColor(Renderer, V4(0.3, 0.0, 0.0, 1));
SDL_Rect BGRect = EnemyRect;
SDL_RenderFillRect(
Renderer,
&BGRect
);
// Enemy health
SetDrawColor(Renderer, V4(1, 0, 0, 1));
EnemyRect.y += (1.0f - FilledPercent) * EnemyRect.h;
EnemyRect.h -= (1.0f - FilledPercent) * EnemyRect.h;
SDL_RenderFillRect(
Renderer,
&EnemyRect
);
}
}
void DrawTopBar(SDL_Renderer* Renderer, app* App)
{
SDL_Rect BarRect = {};
BarRect.w = App->Window.Width;
BarRect.h = TOP_BAR_SIZE;
BarRect.x = 0;
BarRect.y = 0;
SetDrawColor(Renderer, V4(0, 0, 0, 0.5));
SDL_RenderFillRect(
Renderer,
&BarRect
);
char* Text = "abcdefghijklmnopqrstuvwxyz\nHello";//"HPj[()]";
DrawTextRevised(Renderer, App, Text, 30, V2I(0, 0));
SetDrawColor(Renderer, V4(1, 1, 1, 1));
SDL_RenderDrawLine(Renderer, 0, TOP_BAR_SIZE, App->Window.Width, TOP_BAR_SIZE);
}
i32 Sign(f32 Value)
{
return (Value > 0) ? 1 : -1;
}
b32 HasCollision(app* App, v4 Rect)
{
for (u32 I = 0; I < App->NumCollisionRects; ++I)
{
if (RectRectIntersect(Rect, App->CollisionRects[I]))
{
#if 0
printf(
"%f,%f,%f,%f - %f,%f,%f,%f\n",
Rect.X, Rect.Y, Rect.Z, Rect.W,
App->CollisionRects[I].X, App->CollisionRects[I].Y, App->CollisionRects[I].Z, App->CollisionRects[I].W
);
#endif
return(true);
}
}
for (u32 I = 0; I < App->NumEnemies; ++I)
{
if (RectRectIntersect(Rect, App->Enemies[I].Rect))
{
return(true);
}
}
return(false);
}
internal enemy* WeaponHitEnemy(app* App)
{
v4 WeaponRect = {};
switch (App->Player.Dir)
{
case DIRECTION_right:
{
WeaponRect = V4(
App->Window.Width/2 - App->TileSize.Width / 2 + App->Player.Pos.X + App->TileSize.W,
App->Window.Height/2 + App->Player.Pos.Y - App->Player.WeaponDim.Height / 2,
App->Player.WeaponDim.Width,
App->Player.WeaponDim.Height
);
}
break;
case DIRECTION_left:
{
WeaponRect = V4(
App->Window.Width/2 - App->TileSize.Width / 2 + App->Player.Pos.X - App->Player.WeaponDim.Width,
App->Window.Height/2 + App->Player.Pos.Y - App->Player.WeaponDim.Height / 2,
App->Player.WeaponDim.Width,
App->Player.WeaponDim.Height
);
}
break;
case DIRECTION_up:
{
WeaponRect = V4(
App->Window.Width/2 - App->TileSize.Width / 2 + App->Player.Pos.X + App->TileSize.W/2 - App->Player.WeaponDim.Height/2,
App->Window.Height/2 - App->TileSize.Height / 2 + App->Player.Pos.Y - App->Player.WeaponDim.Width,
App->Player.WeaponDim.Height,
App->Player.WeaponDim.Width
);
}
break;
case DIRECTION_down:
{
WeaponRect = V4(
App->Window.Width/2 - App->TileSize.Width / 2 + App->Player.Pos.X + App->TileSize.W/2 - App->Player.WeaponDim.Height/2,
App->Window.Height/2 + App->TileSize.Height / 2 + App->Player.Pos.Y,
App->Player.WeaponDim.Height,
App->Player.WeaponDim.Width
);
}
break;
default: break;
}
// NOTE(eric): Draw ephemeral debug rects for weapon and character. Will need
// to pass renderer to this method if you want to use.
#if 0
DrawRect(Renderer, WeaponRect, V4(0, 1, 1, 1));
DrawRect(
Renderer,
V4(
App->Window.Width/2 - App->TileSize.Width / 2 + App->Player.Pos.X,
App->Window.Height/2 - App->TileSize.Height/2 + App->Player.Pos.Y,
App->TileSize.Width,
App->TileSize.Height
),
V4(1, 0, 0, 1)
);
#endif
for (u32 I = 0; I < App->NumEnemies; ++I)
{
enemy* Enemy = App->Enemies + I;
if (Enemy->HP <= 0)
{
continue;
}
#if 0
DrawRect(Renderer, Enemy->Rect, V4(0, 1, 1, 1));
#endif
if (RectRectIntersect(WeaponRect, Enemy->Rect))
{
return(Enemy);
}
}
return(0);
}
void MovePlayerX(app* App, f32 DeltaX)
{
App->Player.Rem.X += DeltaX;
i32 Move = (i32)round(App->Player.Rem.X);
if (Move != 0)
{
App->Player.Rem.X -= Move;
int Sgn = Sign(Move);
v4 PlayerRect = V4(
App->Window.Width/2 - App->TileSize.W/2,
App->Window.Height/2 - App->TileSize.H/2 + 3*App->TileSize.H/4,
App->TileSize.W,
App->TileSize.H/4
);
while (Move != 0)
{
v4 ScreenSpaceAdjust = V4(
App->Player.Pos.X + Sgn,
App->Player.Pos.Y,
0, 0
);
if (!HasCollision(App, PlayerRect + ScreenSpaceAdjust))
{
App->Player.Pos.X += Sgn;
Move -= Sgn;
}
else
{
break;
}
}
}
}
void MovePlayerY(app* App, f32 DeltaY)
{
App->Player.Rem.Y += DeltaY;
i32 Move = (i32)round(App->Player.Rem.Y);
if (Move != 0)
{
App->Player.Rem.Y -= Move;
int Sgn = Sign(Move);
while (Move != 0)
{
v4 PlayerRect = V4(
App->Window.Width/2 - App->TileSize.Width/2,
App->Window.Height/2 - App->TileSize.Height/2,
App->TileSize.W,
App->TileSize.H/4
);
v4 ScreenSpaceAdjust = V4(
App->Player.Pos.X,
App->Player.Pos.Y + 3*App->TileSize.H/4 + Sgn,
0, 0
);
if (!HasCollision(App, PlayerRect + ScreenSpaceAdjust))
{
App->Player.Pos.Y += Sgn;
Move -= Sgn;
}
else
{
break;
}
}
}
}
inline direction DirectionLeft(direction CurrentDir)
{
switch (CurrentDir)
{
case DIRECTION_right:
return(DIRECTION_up);
break;
case DIRECTION_up:
return(DIRECTION_left);
break;
case DIRECTION_left:
return(DIRECTION_down);
break;
case DIRECTION_down:
return(DIRECTION_right);
break;
default: break;
}
return(DIRECTION_MAX);
}
inline direction DirectionRight(direction CurrentDir)
{
switch (CurrentDir)
{
case DIRECTION_right:
return(DIRECTION_down);
break;
case DIRECTION_up:
return(DIRECTION_right);
break;
case DIRECTION_left:
return(DIRECTION_up);
break;
case DIRECTION_down:
return(DIRECTION_left);
break;
default: break;
}
return(DIRECTION_MAX);
}
void TickEnemies(app* App, input* Input)
{
for (u32 I = 0; I < App->NumEnemies; ++I)
{
enemy* Enemy = App->Enemies + I;
if (Enemy->HP <= 0)
{
continue;
}
Enemy->MoveTimer -= Input->DeltaTimeSecs;
if (Enemy->MoveTimer <= 0)
{
Enemy->MoveTimer = Enemy->MoveTimeSecs;
direction MoveDir = RandomDirection();
v2i NewPos = {};
// Calculate new pos
switch (MoveDir)
{
case DIRECTION_right:
{
NewPos = Enemy->Pos + V2I(1, 0);
}
break;
case DIRECTION_left:
{
NewPos = Enemy->Pos - V2I(1, 0);
}
break;
case DIRECTION_up:
{
NewPos = Enemy->Pos - V2I(0, 1);
}
break;
case DIRECTION_down:
{
NewPos = Enemy->Pos + V2I(0, 1);
}
default: break;
}
// Throw away new pos if out of bounds
if (NewPos.X < 0 || NewPos.X >= App->Map.Dim.Width || NewPos.Y < 0 || NewPos.Y >= App->Map.Dim.Height)
{
continue;
}
v4 NewRect = V4(NewPos.X * App->TileSize.W, NewPos.Y * App->TileSize.H, App->TileSize.W, App->TileSize.H);
v4 PlayerRect = V4(
App->Window.Width/2 - App->TileSize.Width/2 + App->Player.Pos.X,
App->Window.Height/2 - App->TileSize.Height/2 + App->Player.Pos.Y,
App->TileSize.W,
App->TileSize.H
);
// Throw away new pos if collision on map
if (App->Map.Tiles[NewPos.Y][NewPos.X] == MAP_TILE_TYPE_wall || App->Map.Tiles[NewPos.Y][NewPos.X] == MAP_TILE_TYPE_water)
{
continue;
}
// Throw away new pos if collision with another enemy
b32 WasIntersection = false;
for (u32 J = 0; J < App->NumEnemies; ++J)
{
// Skip self-collision check
if (I == J)
{
continue;
}
if (RectRectIntersect(NewRect, App->Enemies[J].Rect))
{
WasIntersection = true;
break;
}
}
if (WasIntersection)
{
continue;
}
// Throw away new pos if collision with character
if (RectRectIntersect(PlayerRect, NewRect))
{
continue;
}
Enemy->Pos = NewPos;
Enemy->Rect = NewRect;
}
}
}
void UpdateDialogBox(SDL_Renderer* Renderer, app* App, input* Input)
{
f32 DialogBoxWidth = 800.0f;
f32 DialogBoxHeight = 400.0f;
if (App->DialogIndex >= ArrayCount(Dialog) || Dialog[App->DialogIndex] == 0) {
return;
}
v4 DialogBoxRect = V4(
App->Window.Width / 2 - DialogBoxWidth/2,
App->Window.Height/2 - DialogBoxHeight/8,
DialogBoxWidth,
DialogBoxHeight
);
DrawRect(
Renderer,
DialogBoxRect,
V4(0, 0, 0, 0.5)
);
SetDrawColor(Renderer, V4(1, 1, 1, 1));
SDL_RenderDrawLine(Renderer, DialogBoxRect.X, DialogBoxRect.Y, DialogBoxRect.X + DialogBoxRect.Width, DialogBoxRect.Y);
SDL_RenderDrawLine(Renderer, DialogBoxRect.X + DialogBoxRect.Width, DialogBoxRect.Y, DialogBoxRect.X + DialogBoxRect.Width, DialogBoxRect.Y + DialogBoxRect.Height);
SDL_RenderDrawLine(Renderer, DialogBoxRect.X + DialogBoxRect.Width, DialogBoxRect.Y + DialogBoxRect.Height, DialogBoxRect.X, DialogBoxRect.Y + DialogBoxRect.Height);
SDL_RenderDrawLine(Renderer, DialogBoxRect.X, DialogBoxRect.Y + DialogBoxRect.Height, DialogBoxRect.X, DialogBoxRect.Y);
DrawTextRevised(Renderer, App, Dialog[App->DialogIndex], 30, V2I(DialogBoxRect.X + 10, DialogBoxRect.Y + 10));
if (WasPressed(Input->ActionRight))
{
if (App->DialogIndex < ArrayCount(Dialog))
{
App->DialogIndex++;
}
}
}
void Update(SDL_Renderer* Renderer, app* App, input* Input)
{
// NOTE(eric): Player movement
f32 PlayerVel = 500.0f;
v2 dP = {};
if (App->FadeInSecs <= 0) {
if (IsPressed(Input->Shift))
{
if (WasPressed(Input->MoveLeft))
{
App->Player.Dir = DirectionLeft(App->Player.Dir);
}
else if (WasPressed(Input->MoveRight))
{
App->Player.Dir = DirectionRight(App->Player.Dir);
}
}
else
{
if (IsPressed(Input->MoveLeft))
{
dP.X = -Input->DeltaTimeSecs * PlayerVel;
}
else if (IsPressed(Input->MoveRight))
{
dP.X = Input->DeltaTimeSecs * PlayerVel;
}
if (IsPressed(Input->MoveUp))
{
dP.Y = -Input->DeltaTimeSecs * PlayerVel;
}
else if (IsPressed(Input->MoveDown))
{
dP.Y = Input->DeltaTimeSecs * PlayerVel;
}
}
}
MovePlayerX(App, dP.X);
MovePlayerY(App, dP.Y);
TickEnemies(App, Input);
if (WasPressed(Input->ActionDown) && App->FadeInSecs <= 0)
{
enemy* HitEnemy = WeaponHitEnemy(App);
if (HitEnemy)
{
HitEnemy->HP -= 2;
}
}
DrawMap(Renderer, App);
DrawEnemies(Renderer, App);
DrawPlayer(Renderer, App);
//DrawTopBar(Renderer, App);
if (App->FadeInSecs >= 0.0f) {
DrawRect(Renderer, V4(0, 0, App->Window.Width, App->Window.Height), V4(0, 0, 0, Lerp(0.0f, 1.0f, App->FadeInSecs / App->TotalFadeInSecs)));
App->FadeInSecs -= Input->DeltaTimeSecs;
}
if (App->FadeInSecs <= 0) {
UpdateDialogBox(Renderer, App, Input);
}
// NOTE(eric): Draw collision rects
#if 0
v2 Pos = V2(
App->Window.Width/2 - App->TileSize.W/2,
App->Window.Height/2 - App->TileSize.H/2
);
DrawRect(Renderer, V4(Pos.X, Pos.Y + 3*App->TileSize.H/4, App->TileSize.W, App->TileSize.H/4));
for (u32 I = 0; I < App->NumCollisionRects; ++I)
{
v4 UpdatedRect = App->CollisionRects[I];
UpdatedRect.X -= App->Player.Pos.X;
UpdatedRect.Y -= App->Player.Pos.Y;
DrawRect(Renderer, UpdatedRect);
}
for (u32 I = 0; I < App->NumEnemies; ++I)
{
v4 EnemyRect = App->Enemies[I].Rect;
EnemyRect.X -= App->Player.Pos.X;
EnemyRect.Y -= App->Player.Pos.Y;
DrawRect(Renderer, EnemyRect);
}
#endif
}
void BuildMapCollisionRects(char* Map, v2i MapDim) {
v4 Rects[100];
u32 NumRects = 0;
for (i32 Y = 0; Y < MapDim.Height; ++Y) {
v4 CurrentRect = V4(-1, -1, -1, -1);
for (i32 X = 0; X < MapDim.Width; ++X) {
if (Map[X + Y*MapDim.Width] == 'X') {
if (CurrentRect.X == -1) {
CurrentRect.X = X;
CurrentRect.Y = Y;
CurrentRect.Height = 1;
CurrentRect.Width = 1;
} else {
CurrentRect.Width += 1;
}
} else if (CurrentRect.X != -1) {
Rects[NumRects++] = CurrentRect;
CurrentRect = V4(-1, -1, -1, -1);
}
}
if (CurrentRect.X != -1) {
Rects[NumRects++] = CurrentRect;
}
}
}
void AppInit(app* App)
{
// NOTE(eric): Tile size derived from window size
i32 TileDim = App->Window.Width / TILES_PER_WINDOW_WIDTH;
App->TileSize = V2I(TileDim, TileDim);
App->FadeInSecs = 1.0f;
App->TotalFadeInSecs = 1.0f;
App->DialogIndex = 0;
// NOTE(eric): Load font
if (!FontLoad(//"Inconsolata-Regular.ttf",
FONT_FILE_NAME,
&App->PermArena, &App->Font)) {
fprintf(stderr, "error: Unable to load font '%s'\n", FONT_FILE_NAME);
}
App->Player.Pos = V2(240, 1236);//V2(0, 0);// {{App->Window.Width/2 - TileDim/2, App->Window.Height/2 - TileDim/2}};
App->Player.Color = V4(1, 0, 1, 1);
App->Player.WeaponColor = V4(1, 1, 1, 1);
App->Player.WeaponDim = V2(App->TileSize.Width, App->TileSize.Height / 4);
App->Player.Dir = DIRECTION_right;
// NOTE(eric): Build out our map
App->Map.Dim = V2I(20, 20);
char Array[20][20] = {
{'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'},
{'X', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'X', '_', '_', '_', 'X'},
{'X', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'X', '_', '_', '_', 'X'},
{'X', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'X', '_', '_', '_', 'X'},
{'X', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'X', 'X', 'X', '_', 'X', '_', '_', '_', 'X'},
{'X', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '~', '~', '~', '_', 'X', 'X', 'X', '_', 'X'},
{'X', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '~', '~', '~', '_', '_', '_', '_', '_', 'X'},
{'X', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'X'},
{'X', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'X'},
{'X', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'X'},
{'X', '_', '_', '_', '_', '_', '_', 'X', '_', '_', 'X', '_', '_', '_', '_', '_', '_', '_', '_', 'X'},
{'X', '_', '_', '_', '_', '_', '_', 'X', '_', '_', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'},
{'X', '_', '_', '_', '_', '_', '_', 'X', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'X'},
{'X', '_', '_', '_', '_', '_', '_', 'X', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'X'},
{'X', '_', '_', '_', '_', '_', '_', 'X', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'X'},
{'X', '_', '_', '_', '_', '_', '_', 'X', '_', '_', '_', 'X', '_', '_', 'X', 'X', '_', '_', '_', 'X'},
{'X', '_', '_', '_', '_', '_', '_', 'X', '_', '_', '_', 'X', '_', '_', '_', 'X', '_', '_', '_', 'X'},
{'X', '_', '_', '_', '_', '_', '_', 'X', '_', '_', '_', 'X', '_', '_', '_', 'X', '_', '_', '_', 'X'},
{'X', '_', '_', '_', '_', '_', '_', 'X', '_', '_', '_', 'X', '_', '_', '_', 'X', '_', '_', '_', 'X'},
{'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'},
};
memcpy(&App->Map.Tiles, Array, sizeof(char)*App->Map.Dim.X*App->Map.Dim.Y);
BuildMapCollisionRects((char*)App->Map.Tiles, App->Map.Dim);
// NOTE(eric): Place enemies
for (u32 I = 0; I < 5; ++I)
{
enemy* Enemy = App->Enemies + App->NumEnemies;
// HP Initialization
Enemy->HP = 10;
Enemy->MaxHP = 10;
Enemy->MoveTimeSecs = 5;
Enemy->MoveTimer = 5;
// Find random position
b32 ValidPos = false;
v4 PlayerRect = V4(
App->Window.Width/2 - App->TileSize.Width/2 + App->Player.Pos.X,
App->Window.Height/2 - App->TileSize.Height/2 + App->Player.Pos.Y + 3*App->TileSize.H/4,
App->TileSize.W,
App->TileSize.H/4
);
do {
Enemy->Pos = V2I(RandomI32(0, App->Map.Dim.W), RandomI32(0, App->Map.Dim.H));
Enemy->Rect = V4(Enemy->Pos.X * App->TileSize.W, Enemy->Pos.Y * App->TileSize.H, App->TileSize.W, App->TileSize.H);
if (App->Map.Tiles[Enemy->Pos.Y][Enemy->Pos.X] == MAP_TILE_TYPE_ground)
{
b32 AnotherEntityAtPos = false;
// Check enemy collision
for (u32 J = 0; J < App->NumEnemies; ++J)
{
if (App->Enemies[J].Pos == Enemy->Pos)
{
AnotherEntityAtPos = true;
}
}
// Check player collision
if (RectRectIntersect(Enemy->Rect, PlayerRect))
{
AnotherEntityAtPos = true;
}
ValidPos = !AnotherEntityAtPos;
}
} while (!ValidPos);
++App->NumEnemies;
}
// NOTE(eric): Build collision rects
for (u32 Y = 0; Y < App->Map.Dim.Height; ++Y)
{
for (u32 X = 0; X < App->Map.Dim.Width; ++X)
{
if (App->Map.Tiles[Y][X] == MAP_TILE_TYPE_wall || App->Map.Tiles[Y][X] == MAP_TILE_TYPE_water)
{
App->CollisionRects[App->NumCollisionRects++] = {{
(f32)X * App->TileSize.Width,
(f32)Y * App->TileSize.Height,
(f32)App->TileSize.Width,
(f32)App->TileSize.Height
}};
}
}
}
printf("Collision Rects: %d\n", App->NumCollisionRects);
}
int main()
{
{
SDL_Init(SDL_INIT_EVERYTHING);
SDL_Window* Window = SDL_CreateWindow(
"Tiler",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
WINDOW_WIDTH,
WINDOW_HEIGHT,
SDL_WINDOW_ALLOW_HIGHDPI
);
SDL_Renderer* Renderer = SDL_CreateRenderer(Window, -1, SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC);
// NOTE(eric): Handle High DPI displays where drawable size is possibly
// larger than window size.
int Width, Height;
SDL_GetRendererOutputSize(Renderer, &Width, &Height);
// NOTE(eric): Allocate storage arenas
u32 PermanentStorageSize = Megabytes(64);
u8* PermanentStorage = (u8*)malloc(PermanentStorageSize);
u32 TemporaryStorageSize = Megabytes(64);
u8* TemporaryStorage = (u8*)malloc(TemporaryStorageSize);
// NOTE(eric): Seed RNG before app initialization. Maybe move this into
// AppInit?
SeedRandomNumberGenerator();
app App = {};
App.Running = true;
App.Window = {{(f32)Width, (f32)Height}};
App.PermArena = ArenaInit(PermanentStorage, PermanentStorageSize);
App.TempArena = ArenaInit(TemporaryStorage, TemporaryStorageSize);
AppInit(&App);
input Input = {};
SDL_SetRenderDrawBlendMode(Renderer, SDL_BLENDMODE_BLEND);
SDL_Event Event;
u32 LastTime = SDL_GetTicks();
while (App.Running)
{
while (SDL_PollEvent(&Event))
{
if (Event.type == SDL_QUIT)
{
App.Running = false;
}
else if (Event.type == SDL_KEYDOWN || Event.type == SDL_KEYUP)
{
SDL_Keycode KeyCode = Event.key.keysym.sym;
b32 IsDown = (Event.key.state == SDL_PRESSED);
b32 WasDown = (Event.key.state == SDL_RELEASED && Event.key.repeat == 0);
if (KeyCode == SDLK_ESCAPE)
{
App.Running = false;
}
else if (IsDown != WasDown)
{
switch (KeyCode)
{
case SDLK_LEFT:
LinuxProcessKeyDown(&Input.MoveLeft, IsDown);
break;
case SDLK_RIGHT:
LinuxProcessKeyDown(&Input.MoveRight, IsDown);
break;
case SDLK_UP:
LinuxProcessKeyDown(&Input.MoveUp, IsDown);
break;
case SDLK_DOWN:
LinuxProcessKeyDown(&Input.MoveDown, IsDown);
break;
case SDLK_RETURN:
LinuxProcessKeyDown(&Input.ActionRight, IsDown);
break;
case SDLK_SPACE:
LinuxProcessKeyDown(&Input.ActionDown, IsDown);
break;
case SDLK_LSHIFT:
case SDLK_RSHIFT:
LinuxProcessKeyDown(&Input.Shift, IsDown);
break;
default: break;
}
}
}
}
SDL_SetRenderDrawColor(Renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
SDL_RenderClear(Renderer);
u32 CurrentTime = SDL_GetTicks();
//Input.DeltaTimeSecs = (CurrentTime - LastTime) / 1000.0f;
Input.DeltaTimeSecs = 1/60.0f;
Update(Renderer, &App, &Input);
LastTime = CurrentTime;
for (u32 I = 0; I < ArrayCount(Input.Keys); ++I)
{
Input.Keys[I].HalfTransitionCount = 0;
}
SDL_RenderPresent(Renderer);
}
free(TemporaryStorage);
free(PermanentStorage);
SDL_DestroyRenderer(Renderer);
SDL_DestroyWindow(Window);
SDL_Quit();
}
return(0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment