WIP decoupled rendering
This commit is contained in:
parent
ba53a7dd66
commit
a43b46449f
58
README.md
Normal file
58
README.md
Normal file
@ -0,0 +1,58 @@
|
||||
## Physics and Timing
|
||||
|
||||
I want to have decoupled rendering with the ability to render partial physics simulations, so it needs to be deterministic.
|
||||
|
||||
Based on this snippet from [this article](https://gafferongames.com/post/fix_your_timestep/)
|
||||
|
||||
```c
|
||||
double t = 0.0;
|
||||
double dt = 0.01;
|
||||
|
||||
double currentTime = hires_time_in_seconds();
|
||||
double accumulator = 0.0;
|
||||
|
||||
State previous;
|
||||
State current;
|
||||
|
||||
while ( !quit )
|
||||
{
|
||||
double newTime = time();
|
||||
double frameTime = newTime - currentTime;
|
||||
if ( frameTime > 0.25 )
|
||||
frameTime = 0.25;
|
||||
currentTime = newTime;
|
||||
|
||||
accumulator += frameTime;
|
||||
|
||||
while ( accumulator >= dt )
|
||||
{
|
||||
previousState = currentState;
|
||||
integrate( currentState, t, dt );
|
||||
t += dt;
|
||||
accumulator -= dt;
|
||||
}
|
||||
|
||||
const double alpha = accumulator / dt;
|
||||
|
||||
State state = currentState * alpha +
|
||||
previousState * ( 1.0 - alpha );
|
||||
|
||||
render( state );
|
||||
}
|
||||
```
|
||||
|
||||
or from this [medium article](https://medium.com/@tglaiel/how-to-make-your-game-run-at-60fps-24c61210fe75)
|
||||
|
||||
```c
|
||||
while(running){
|
||||
computeDeltaTimeSomehow();
|
||||
accumulator += deltaTime;
|
||||
while(accumulator >= 1.0/60.0){
|
||||
previous_state = current_state;
|
||||
current_state = update();
|
||||
accumulator -= 1.0/60.0;
|
||||
}
|
||||
render_interpolated_somehow(previous_state, current_state, accumulator/(1.0/60.0));
|
||||
display();
|
||||
}
|
||||
```
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 300 KiB |
@ -1,6 +1,9 @@
|
||||
const sdl = @import("./sdl.zig").c;
|
||||
const std = @import("std");
|
||||
const sf = @import("./slime_factory.zig");
|
||||
const atils = @import("./asset_utils.zig");
|
||||
const GameText = @import("./text.zig").GameText;
|
||||
const RGBAColor = @import("./utils/rgb_color.zig").RGBAColor;
|
||||
|
||||
pub const GameState = struct {
|
||||
r: u8 = 0xff,
|
||||
@ -21,7 +24,66 @@ pub const GameState = struct {
|
||||
self.slime_factory.deinit();
|
||||
}
|
||||
|
||||
pub fn update_tick(self: *GameState) void {
|
||||
// Physics main loop that processes variable time steps
|
||||
pub fn update_tick(self: *GameState) !void {
|
||||
const bg_texture = try atils.loadTexture(self.renderer, "assets/background.png");
|
||||
defer sdl.SDL_DestroyTexture(bg_texture);
|
||||
|
||||
_ = sdl.SDL_SetRenderDrawColor(self.renderer, 0xff, 0xff, 0xff, 0xff);
|
||||
_ = sdl.SDL_RenderClear(self.renderer);
|
||||
_ = sdl.SDL_RenderCopy(self.renderer, bg_texture, null, null);
|
||||
|
||||
var text = try GameText.loadFromRenderedText("Press S to start / stop", RGBAColor.whiteSmoke().tosdl());
|
||||
try text.render(
|
||||
self,
|
||||
.{ .x = @intCast(self.SCREEN_WIDTH - text.w), .y = 2 },
|
||||
);
|
||||
text = try GameText.loadFromRenderedText("Press P to pause / unpause", RGBAColor.whiteSmoke().tosdl());
|
||||
try text.render(
|
||||
self,
|
||||
.{ .x = @intCast(self.SCREEN_WIDTH - text.w), .y = 24 },
|
||||
);
|
||||
|
||||
// var buf: [16]u8 = undefined;
|
||||
// const timer_ms = timer.getTicks();
|
||||
// // const timer_base = std.math.log10(timer_ms) + 3;
|
||||
// const time_str: [*:0]const u8 = try std.fmt.bufPrintZ(&buf, "{d}ms", .{timer_ms});
|
||||
// var time_text = try GameText.loadFromRenderedText(time_str, RGBAColor.whiteSmoke().tosdl());
|
||||
// try time_text.render(
|
||||
// &game_state,
|
||||
// .{ .x = @intCast(game_state.SCREEN_WIDTH - time_text.w), .y = 64 },
|
||||
// );
|
||||
|
||||
// const fps_ms: f32 = @floatFromInt(fps_timer.getTicks());
|
||||
// const avg_fps: f32 = @round((frames / fps_ms) * 100_000) / 100.0;
|
||||
// var fps_text = try GameText.loadFromRenderedText(try std.fmt.bufPrintZ(&buf, "{d} fps", .{avg_fps}), RGBAColor.whiteSmoke().tosdl());
|
||||
// try fps_text.render(
|
||||
// &game_state,
|
||||
// .{ .x = 5, .y = 5 },
|
||||
// );
|
||||
|
||||
// Render red rect
|
||||
// const fill_rect: sdl.struct_SDL_Rect = sdl.SDL_Rect{ .x = SCREEN_WIDTH / 4, .y = SCREEN_HEIGHT / 4, .w = SCREEN_WIDTH / 2 + img_pos.x, .h = SCREEN_HEIGHT / 2 + img_pos.y };
|
||||
// _ = sdl.SDL_SetRenderDrawColor(self.renderer, 0xff, 0x00, 0x00, 0xff);
|
||||
// _ = sdl.SDL_RenderFillRect(self.renderer, &fill_rect);
|
||||
// _ = sdl.SDL_RenderSetViewport(self.renderer, &.{ .x = SCREEN_WIDTH / 2, .y = SCREEN_HEIGHT / 2, .w = SCREEN_WIDTH, .h = SCREEN_HEIGHT / 2 });
|
||||
// _ = sdl.SDL_RenderCopy(self.renderer, texture, null, null);
|
||||
// frames += 1;
|
||||
// if (frames > 60) {
|
||||
// frames = 1;
|
||||
// fps_timer.reset();
|
||||
// }
|
||||
// var dest_rect = sdl.SDL_Rect{ .x = src_rect.x + img_pos.x, .y = src_rect.y + img_pos.y, .w = SCREEN_WIDTH, .h = SCREEN_HEIGHT };
|
||||
// _ = sdl.SDL_BlitScaled(zig_image, &src_rect, screen_surface, &dest_rect);
|
||||
|
||||
// _ = sdl.SDL_UpdateWindowSurface(window);
|
||||
// sdl.SDL_Delay(100);
|
||||
|
||||
self.slime_factory.render(self);
|
||||
}
|
||||
|
||||
// Renders the current frame to screen
|
||||
pub fn render(self: *GameState) void {
|
||||
_ = sdl.SDL_RenderPresent(self.renderer);
|
||||
}
|
||||
};
|
||||
|
123
src/main.zig
123
src/main.zig
@ -1,10 +1,11 @@
|
||||
const std = @import("std");
|
||||
const sdl = @import("sdl.zig").c;
|
||||
const atils = @import("./asset_utils.zig");
|
||||
const gs = @import("./game_state.zig");
|
||||
const assert = std.debug.assert;
|
||||
const SCREEN_WIDTH = 640;
|
||||
const SCREEN_HEIGHT = 480;
|
||||
const SCREEN_FPS: f64 = 60.0;
|
||||
const SCREEN_TICKS_PER_FRAME: f64 = 1000 / SCREEN_FPS;
|
||||
const log = sdl.SDL_Log;
|
||||
const GameText = @import("./text.zig").GameText;
|
||||
const RGBAColor = @import("./utils/rgb_color.zig").RGBAColor;
|
||||
@ -27,64 +28,29 @@ pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
const bg_texture = try atils.loadTexture(renderer, "assets/background.png");
|
||||
defer sdl.SDL_DestroyTexture(bg_texture);
|
||||
|
||||
try GameText.initFont();
|
||||
defer GameText.deinitFont();
|
||||
|
||||
// var slime_factory = try sf.SlimeFactory.init(allocator, renderer);
|
||||
// defer slime_factory.deinit();
|
||||
// try slime_factory.add(.{});
|
||||
|
||||
var game_state = try gs.GameState.init(allocator, renderer);
|
||||
defer game_state.deinit();
|
||||
|
||||
var quit = false;
|
||||
var shifted = false;
|
||||
var start_time: u32 = 0;
|
||||
var timer = Timer{};
|
||||
var fps_timer = Timer{};
|
||||
fps_timer.start();
|
||||
var frames: f32 = 0;
|
||||
var frame_time = Timer{};
|
||||
var prev_frame_ms: f64 = 16.0;
|
||||
var accumulator: f64 = 0.0;
|
||||
|
||||
while (!quit) {
|
||||
frame_time.reset();
|
||||
var event: sdl.SDL_Event = undefined;
|
||||
while (sdl.SDL_PollEvent(&event) != 0) {
|
||||
switch (event.type) {
|
||||
sdl.SDL_QUIT => {
|
||||
quit = true;
|
||||
},
|
||||
sdl.SDL_KEYUP => {
|
||||
switch (event.key.keysym.sym) {
|
||||
sdl.SDLK_LSHIFT => {
|
||||
shifted = false;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
sdl.SDL_KEYDOWN => {
|
||||
switch (event.key.keysym.sym) {
|
||||
sdl.SDLK_RETURN => {
|
||||
start_time = sdl.SDL_GetTicks();
|
||||
},
|
||||
sdl.SDLK_s => {
|
||||
if (timer.status == .started) timer.stop() else timer.start();
|
||||
},
|
||||
sdl.SDLK_p => {
|
||||
if (timer.status == .paused) timer.unpause() else timer.pause();
|
||||
},
|
||||
// sdl.SDLK_UP => {
|
||||
// img_pos.y = img_pos.y - 5;
|
||||
// },
|
||||
// sdl.SDLK_DOWN => {
|
||||
// img_pos.y = img_pos.y + 5;
|
||||
// },
|
||||
// sdl.SDLK_LEFT => {
|
||||
// img_pos.x = img_pos.x - 5;
|
||||
// },
|
||||
// sdl.SDLK_RIGHT => {
|
||||
// img_pos.x = img_pos.x + 5;
|
||||
// },
|
||||
else => {},
|
||||
}
|
||||
log("Got key event: %i\n", event.key.keysym.sym);
|
||||
@ -92,52 +58,43 @@ pub fn main() !void {
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
_ = sdl.SDL_SetRenderDrawColor(renderer, 0xff, 0xff, 0xff, 0xff);
|
||||
_ = sdl.SDL_RenderClear(renderer);
|
||||
_ = sdl.SDL_RenderCopy(renderer, bg_texture, null, null);
|
||||
game_state.update_tick();
|
||||
var text = try GameText.loadFromRenderedText("Press S to start / stop", RGBAColor.whiteSmoke().tosdl());
|
||||
try text.render(
|
||||
&game_state,
|
||||
.{ .x = @intCast(game_state.SCREEN_WIDTH - text.w), .y = 2 },
|
||||
);
|
||||
text = try GameText.loadFromRenderedText("Press P to pause / unpause", RGBAColor.whiteSmoke().tosdl());
|
||||
try text.render(
|
||||
&game_state,
|
||||
.{ .x = @intCast(game_state.SCREEN_WIDTH - text.w), .y = 24 },
|
||||
);
|
||||
|
||||
var buf: [16]u8 = undefined;
|
||||
const timer_ms = timer.getTicks();
|
||||
// const timer_base = std.math.log10(timer_ms) + 3;
|
||||
const time_str: [*:0]const u8 = try std.fmt.bufPrintZ(&buf, "{d}ms", .{timer_ms});
|
||||
var time_text = try GameText.loadFromRenderedText(time_str, RGBAColor.whiteSmoke().tosdl());
|
||||
try time_text.render(
|
||||
&game_state,
|
||||
.{ .x = @intCast(game_state.SCREEN_WIDTH - time_text.w), .y = 64 },
|
||||
);
|
||||
// double newTime = time();
|
||||
// double frameTime = newTime - currentTime;
|
||||
// if ( frameTime > 0.25 )
|
||||
// frameTime = 0.25;
|
||||
// currentTime = newTime;
|
||||
|
||||
const fps_time: f32 = @floatFromInt(fps_timer.getTicks() / 1000);
|
||||
const avg_fps: f32 = @round((frames / fps_time) * 100) / 100.0;
|
||||
var fps_text = try GameText.loadFromRenderedText(try std.fmt.bufPrintZ(&buf, "{d} fps", .{avg_fps}), RGBAColor.whiteSmoke().tosdl());
|
||||
try fps_text.render(
|
||||
&game_state,
|
||||
.{ .x = 5, .y = 5 },
|
||||
);
|
||||
// accumulator += frameTime;
|
||||
|
||||
// Render red rect
|
||||
// const fill_rect: sdl.struct_SDL_Rect = sdl.SDL_Rect{ .x = SCREEN_WIDTH / 4, .y = SCREEN_HEIGHT / 4, .w = SCREEN_WIDTH / 2 + img_pos.x, .h = SCREEN_HEIGHT / 2 + img_pos.y };
|
||||
// _ = sdl.SDL_SetRenderDrawColor(renderer, 0xff, 0x00, 0x00, 0xff);
|
||||
// _ = sdl.SDL_RenderFillRect(renderer, &fill_rect);
|
||||
// _ = sdl.SDL_RenderSetViewport(renderer, &.{ .x = SCREEN_WIDTH / 2, .y = SCREEN_HEIGHT / 2, .w = SCREEN_WIDTH, .h = SCREEN_HEIGHT / 2 });
|
||||
// _ = sdl.SDL_RenderCopy(renderer, texture, null, null);
|
||||
_ = sdl.SDL_RenderPresent(renderer);
|
||||
frames += 1;
|
||||
// var dest_rect = sdl.SDL_Rect{ .x = src_rect.x + img_pos.x, .y = src_rect.y + img_pos.y, .w = SCREEN_WIDTH, .h = SCREEN_HEIGHT };
|
||||
// _ = sdl.SDL_BlitScaled(zig_image, &src_rect, screen_surface, &dest_rect);
|
||||
// while ( accumulator >= dt )
|
||||
// {
|
||||
// previousState = currentState;
|
||||
// integrate( currentState, t, dt );
|
||||
// t += dt;
|
||||
// accumulator -= dt;
|
||||
// }
|
||||
|
||||
// _ = sdl.SDL_UpdateWindowSurface(window);
|
||||
// sdl.SDL_Delay(100);
|
||||
// const double alpha = accumulator / dt;
|
||||
|
||||
// State state = currentState * alpha +
|
||||
// previousState * ( 1.0 - alpha );
|
||||
|
||||
// render( state );
|
||||
accumulator = accumulator + prev_frame_ms;
|
||||
while (accumulator >= SCREEN_TICKS_PER_FRAME) {
|
||||
try game_state.update_tick();
|
||||
accumulator = accumulator - SCREEN_TICKS_PER_FRAME;
|
||||
std.debug.print("Tick update done. Acc: {d}\n", .{accumulator});
|
||||
}
|
||||
|
||||
game_state.render();
|
||||
|
||||
prev_frame_ms = @floatFromInt(frame_time.getTicks());
|
||||
// const frame_time_ticks = frame_time.getTicks();
|
||||
// if (frame_time_ticks < SCREEN_TICKS_PER_FRAME) {
|
||||
// sdl.SDL_Delay(SCREEN_TICKS_PER_FRAME - frame_time_ticks);
|
||||
// }
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ pub const Timer = struct {
|
||||
self.pause_ticks_ms = 0;
|
||||
}
|
||||
|
||||
// Resets the timer to 0 and starts it
|
||||
pub fn reset(self: *Timer) void {
|
||||
self.status = .started;
|
||||
self.start_ticks_ms = sdl.SDL_GetTicks();
|
||||
|
Loading…
Reference in New Issue
Block a user