Split the simulation from presentation into an SDL-free library
Sim holds one whole game (tick, snapshots, checksum) and talks to the screen only through Cues, which the client's Scene plays and the server ignores. All drawing moved to scene.cpp. Match takes this screen's wishes as request flags instead of reading the menu, input and settings. The sim library builds without SDL and the server links it, ready to run games itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2007fb7edc
commit
465f75f6d7
38 files changed
+2225
-1965
No files matched your search
+34
-44
@@ -19,21 +19,21 @@ constexpr float CAMERA_FOLLOW = 0.15f;
|
||||
// middle, clamped to the world or centred when the world is smaller.
|
||||
static Vec2 camera_target_get(Game& game)
|
||||
{
|
||||
Vec2 world = game.world.size_get();
|
||||
Vec2 world = game.sim.world.size_get();
|
||||
Vec2 canvas = {CANVAS_WIDTH, CANVAS_HEIGHT};
|
||||
Vec2 center = {};
|
||||
int count = 0;
|
||||
for (int i = 0; i < game.fighterCount; i++)
|
||||
for (int i = 0; i < game.sim.fighterCount; i++)
|
||||
{
|
||||
if (!game.is_local(i) || !game.fighters[i].isAlive)
|
||||
if (!game.is_local(i) || !game.sim.fighters[i].isAlive)
|
||||
continue;
|
||||
center = center + game.fighters[i].center_get();
|
||||
center = center + game.sim.fighters[i].center_get();
|
||||
count++;
|
||||
}
|
||||
// Spectating, it follows whoever is being watched instead.
|
||||
if (game.ui.spectated >= 0)
|
||||
{
|
||||
center = game.fighters[game.ui.spectated].center_get();
|
||||
center = game.sim.fighters[game.ui.spectated].center_get();
|
||||
count = 1;
|
||||
}
|
||||
Vec2 target = (count > 0 ? center * (1.0f / count) : game.local_get().center_get()) - canvas * 0.5f;
|
||||
@@ -48,18 +48,20 @@ static void camera_update(Game& game)
|
||||
game.camera = game.camera + (camera_target_get(game) - game.camera) * CAMERA_FOLLOW;
|
||||
}
|
||||
|
||||
// One simulation tick: everything rollback saves and replays. Fighters hold still through a round's countdown. The
|
||||
// menu is this screen's alone, so it updates before the tick (see frame_run), not in it.
|
||||
// One tick of the game this screen shows, with what it asks of the match offline: the menu's Play, Escape (or B)
|
||||
// leaving the results unless it's just closed the menu, and its player's colour. The menu is this screen's alone, so
|
||||
// it updates before the tick (see frame_run), not in it.
|
||||
void game_tick(Game& game)
|
||||
{
|
||||
game.match.update(game);
|
||||
if (game.match.state != MATCH_COUNTDOWN)
|
||||
game.bot.update(game);
|
||||
for (int i = 0; i < game.fighterCount && game.match.state != MATCH_COUNTDOWN; i++)
|
||||
game.fighters[i].update(game);
|
||||
game.weapons.update(game);
|
||||
game.world.update(game);
|
||||
game.input.tick_end();
|
||||
Match& match = game.sim.match;
|
||||
Input& input = game.input;
|
||||
match.isOnline = game.session.isActive;
|
||||
match.isPlayRequested = input.isPlayRequested;
|
||||
match.isSandboxRequested = !game.menu.isOpen && !game.menu.isEscapeTaken &&
|
||||
(input.is_pressed(SDL_SCANCODE_ESCAPE) || input.is_pad_tapped(SDL_GAMEPAD_BUTTON_EAST));
|
||||
match.colorWanted = game.settings.color_get();
|
||||
game.sim.tick();
|
||||
input.tick_end();
|
||||
}
|
||||
|
||||
// Steps what only this screen sees, once per live tick: particles, HUD fades and the following camera.
|
||||
@@ -67,7 +69,7 @@ static void presentation_update(Game& game)
|
||||
{
|
||||
game.effects.update();
|
||||
game.ui.update(game);
|
||||
game.sprites.update(game);
|
||||
game.scene.update();
|
||||
game.cameraPrevious = game.camera;
|
||||
camera_update(game);
|
||||
}
|
||||
@@ -77,50 +79,37 @@ static void presentation_update(Game& game)
|
||||
static void game_draw(Game& game)
|
||||
{
|
||||
Rect boxes[MAX_FIGHTERS];
|
||||
std::vector<Attack> attacks = game.weapons.attacks;
|
||||
for (int i = 0; i < game.fighterCount; i++)
|
||||
std::vector<Attack> attacks = game.sim.weapons.attacks;
|
||||
for (int i = 0; i < game.sim.fighterCount; i++)
|
||||
{
|
||||
boxes[i] = game.fighters[i].body.box;
|
||||
game.fighters[i].blend(game.alpha);
|
||||
boxes[i] = game.sim.fighters[i].body.box;
|
||||
game.sim.fighters[i].blend(game.alpha);
|
||||
}
|
||||
game.weapons.blend(game.alpha);
|
||||
game.sim.weapons.blend(game.alpha);
|
||||
|
||||
Vec2 camera = vec2_lerp(game.cameraPrevious, game.camera, game.alpha);
|
||||
if (game.settings.isShaking)
|
||||
camera = camera + game.effects.shake_get();
|
||||
game.renderer.camera = {std::round(camera.x), std::round(camera.y)};
|
||||
|
||||
game.world.draw(game);
|
||||
game.bot.draw(game);
|
||||
game.effects.afterimage_draw(game);
|
||||
game.weapons.blast_draw(game);
|
||||
for (int i = 0; i < game.fighterCount; i++)
|
||||
game.fighters[i].draw(game);
|
||||
game.weapons.draw(game);
|
||||
if (game.settings.isCollisionShown)
|
||||
game.weapons.collision_draw(game);
|
||||
game.scene.draw();
|
||||
game.effects.draw(game);
|
||||
game.ui.draw(game);
|
||||
game.renderer.present();
|
||||
|
||||
for (int i = 0; i < game.fighterCount; i++)
|
||||
game.fighters[i].body.box = boxes[i];
|
||||
game.weapons.attacks = attacks;
|
||||
for (int i = 0; i < game.sim.fighterCount; i++)
|
||||
game.sim.fighters[i].body.box = boxes[i];
|
||||
game.sim.weapons.attacks = attacks;
|
||||
}
|
||||
|
||||
// Sets up everything the simulation needs: the world, the bot graph, fighters, weapons, trinkets and the seeded
|
||||
// generator (RPG_SEED, every number its own odd state, or the clock). Without a renderer the sprites come back empty,
|
||||
// which the simulation never needs.
|
||||
// Sets up the game, seeded from RPG_SEED (every number its own odd state) or the clock. Its cues go nowhere until a
|
||||
// window shows them.
|
||||
static void simulation_init(Game& game)
|
||||
{
|
||||
game.world.init(game);
|
||||
game.bot.init(game);
|
||||
for (int i = 0; i < MAX_FIGHTERS; i++)
|
||||
game.fighters[i].init(game, i);
|
||||
const char* seed = SDL_getenv("RPG_SEED");
|
||||
game.sim.init(seed ? (Uint32)SDL_atoi(seed) * 2 + 1 : (Uint32)SDL_GetPerformanceCounter() | 1);
|
||||
game.local_get().isBot = SDL_getenv("RPG_BOT_LOCAL");
|
||||
game.rollback.init();
|
||||
const char* seed = SDL_getenv("RPG_SEED");
|
||||
game.rng.state = seed ? (Uint32)SDL_atoi(seed) * 2 + 1 : (Uint32)SDL_GetPerformanceCounter() | 1;
|
||||
}
|
||||
|
||||
// The frame clock: when the last frame began, and the time not yet simulated.
|
||||
@@ -173,9 +162,9 @@ static bool frame_run(bool isDrawn = true)
|
||||
continue;
|
||||
}
|
||||
session.view_leave(game);
|
||||
for (int i = 0; i < game.fighterCount; i++)
|
||||
for (int i = 0; i < game.sim.fighterCount; i++)
|
||||
{
|
||||
Fighter& fighter = game.fighters[i];
|
||||
Fighter& fighter = game.sim.fighters[i];
|
||||
bool isMenuOpen = game.menu.isOpen && i == game.local;
|
||||
if (game.is_local(i))
|
||||
fighter.controls = game.input.controls_get(game.devices[i], game.camera, fighter.center_get(),
|
||||
@@ -265,6 +254,7 @@ int main(int argc, char** argv)
|
||||
game.ui.init(game);
|
||||
game.sprites.init(game);
|
||||
simulation_init(game);
|
||||
game.sim.cues = &game.scene;
|
||||
game.renderer.atlas_upload();
|
||||
game.settings.apply(game);
|
||||
game.settings.restore(game);
|
||||
|
||||
Reference in new issue
Block a user