Created
April 30, 2020 23:19
-
-
Save wbbradley/c2c31ddffbc06c16687b5aab49d75a09 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/********************************************************************************************** | |
* | |
* raylib 32x32 game/demo competition | |
* | |
* Competition consist in developing a videogame in a 32x32 pixels screen size. | |
* | |
* LICENSE: zlib/libpng | |
* | |
* Copyright (c) 2020 Will Bradley (@wbbradley) | |
* | |
* This software is provided "as-is", without any express or implied warranty. | |
* In no event will the authors be held liable for any damages arising from the | |
* use of this software. | |
* | |
* Permission is granted to anyone to use this software for any purpose, | |
* including commercial applications, and to alter it and redistribute it freely, | |
* subject to the following restrictions: | |
* | |
* 1. The origin of this software must not be misrepresented; you must not | |
* claim that you wrote the original software. If you use this software in a | |
* product, an acknowledgment in the product documentation would be appreciated | |
* but is not required. | |
* | |
* 2. Altered source versions must be plainly marked as such, and must not | |
* be misrepresented as being the original software. | |
* | |
* 3. This notice may not be removed or altered from any source | |
* distribution. | |
* | |
**********************************************************************************************/ | |
#include "raylib.h" | |
#include "raymath.h" | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <assert.h> | |
#define PLAYER_MAX 4 | |
#define MENU_DEMO_MONSTERS 4 | |
#define GAME_SIZE 32 | |
#define ANON_BULLETS 0 | |
#define CLOSE_ENOUGH 1.0 | |
#define BOUNCY_WALLS 0 | |
#define BULLET_FREQ 6 | |
#define BULLET_MAX_AGE 60*14 | |
#define STICKY_BULLETS 1 | |
#define STICKY_WALLS (STICKY_BULLETS && 0) | |
#define MAX_MOTION_SPEED 0.25 | |
#define BULLET_SPEED (MAX_MOTION_SPEED * 1.5) | |
#define INCLUDE_MOVEMENT_IN_SHOOTING 0 | |
#define max(a, b) ((a) > (b) ? (a) : (b)) | |
#define min(a, b) ((a) < (b) ? (a) : (b)) | |
const int windowWidth = 768; | |
const int windowHeight = 768; | |
const int gameScreenWidth = GAME_SIZE; | |
const int gameScreenHeight = GAME_SIZE; | |
#ifdef __APPLE__ | |
// HACKHACK: get the correct HiDPI scaling ratio for this machine... | |
const float extra_scale = 2.0; | |
#else | |
const float extra_scale = 1.0; | |
#endif | |
static Color *player_colors = NULL; | |
static Color *bullet_colors = NULL; | |
static Color wall_color = {0xce, 0xce, 0xce, 0xff}; | |
static const float gamepad_fudge = 0.3; | |
struct State { | |
bool menu_visible; | |
bool game_paused; | |
int player_max_selection; | |
struct Game *game; | |
}; | |
struct Vec2D { | |
float x, y; | |
}; | |
static const struct Vec2D origin = {0.0, 0.0}; | |
struct Player { | |
struct Vec2D pos; | |
bool is_monster; | |
int gamepad; | |
int last_bullet_frame; | |
struct Color color; | |
}; | |
struct Game { | |
int frame_count; | |
int humans; | |
int monsters; | |
int score; | |
bool game_over; | |
struct Player player[PLAYER_MAX]; | |
struct Bullet *bullets; | |
}; | |
int GameGetPlayerCount(struct Game *game) { | |
return game->humans + game->monsters; | |
} | |
struct Bullet { | |
struct Vec2D pos; | |
struct Vec2D dir; | |
int player; | |
int alive_until_frame; | |
bool dead; | |
struct Bullet *next; | |
}; | |
const int ALIVE_UNTIL_FOREVER = -1; | |
Color palette_lerp(const Color *palette, int palette_count, float alpha) { | |
int index = (float)palette_count * Clamp(alpha, 0.0, 1.0); | |
if (index >= palette_count) { | |
index = palette_count - 1; | |
} | |
return palette[index]; | |
} | |
float sqr(float x) { | |
return x * x; | |
} | |
float vec2d_length_squared(struct Vec2D a) { | |
return sqr(a.x)+sqr(a.y); | |
} | |
float vec2d_length(struct Vec2D a) { | |
return sqrt(vec2d_length_squared(a)); | |
} | |
float distance_squared(struct Vec2D *a, struct Vec2D *b) { | |
return sqr(a->x-b->x)+sqr(a->y-b->y); | |
} | |
float distance(struct Vec2D *a, struct Vec2D *b) { | |
return sqrt(distance_squared(a, b)); | |
} | |
struct Vec2D scale(float a, struct Vec2D b) { | |
return (struct Vec2D){.x=b.x*a, .y=b.y*a}; | |
} | |
struct Vec2D roundto(struct Vec2D a, float z) { | |
return (struct Vec2D){ | |
.x = round(a.x / z) * z, | |
.y = round(a.y / z) * z, | |
}; | |
} | |
struct Vec2D subtract(struct Vec2D a, struct Vec2D b) { | |
return (struct Vec2D){.x=a.x-b.x, .y=a.y-b.y}; | |
} | |
struct Vec2D add(struct Vec2D a, struct Vec2D b) { | |
return (struct Vec2D){.x=a.x+b.x, .y=a.y+b.y}; | |
} | |
struct Vec2D rotate90(struct Vec2D a) { | |
return (struct Vec2D){ | |
.x = -a.y, | |
.y = a.x, | |
}; | |
} | |
struct Vec2D rotateneg90(struct Vec2D a) { | |
return (struct Vec2D){ | |
.x = a.y, | |
.y = -a.x, | |
}; | |
} | |
float dot(struct Vec2D a, struct Vec2D b) { | |
return a.x*b.x+a.y*b.y; | |
} | |
struct Vec2D normalize(struct Vec2D a) { | |
float len = sqrt(sqr(a.x)+sqr(a.y)); | |
return (struct Vec2D){.x=a.x/len,.y=a.y/len}; | |
} | |
struct Vec2D clamp_length(struct Vec2D a, float min_length, float max_length) { | |
float len_squared = sqr(a.x) + sqr(a.y); | |
if (len_squared >= sqr(min_length)) { | |
if (len_squared <= sqr(max_length)) { | |
/* within range */ | |
return a; | |
} else { | |
return scale(max_length, normalize(a)); | |
} | |
} else { | |
return scale(min_length, normalize(a)); | |
} | |
} | |
struct Vec2D ClampMotion(struct Vec2D movement) { | |
return scale(MAX_MOTION_SPEED, clamp_length(movement, 0.0, 1.0)); | |
} | |
bool IsCloseEnough(struct Vec2D *a, struct Vec2D *b) { | |
return distance(a, b) < CLOSE_ENOUGH; | |
} | |
bool IsOffscreen(struct Vec2D a) { | |
return a.x < 0 || a.y < 0 || a.x > gameScreenWidth || a.y > gameScreenHeight; | |
} | |
bool IsBulletSolid(struct Bullet *bullet, int player) { | |
if (bullet->dead) { | |
return false; | |
} | |
#if ANON_BULLETS | |
return true; | |
#else | |
/* player's bullets are not "solid" to them in non-anonomyous bullet mode */ | |
return bullet->player != player; | |
#endif | |
} | |
bool IsBulletWall(struct Bullet *bullet) { | |
return !bullet->dead && vec2d_length_squared(bullet->dir) <= 1E-5; | |
} | |
bool IsBulletArmed(struct Bullet *bullet, int player) { | |
return !bullet->dead && !IsBulletWall(bullet) && IsBulletSolid(bullet, player); | |
} | |
bool IsFreePos(struct Game *game, struct Vec2D pos, int ignore_player) { | |
for (int i = 0; i < game->humans + game->monsters; i++) { | |
if (i == ignore_player) { | |
continue; | |
} | |
if (IsCloseEnough(&game->player[i].pos, &pos)) { | |
return false; | |
} | |
} | |
for (struct Bullet *bullet = game->bullets; bullet != NULL; | |
bullet = bullet->next) { | |
if (!IsBulletSolid(bullet, ignore_player)) { | |
continue; | |
} | |
if (IsCloseEnough(&bullet->pos, &pos)) { | |
return false; | |
} | |
} | |
return true; | |
} | |
struct Vec2D FindFreeRandomPosForPlayer(struct Game *game, int player) { | |
do { | |
struct Vec2D pos = (struct Vec2D){ | |
.x = rand() % gameScreenWidth, | |
.y = rand() % gameScreenHeight, | |
}; | |
if (IsFreePos(game, pos, player/*ignore_player*/)) { | |
return pos; | |
} | |
} while (true); | |
} | |
struct Vec2D FindFreeRandomPosAwayFromPlayer(struct Game *game) { | |
struct Vec2D pos; | |
float away_fraction = 0.7; | |
l_tryagain: | |
away_fraction *= 0.9; | |
pos = FindFreeRandomPosForPlayer(game, -1); | |
for (int i = 0; i < game->humans + game->monsters; i++) { | |
if (distance(&pos, &game->player[i].pos) < | |
min(gameScreenWidth, gameScreenHeight) * away_fraction) { | |
/* too close! */ | |
goto l_tryagain; | |
} | |
} | |
return pos; | |
} | |
void KillBullet(struct Bullet *bullet, bool caused_by_collision) { | |
if (bullet->alive_until_frame != ALIVE_UNTIL_FOREVER) { | |
#if STICKY_BULLETS | |
if (caused_by_collision) { | |
bullet->pos = roundto(bullet->pos, 0.5); | |
bullet->dir = origin; | |
bullet->player = PLAYER_MAX; | |
} else { | |
bullet->dead = true; | |
} | |
#else | |
bullet->dead = true; | |
#endif | |
} else { | |
/* alive forever bullets cannot be killed */ | |
} | |
} | |
void ResetBullets(struct Bullet *bullet) { | |
/* kill any bullets that may have been alive in the past game */ | |
for (; bullet != NULL; bullet = bullet->next) { | |
KillBullet(bullet, false); | |
} | |
} | |
void InitGame(struct Game *game, int humans, int monsters) { | |
*game = (struct Game){ | |
.game_over = false, | |
.frame_count = 0, | |
.score = 0, | |
.bullets = game->bullets, | |
}; | |
ResetBullets(game->bullets); | |
game->humans = 0; | |
game->monsters = 0; | |
for (int i = 0; i < humans; i++) { | |
game->humans = i; | |
game->player[i] = (struct Player){ | |
.pos = FindFreeRandomPosAwayFromPlayer(game), | |
.is_monster = false, | |
.gamepad = GAMEPAD_PLAYER1 + i, | |
.last_bullet_frame = 0, | |
.color = player_colors[i], | |
}; | |
} | |
game->humans = humans; | |
for (int i = 0; i < monsters; i++) { | |
game->monsters = i; | |
game->player[humans + i] = (struct Player){ | |
.pos = FindFreeRandomPosAwayFromPlayer(game), | |
.is_monster = true, | |
.gamepad = 0, | |
.last_bullet_frame = 0, | |
.color = player_colors[humans + i], | |
}; | |
} | |
game->monsters = monsters; | |
} | |
struct Game *NewGame(int humans, int monsters) { | |
struct Game *game = malloc(sizeof(struct Game)); | |
*game = (struct Game){0}; | |
InitGame(game, humans, monsters); | |
return game; | |
} | |
void InitBullet(struct Bullet *bullet, int frame_count, int player, float x, | |
float y, float dx, float dy) { | |
assert(bullet->dead); | |
struct Vec2D bullet_direction = normalize((struct Vec2D){.x = dx, .y = dy}); | |
struct Vec2D dir = scale(BULLET_SPEED, bullet_direction); | |
struct Vec2D pos = (struct Vec2D){.x = x, .y = y}; | |
struct Vec2D initial_pos = | |
add(pos, scale(CLOSE_ENOUGH * 1.5, bullet_direction)); | |
*bullet = (struct Bullet) { | |
.dead = false, .alive_until_frame = frame_count + BULLET_MAX_AGE, | |
.pos = initial_pos, .dir = dir, | |
#if ANON_BULLETS | |
.player = PLAYER_MAX, | |
#else | |
.player = player, | |
#endif | |
.next = bullet->next, | |
}; | |
} | |
struct Bullet *NewBullet(int frame_count, int player, float x, float y, float dx, float dy) { | |
static int count = 0; | |
++count; | |
struct Bullet *bullet = malloc(sizeof(struct Bullet)); | |
bullet->next = NULL; | |
bullet->dead = true; | |
InitBullet(bullet, frame_count, player, x, y, dx, dy); | |
return bullet; | |
} | |
void AddBullet(struct Game *game, int player, float x, float y, float dx, | |
float dy) { | |
if (game->player[player].last_bullet_frame + BULLET_FREQ > | |
game->frame_count) { | |
return; | |
} | |
game->player[player].last_bullet_frame = game->frame_count; | |
if (game->bullets == NULL) { | |
game->bullets = NewBullet(game->frame_count, player, x, y, dx, dy); | |
} else { | |
struct Bullet *bullet = game->bullets; | |
for (; bullet != NULL; bullet = bullet->next) { | |
if (bullet->dead) { | |
InitBullet(bullet, game->frame_count, player, x, y, dx, dy); | |
return; | |
} | |
} | |
bullet = NewBullet(game->frame_count, player, x, y, dx, dy); | |
bullet->next = game->bullets; | |
game->bullets = bullet; | |
} | |
} | |
void CollideBullets(struct Bullet *bullet, struct Bullet *bullet2) { | |
if (!IsBulletSolid(bullet, bullet2->player) || | |
!IsBulletSolid(bullet2, bullet->player)) { | |
return; | |
} | |
assert(!bullet->dead && !bullet2->dead); | |
if (!IsCloseEnough(&bullet->pos, &bullet2->pos)) { | |
return; | |
} | |
bool is_moving[2] = { | |
!IsBulletWall(bullet), | |
!IsBulletWall(bullet2), | |
}; | |
/* these bullets are too close */ | |
if (is_moving[0]) { | |
KillBullet(bullet, STICKY_WALLS || is_moving[1]); | |
} | |
if (is_moving[1]) { | |
KillBullet(bullet2, STICKY_WALLS || is_moving[0]); | |
} | |
} | |
void MoveBullets(struct State *state) { | |
struct Game *game = state->game; | |
int count =0; | |
for (struct Bullet *bullet = game->bullets; bullet != NULL; | |
bullet = bullet->next) { | |
if (bullet->alive_until_frame != ALIVE_UNTIL_FOREVER && | |
game->frame_count > bullet->alive_until_frame) { | |
KillBullet(bullet, false /*caused_by_collision*/); | |
continue; | |
} | |
if (bullet->dead) { | |
continue; | |
} | |
/* look for bullet collisions */ | |
for (struct Bullet *bullet2 = bullet->next; bullet2 != NULL; | |
bullet2 = bullet2->next) { | |
CollideBullets(bullet, bullet2); | |
} | |
if (bullet->dead) { | |
continue; | |
} | |
++count; | |
bullet->pos.x += bullet->dir.x; | |
bullet->pos.y += bullet->dir.y; | |
#if BOUNCY_WALLS | |
if (bullet->pos.y < 0) { | |
bullet->pos.y = 0; | |
bullet->dir.y *= -1; | |
} else if (bullet->pos.y > gameScreenHeight) { | |
bullet->pos.y = gameScreenHeight; | |
bullet->dir.y *= -1; | |
} | |
if (bullet->pos.x < 0) { | |
bullet->pos.x = 0; | |
bullet->dir.x *= -1; | |
} else if (bullet->pos.x > gameScreenWidth) { | |
bullet->pos.x = gameScreenWidth; | |
bullet->dir.x *= -1; | |
} | |
#endif | |
if (IsOffscreen(bullet->pos)) { | |
KillBullet(bullet, false); | |
} else { | |
for (int i = 0; i < game->humans + game->monsters; i++) { | |
/* player collision with bullet */ | |
if (IsBulletArmed(bullet, i) && | |
IsCloseEnough(&bullet->pos, &game->player[i].pos)) { | |
game->game_over = true; | |
InitGame(game, 0, MENU_DEMO_MONSTERS); | |
state->menu_visible = true; | |
} | |
} | |
} | |
} | |
} | |
struct Motion { | |
struct Vec2D movement; | |
struct Vec2D shooting; | |
}; | |
struct Motion PlanMonsterMoveCore(struct Game *game, int player_index) { | |
struct Player *monster = &game->player[player_index]; | |
/* find nearest danger */ | |
float nearest_distance_squared = sqr(max(gameScreenWidth, gameScreenWidth)); | |
struct Bullet *nearest_bullet = NULL; | |
for (struct Bullet *bullet = game->bullets; bullet != NULL; | |
bullet = bullet->next) { | |
if (bullet->dead || bullet->player == player_index) { | |
continue; | |
} | |
float d_squared = distance_squared(&bullet->pos, &monster->pos); | |
if (d_squared < nearest_distance_squared) { | |
nearest_bullet = bullet; | |
nearest_distance_squared = d_squared; | |
} | |
} | |
struct Vec2D towards_center = | |
clamp_length(subtract( | |
(struct Vec2D){ | |
.x = gameScreenWidth / 2.0, | |
.y = gameScreenHeight / 2.0, | |
}, | |
monster->pos), | |
0.0, 0.2); | |
int enemy_player_index = rand() % (GameGetPlayerCount(game)-1); | |
if (enemy_player_index >= player_index) { | |
enemy_player_index += 1; | |
} | |
struct Vec2D dir = towards_center; | |
if (nearest_bullet != NULL) { | |
struct Vec2D dodge_bullet; | |
struct Vec2D towards_bullet = | |
normalize(subtract(nearest_bullet->pos, monster->pos)); | |
struct Vec2D (*evasion)(struct Vec2D) = | |
(((game->frame_count / ((player_index + 3) * 60) + | |
player_index * (rand() % 10 == 1)) & | |
1) | |
? rotateneg90 | |
: rotate90); | |
if (IsBulletWall(nearest_bullet)) { | |
return (struct Motion){ | |
.movement = scale(0.8, evasion(towards_bullet)), | |
.shooting = normalize( | |
subtract(game->player[enemy_player_index].pos, monster->pos)), | |
}; | |
} else { | |
dodge_bullet = rotate90(normalize(nearest_bullet->dir)); | |
} | |
float t = dot(dodge_bullet, towards_bullet); | |
if (t > 0) { | |
dodge_bullet = (struct Vec2D){ | |
.x = -dodge_bullet.x, | |
.y = -dodge_bullet.y, | |
}; | |
} | |
dir = normalize(add(dir, dodge_bullet)); | |
return (struct Motion){ | |
.movement = dir, | |
.shooting = normalize( | |
subtract(game->player[enemy_player_index].pos, monster->pos)), | |
}; | |
} | |
return (struct Motion){ | |
.movement = dir, | |
.shooting = normalize( | |
subtract(game->player[enemy_player_index].pos, monster->pos)), | |
}; | |
} | |
struct Motion PlanPlayerMove(struct Game *game, int player_index) { | |
struct Player *player = &game->player[player_index]; | |
if (player->is_monster) { | |
return PlanMonsterMoveCore(game, player_index); | |
} | |
float clx = 0.0, cly = 0.0, crx = 0.0, cry = 0.0; | |
if (IsKeyDown(KEY_UP)) { | |
cry = -1.0; | |
} | |
if (IsKeyDown(KEY_DOWN)) { | |
cry = 1.0; | |
} | |
if (IsKeyDown(KEY_LEFT)) { | |
crx = -1.0; | |
} | |
if (IsKeyDown(KEY_RIGHT)) { | |
crx = 1.0; | |
} | |
if (IsKeyDown('W')) { | |
cly = -1.0; | |
} | |
if (IsKeyDown('A')) { | |
clx = -1.0; | |
} | |
if (IsKeyDown('S')) { | |
cly = 1.0; | |
} | |
if (IsKeyDown('D')) { | |
clx = 1.0; | |
} | |
const int gamepad = player->gamepad; | |
clx += GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_LEFT_X); | |
cly += GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_LEFT_Y); | |
crx += GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_RIGHT_X); | |
cry += GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_RIGHT_Y); | |
return (struct Motion){ | |
.movement = | |
(struct Vec2D){ | |
.x = clx, | |
.y = cly, | |
}, | |
.shooting = | |
(struct Vec2D){ | |
.x = crx, | |
.y = cry, | |
}, | |
}; | |
} | |
struct Motion PlanPlayerMoveSafely(struct Game *game, int player_index) { | |
struct Motion motion = PlanPlayerMove(game, player_index); | |
struct Vec2D pos; | |
pos = add(game->player[player_index].pos, ClampMotion(motion.movement)); | |
if (IsFreePos(game, pos, player_index)) { | |
return motion; | |
} | |
struct Vec2D y_motion = (struct Vec2D){.x = 0, .y = motion.movement.y}; | |
pos = add(game->player[player_index].pos, ClampMotion(y_motion)); | |
if (IsFreePos(game, pos, player_index)) { | |
return (struct Motion){ | |
.movement = y_motion, | |
.shooting = motion.shooting, | |
}; | |
} | |
struct Vec2D x_motion = (struct Vec2D){.x = motion.movement.x, .y = 0}; | |
pos = add(game->player[player_index].pos, ClampMotion(x_motion)); | |
if (IsFreePos(game, pos, player_index)) { | |
return (struct Motion){ | |
.movement = x_motion, | |
.shooting = motion.shooting, | |
}; | |
} | |
/* no where to move! */ | |
return (struct Motion){ | |
.movement = origin, | |
.shooting = motion.shooting, | |
}; | |
} | |
void MovePlayer(struct Game *game, int player_index) { | |
struct Player *player = &game->player[player_index]; | |
/* get the player's desired directions */ | |
struct Motion motion = PlanPlayerMoveSafely(game, player_index); | |
/* clean up the player's supplied directions */ | |
motion.movement = ClampMotion(motion.movement); | |
motion.shooting = clamp_length(motion.shooting, 0.0, 0.5); | |
/* apply the desired motions to the world */ | |
player->pos.x += motion.movement.x; | |
player->pos.y += motion.movement.y; | |
if (fabs(motion.shooting.x) > gamepad_fudge || | |
fabs(motion.shooting.y) > gamepad_fudge) { | |
AddBullet(game, player_index, | |
player->pos.x + motion.movement.x + motion.shooting.x, | |
player->pos.y + motion.movement.y + motion.shooting.y, | |
#if INCLUDE_MOVEMENT_IN_SHOOTING | |
motion.movement.x + | |
#endif | |
motion.shooting.x, | |
#if INCLUDE_MOVEMENT_IN_SHOOTING | |
motion.movement.y + | |
#endif | |
motion.shooting.y); | |
} | |
/* clamp the player's position to the world coordinates */ | |
player->pos.x = max(0, min(player->pos.x, gameScreenWidth - 1)); | |
player->pos.y = max(0, min(player->pos.y, gameScreenHeight - 1)); | |
} | |
void UpdateGameState(struct State *state) { | |
struct Game *game = state->game; | |
if (state->menu_visible) { | |
if (IsGamepadButtonPressed(GAMEPAD_PLAYER1, GAMEPAD_BUTTON_MIDDLE) || | |
IsGamepadButtonPressed(GAMEPAD_PLAYER2, GAMEPAD_BUTTON_MIDDLE) || | |
IsKeyPressed(KEY_ENTER)) { | |
InitGame(game, 1, 2); | |
state->menu_visible = false; | |
} | |
} | |
game->frame_count++; | |
MoveBullets(state); | |
for (int i = 0; i < GameGetPlayerCount(game); i++) { | |
MovePlayer(game, i); | |
} | |
} | |
float alpha(float x, float y) { | |
return x / y; | |
} | |
void DrawGame(struct Game *game) { | |
ClearBackground(BLACK); | |
for (int i = 0; i < game->score; i++) { | |
DrawPixel(i % gameScreenWidth, i / gameScreenWidth, | |
(Color){0xff, 0xff, 0x00, 0xff}); | |
} | |
for (struct Bullet *bullet = game->bullets; bullet != NULL; | |
bullet = bullet->next) { | |
if (bullet->dead) { | |
continue; | |
} | |
DrawPixel(bullet->pos.x, bullet->pos.y, | |
IsBulletArmed(bullet, -1) | |
? bullet_colors[bullet->player] | |
: Fade(wall_color, Lerp(0.5, 1.0, | |
alpha(bullet->alive_until_frame - | |
game->frame_count, | |
BULLET_MAX_AGE)))); | |
} | |
for (int i = 0; i < GameGetPlayerCount(game); i++) { | |
struct Player *player = &game->player[i]; | |
DrawPixel(player->pos.x, player->pos.y, player->color); | |
} | |
} | |
void InitColors() { | |
free(player_colors); | |
player_colors = malloc(sizeof(player_colors[0]) * PLAYER_MAX); | |
free(bullet_colors); | |
bullet_colors = malloc(sizeof(bullet_colors[0]) * (PLAYER_MAX + 1)); | |
for (int i = 0; i < PLAYER_MAX; i++) { | |
player_colors[i] = ColorFromHSV( | |
(struct Vector3){(float)i * 360.0 / (float)PLAYER_MAX, 0.85, 0.85}); | |
bullet_colors[i] = ColorFromHSV( | |
(struct Vector3){(float)i * 360.0 / (float)PLAYER_MAX, 0.25, 0.5}); | |
} | |
bullet_colors[PLAYER_MAX] = ColorFromHSV((struct Vector3){0, 0.0, 0.25}); | |
} | |
void InitState(struct State *state) { | |
*state = (struct State){ | |
.game = NewGame(0, MENU_DEMO_MONSTERS), | |
.menu_visible = true, | |
.game_paused = false, | |
.player_max_selection = 0, | |
}; | |
} | |
void DrawMenu(const struct State *state) { | |
if (!state->menu_visible) { | |
return; | |
} | |
float inset = 1.0; | |
struct Rectangle r = { | |
.x = inset, | |
.y = inset, | |
.width = gameScreenWidth - inset * 2.0, | |
.height = gameScreenHeight - inset * 2.0, | |
}; | |
DrawRectangleRounded(r, 0.1 /*roundness*/, 2 /*segments*/, Fade(GRAY, 0.7)); | |
DrawText("bar-", 0, 0, 1, RED); | |
DrawText("rage", 6, 8, 1, RED); | |
} | |
int main(void) { | |
InitColors(); | |
SetTraceLogLevel(LOG_WARNING); | |
SetConfigFlags(FLAG_FULLSCREEN_MODE); | |
InitWindow(windowWidth, windowHeight, "my 32x32 game/demo"); | |
RenderTexture2D target = LoadRenderTexture(gameScreenWidth, gameScreenHeight); | |
SetTextureFilter(target.texture, | |
FILTER_POINT); | |
SetTargetFPS(60); | |
//-------------------------------------------------------------------------------------- | |
struct State state; | |
InitState(&state); | |
while (!WindowShouldClose()) { | |
if (!state.game_paused) { | |
if (IsKeyPressed('P')) { | |
state.game_paused = true; | |
} else { | |
UpdateGameState(&state); | |
} | |
} else if (IsKeyPressed('P')) { | |
state.game_paused = false; | |
} | |
float scale = min((float)GetScreenWidth() / gameScreenWidth, | |
(float)GetScreenHeight() / gameScreenHeight) * | |
extra_scale; | |
BeginDrawing(); | |
ClearBackground(BLACK); | |
BeginTextureMode(target); | |
DrawGame(state.game); | |
DrawMenu(&state); | |
EndTextureMode(); | |
DrawTexturePro(target.texture, | |
(Rectangle){0.0f, 0.0f, (float)target.texture.width, | |
(float)-target.texture.height}, | |
(Rectangle){0, 0, (float)gameScreenWidth * scale, | |
(float)gameScreenHeight * scale}, | |
(Vector2){0, 0}, 0.0f, WHITE); | |
EndDrawing(); | |
} | |
UnloadRenderTexture(target); | |
CloseWindow(); | |
return EXIT_SUCCESS; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment