diff --git a/assets/png_test.png b/assets/png_test.png new file mode 100644 index 0000000..fb9dfe9 Binary files /dev/null and b/assets/png_test.png differ diff --git a/assets/slime_still.png b/assets/slime_still.png new file mode 100644 index 0000000..8e13f88 Binary files /dev/null and b/assets/slime_still.png differ diff --git a/build.zig b/build.zig index 4aa240b..3aa1e7e 100644 --- a/build.zig +++ b/build.zig @@ -12,14 +12,6 @@ pub fn build(b: *Build) void { .target = target, .optimize = optimize, }); - // Keeping this here just in case, but dynamic linking is working AFAICT - // if (target.query.isNativeOs() and target.result.os.tag == .linux) { - // // The SDL package doesn't work for Linux yet, so we rely on system - // // packages for now. - // exe.linkSystemLibrary("SDL2"); - // exe.linkSystemLibrary("SDL2_ttf"); - // exe.linkSystemLibrary("SDL2_image"); - // } else { const sdl_dep = b.dependency("SDL", .{ .optimize = .ReleaseFast, .target = target, @@ -37,7 +29,6 @@ pub fn build(b: *Build) void { .target = target, }); exe.linkLibrary(sdl_image_dep.artifact("SDL2_image")); - // } b.installArtifact(exe); // Create Check step for zls @@ -47,7 +38,8 @@ pub fn build(b: *Build) void { .target = target, .optimize = optimize, }); - exe_check.linkLibrary(sdl_dep.artifact("SDL2")); + exe_check.root_module.addIncludePath(sdl_lib.getEmittedIncludeTree().path(b, "SDL2")); + exe_check.linkLibrary(sdl_lib); exe_check.linkLibrary(sdl_ttf_dep.artifact("SDL2_ttf")); exe_check.linkLibrary(sdl_image_dep.artifact("SDL2_image")); const check = b.step("check", "Check if project compiles, used by Zig Language Server"); @@ -57,93 +49,3 @@ pub fn build(b: *Build) void { const run_cmd = b.addRunArtifact(exe); run.dependOn(&run_cmd.step); } - -// // Although this function looks imperative, note that its job is to -// // declaratively construct a build graph that will be executed by an external -// // runner. -// pub fn build(b: *std.Build) void { -// // Standard target options allows the person running `zig build` to choose -// // what target to build for. Here we do not override the defaults, which -// // means any target is allowed, and the default is native. Other options -// // for restricting supported target set are available. -// const target = b.standardTargetOptions(.{}); - -// // Standard optimization options allow the person running `zig build` to select -// // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not -// // set a preferred release mode, allowing the user to decide how to optimize. -// const optimize = b.standardOptimizeOption(.{}); - -// const lib = b.addStaticLibrary(.{ -// .name = "zsdl", -// // In this case the main source file is merely a path, however, in more -// // complicated build scripts, this could be a generated file. -// .root_source_file = b.path("src/root.zig"), -// .target = target, -// .optimize = optimize, -// }); - -// // This declares intent for the library to be installed into the standard -// // location when the user invokes the "install" step (the default step when -// // running `zig build`). -// b.installArtifact(lib); - -// const exe = b.addExecutable(.{ -// .name = "zsdl", -// .root_source_file = b.path("src/main.zig"), -// .target = target, -// .optimize = optimize, -// }); - -// // This declares intent for the executable to be installed into the -// // standard location when the user invokes the "install" step (the default -// // step when running `zig build`). -// b.installArtifact(exe); - -// // This *creates* a Run step in the build graph, to be executed when another -// // step is evaluated that depends on it. The next line below will establish -// // such a dependency. -// const run_cmd = b.addRunArtifact(exe); - -// // By making the run step depend on the install step, it will be run from the -// // installation directory rather than directly from within the cache directory. -// // This is not necessary, however, if the application depends on other installed -// // files, this ensures they will be present and in the expected location. -// run_cmd.step.dependOn(b.getInstallStep()); - -// // This allows the user to pass arguments to the application in the build -// // command itself, like this: `zig build run -- arg1 arg2 etc` -// if (b.args) |args| { -// run_cmd.addArgs(args); -// } - -// // This creates a build step. It will be visible in the `zig build --help` menu, -// // and can be selected like this: `zig build run` -// // This will evaluate the `run` step rather than the default, which is "install". -// const run_step = b.step("run", "Run the app"); -// run_step.dependOn(&run_cmd.step); - -// // Creates a step for unit testing. This only builds the test executable -// // but does not run it. -// const lib_unit_tests = b.addTest(.{ -// .root_source_file = b.path("src/root.zig"), -// .target = target, -// .optimize = optimize, -// }); - -// const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); - -// const exe_unit_tests = b.addTest(.{ -// .root_source_file = b.path("src/main.zig"), -// .target = target, -// .optimize = optimize, -// }); - -// const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); - -// // Similar to creating the run step earlier, this exposes a `test` step to -// // the `zig build --help` menu, providing a way for the user to request -// // running the unit tests. -// const test_step = b.step("test", "Run unit tests"); -// test_step.dependOn(&run_lib_unit_tests.step); -// test_step.dependOn(&run_exe_unit_tests.step); -// } diff --git a/src/asset_utils.zig b/src/asset_utils.zig new file mode 100644 index 0000000..e67998d --- /dev/null +++ b/src/asset_utils.zig @@ -0,0 +1,28 @@ +const sdl = @import("./sdl.zig").c; +const log = sdl.SDL_Log; + +pub fn loadTexture(renderer: *sdl.struct_SDL_Renderer, path: [*c]const u8) !*sdl.struct_SDL_Texture { + const new_texture = sdl.IMG_LoadTexture(renderer, path) orelse { + log("Unable to load texture from surface %s\n", sdl.SDL_GetError()); + return error.SDLLoadError; + }; + + return new_texture; +} + +// fn loadSurface(path: [*c]const u8) !*sdl.struct_SDL_Surface { +// // TODO check file exists +// const loaded_surface: [*c]sdl.struct_SDL_Surface = sdl.IMG_Load(path); +// if (loaded_surface == null) { +// log("Unable to load media at path %s: %s\n", path, sdl.IMG_GetError()); +// return error.SDLLoadError; +// } +// defer sdl.SDL_FreeSurface(loaded_surface); +// // Converting 24bit bmp to 32bit to match display +// const optimized_surface = sdl.SDL_ConvertSurface(loaded_surface, screen_surface.format, 0); +// if (optimized_surface == null) { +// log("Unable to optimize media at path %s: %s\n", path, sdl.SDL_GetError()); +// return error.SDLLoadError; +// } +// return optimized_surface; +// } diff --git a/src/game_state.zig b/src/game_state.zig new file mode 100644 index 0000000..bcadd37 --- /dev/null +++ b/src/game_state.zig @@ -0,0 +1,25 @@ +const sdl = @import("./sdl.zig").c; +const std = @import("std"); +const sf = @import("./slime_factory.zig"); + +pub const GameState = struct { + r: u8 = 0xff, + g: u8 = 0xff, + b: u8 = 0xff, + slime_factory: sf.SlimeFactory = undefined, + renderer: *sdl.struct_SDL_Renderer = undefined, + + pub fn init(allocator: std.mem.Allocator, renderer: *sdl.struct_SDL_Renderer) !GameState { + var slime_factory = try sf.SlimeFactory.init(allocator, renderer); + try slime_factory.add(.{}); + return .{ .slime_factory = slime_factory, .renderer = renderer }; + } + + pub fn deinit(self: *GameState) void { + self.slime_factory.deinit(); + } + + pub fn update_tick(self: *GameState) void { + self.slime_factory.render(self); + } +}; diff --git a/src/main.zig b/src/main.zig index bd62bd5..543f8c2 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,16 +1,16 @@ -const c = @cImport({ - @cInclude("SDL2/SDL.h"); - @cInclude("SDL2/SDL_image.h"); -}); - 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 log = c.SDL_Log; +const log = sdl.SDL_Log; + +var window: *sdl.struct_SDL_Window = undefined; +// var screen_surface: *sdl.struct_SDL_Surface = undefined; +var renderer: *sdl.struct_SDL_Renderer = undefined; -var window: *c.struct_SDL_Window = undefined; -var screen_surface: *c.struct_SDL_Surface = undefined; const Offset = struct { x: i32, y: i32, @@ -18,36 +18,65 @@ const Offset = struct { var img_pos: Offset = .{ .x = 0, .y = 0 }; pub fn main() !void { + errdefer |err| if (err == error.SdlError) std.log.err("SDL error: {s}", .{sdl.SDL_GetError()}); try init(); defer close(); + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); - const zig_image = try loadSurface("assets/stretch.bmp"); - defer c.SDL_FreeSurface(zig_image); + const texture = try atils.loadTexture(renderer, "assets/png_test.png"); + defer sdl.SDL_DestroyTexture(texture); - var src_rect: c.struct_SDL_Rect = c.SDL_Rect{ .x = 0, .y = 0, .w = SCREEN_WIDTH / 2, .h = SCREEN_HEIGHT / 2 }; + // 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; while (!quit) { - var event: c.SDL_Event = undefined; - while (c.SDL_PollEvent(&event) != 0) { + var event: sdl.SDL_Event = undefined; + while (sdl.SDL_PollEvent(&event) != 0) { switch (event.type) { - c.SDL_QUIT => { + sdl.SDL_QUIT => { quit = true; }, - c.SDL_KEYDOWN => { + sdl.SDL_KEYUP => { switch (event.key.keysym.sym) { - c.SDLK_UP => { - img_pos.y = img_pos.y - 5; + sdl.SDLK_LSHIFT => { + shifted = false; }, - c.SDLK_DOWN => { - img_pos.y = img_pos.y + 5; + else => {}, + } + }, + sdl.SDL_KEYDOWN => { + switch (event.key.keysym.sym) { + sdl.SDLK_r => { + game_state.r = if (shifted) game_state.r - 5 else game_state.r + 5; }, - c.SDLK_LEFT => { - img_pos.x = img_pos.x - 5; + sdl.SDLK_g => { + game_state.g = if (shifted) game_state.g - 5 else game_state.g + 5; }, - c.SDLK_RIGHT => { - img_pos.x = img_pos.x + 5; + sdl.SDLK_b => { + game_state.b = if (shifted) game_state.b - 5 else game_state.b + 5; }, + sdl.SDLK_LSHIFT => { + shifted = true; + }, + // 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); @@ -55,57 +84,65 @@ pub fn main() !void { else => {}, } } - _ = c.SDL_FillRect(screen_surface, null, c.SDL_MapRGB(screen_surface.format, 0xff, 0xff, 0xff)); - var dest_rect = c.SDL_Rect{ .x = src_rect.x + img_pos.x, .y = src_rect.y + img_pos.y, .w = SCREEN_WIDTH, .h = SCREEN_HEIGHT }; - _ = c.SDL_BlitScaled(zig_image, &src_rect, screen_surface, &dest_rect); + _ = sdl.SDL_SetRenderDrawColor(renderer, 0xff, 0xff, 0xff, 0xff); + _ = sdl.SDL_RenderClear(renderer); + std.debug.print("r:{x} g:{x} b:{x}\n", .{ + game_state.r, + game_state.g, + game_state.b, + }); - _ = c.SDL_UpdateWindowSurface(window); - c.SDL_Delay(17); + game_state.update_tick(); + + // 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); + // 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(117); } return; } fn init() !void { // Init SDL with video subsystem flag - if (c.SDL_Init(c.SDL_INIT_VIDEO) < 0) { - log("Unable to initialize SDL: %s\n", c.SDL_GetError()); + if (sdl.SDL_Init(sdl.SDL_INIT_VIDEO) < 0) { + log("Unable to initialize SDL: %s\n", sdl.SDL_GetError()); return error.SDLInitializationFailed; } - const opt_window = c.SDL_CreateWindow("Game Window", c.SDL_WINDOWPOS_UNDEFINED, c.SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, c.SDL_WINDOW_SHOWN); + const opt_window = sdl.SDL_CreateWindow("Game Window", sdl.SDL_WINDOWPOS_UNDEFINED, sdl.SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, sdl.SDL_WINDOW_SHOWN); if (opt_window == null) { - log("Unable to initialize SDL window: %s\n", c.SDL_GetError()); + log("Unable to initialize SDL window: %s\n", sdl.SDL_GetError()); return error.SDLInitializationFailed; } window = opt_window.?; - screen_surface = c.SDL_GetWindowSurface(window); + renderer = sdl.SDL_CreateRenderer(window, -1, sdl.SDL_RENDERER_ACCELERATED) orelse { + log("Unable to initialize SDL window: %s\n", sdl.SDL_GetError()); + return error.SDLInitializationFailed; + }; + _ = sdl.SDL_SetRenderDrawColor(renderer, 0xff, 0xff, 0xff, 0xff); - const img_flags = c.IMG_INIT_PNG; - if (c.IMG_Init(img_flags) != img_flags) { - log("Unable to initialize SDL Image: %s\n", c.IMG_GetError()); + // screen_surface = sdl.SDL_GetWindowSurface(window); + + const img_flags = sdl.IMG_INIT_PNG; + if (sdl.IMG_Init(img_flags) != img_flags) { + // NOTE remember that errors with SDL_Image will be in IMG_GetError() not SDL_GetError() + log("Unable to initialize SDL Image: %s\n", sdl.IMG_GetError()); return error.SDLInitializationFailed; } } -fn loadSurface(path: [*c]const u8) !*c.struct_SDL_Surface { - // TODO check file exists - const loaded_surface: [*c]c.struct_SDL_Surface = c.SDL_LoadBMP(path); - if (loaded_surface == null) { - log("Unable to load media at path %s: %s\n", path, c.SDL_GetError()); - return error.SDLLoadError; - } - defer c.SDL_FreeSurface(loaded_surface); - // Converting 24bit bmp to 32bit to match display - const optimized_surface = c.SDL_ConvertSurface(loaded_surface, screen_surface.format, 0); - if (optimized_surface == null) { - log("Unable to optimize media at path %s: %s\n", path, c.SDL_GetError()); - return error.SDLLoadError; - } - return optimized_surface; -} - fn close() void { - c.SDL_DestroyWindow(window); - c.SDL_Quit(); + sdl.SDL_DestroyRenderer(renderer); + sdl.SDL_DestroyWindow(window); + sdl.IMG_Quit(); + sdl.SDL_Quit(); } diff --git a/src/sdl.zig b/src/sdl.zig new file mode 100644 index 0000000..aa3f9e5 --- /dev/null +++ b/src/sdl.zig @@ -0,0 +1,4 @@ +pub const c = @cImport({ + @cInclude("SDL2/SDL.h"); + @cInclude("SDL2/SDL_image.h"); +}); diff --git a/src/slime_factory.zig b/src/slime_factory.zig new file mode 100644 index 0000000..90b0f94 --- /dev/null +++ b/src/slime_factory.zig @@ -0,0 +1,46 @@ +const std = @import("std"); +const sdl = @import("sdl.zig").c; +const atils = @import("./asset_utils.zig"); +const GameState = @import("./game_state.zig").GameState; +const slime_sprite_sheet_texture_path = "assets/slime_still.png"; + +pub const Slime = struct { + total_frames: u8 = 6, + x: i32 = 64, + y: i32 = 64, + f: u16 = 0, +}; + +pub const SlimeFactory = struct { + slimes: std.ArrayList(Slime) = undefined, + slime_sprite_sheet_texture: *sdl.struct_SDL_Texture = undefined, + + pub fn init(allocator: std.mem.Allocator, renderer: *sdl.struct_SDL_Renderer) !SlimeFactory { + const texture = try atils.loadTexture(renderer, slime_sprite_sheet_texture_path); + return .{ + .slimes = std.ArrayList(Slime).init(allocator), + .slime_sprite_sheet_texture = texture, + }; + } + + pub fn deinit(sf: *SlimeFactory) void { + sdl.SDL_DestroyTexture(sf.slime_sprite_sheet_texture); + sf.slimes.deinit(); + } + + pub fn add(sf: *SlimeFactory, slime: Slime) !void { + try sf.slimes.append(slime); + } + + pub fn render(sf: *SlimeFactory, game_state: *GameState) void { + _ = sdl.SDL_SetTextureColorMod(sf.slime_sprite_sheet_texture, game_state.r, game_state.g, game_state.b); + for (sf.slimes.items, 0..) |slime, s_idx| { + std.debug.print("Slime {d}:\t{}\n", .{ s_idx + 1, slime }); + const sprite_frame_x_dim: u16 = slime.f * 64; + const sprite_frame_rect: sdl.struct_SDL_Rect = .{ .x = sprite_frame_x_dim, .y = 0, .w = 64, .h = 64 }; + const dest_rect: sdl.struct_SDL_Rect = .{ .x = slime.x, .y = slime.y, .w = 128, .h = 128 }; + _ = sdl.SDL_RenderCopy(game_state.renderer, sf.slime_sprite_sheet_texture, &sprite_frame_rect, &dest_rect); + sf.slimes.items[s_idx].f = (sf.slimes.items[s_idx].f + 1) % sf.slimes.items[s_idx].total_frames; + } + } +};