diff --git a/assets/background.png b/assets/background.png new file mode 100644 index 0000000..84999cc Binary files /dev/null and b/assets/background.png differ diff --git a/assets/fonts/DepartureMonoNF-Regular.ttf b/assets/fonts/DepartureMonoNF-Regular.ttf new file mode 100644 index 0000000..b1d8424 Binary files /dev/null and b/assets/fonts/DepartureMonoNF-Regular.ttf differ diff --git a/assets/fonts/DepartureMonoNerdFont-Regular.otf b/assets/fonts/DepartureMonoNerdFont-Regular.otf new file mode 100644 index 0000000..4097cb3 Binary files /dev/null and b/assets/fonts/DepartureMonoNerdFont-Regular.otf differ diff --git a/assets/fonts/DepartureMonoNerdFontMono-Regular.otf b/assets/fonts/DepartureMonoNerdFontMono-Regular.otf new file mode 100644 index 0000000..e950e8e Binary files /dev/null and b/assets/fonts/DepartureMonoNerdFontMono-Regular.otf differ diff --git a/assets/fonts/DepartureMonoNerdFontPropo-Regular.otf b/assets/fonts/DepartureMonoNerdFontPropo-Regular.otf new file mode 100644 index 0000000..656a96c Binary files /dev/null and b/assets/fonts/DepartureMonoNerdFontPropo-Regular.otf differ diff --git a/assets/fonts/LICENSE b/assets/fonts/LICENSE new file mode 100644 index 0000000..de52476 --- /dev/null +++ b/assets/fonts/LICENSE @@ -0,0 +1,93 @@ +Copyright 2022–2024 Helena Zhang (helenazhang.com). + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/assets/fonts/README.md b/assets/fonts/README.md new file mode 100644 index 0000000..d88fed2 --- /dev/null +++ b/assets/fonts/README.md @@ -0,0 +1,48 @@ +# Nerd Fonts + +This is an archived font from the Nerd Fonts release v3.3.0. + +For more information see: +* https://github.com/ryanoasis/nerd-fonts/ +* https://github.com/ryanoasis/nerd-fonts/releases/latest/ + +# Departure Mono + +**Departure Mono** a monospaced pixel font with a lo-fi, techy vibe. + +For more information have a look at the upstream website: https://github.com/rektdeckard/departure-mono + +Version: v1.422 + +## Which font? + +### TL;DR + +* Pick your font family: + * If you are limited to monospaced fonts (because of your terminal, etc) then pick a font with `Nerd Font Mono` (or `NFM`). + * If you want to have bigger icons (usually around 1.5 normal letters wide) pick a font without `Mono` i.e. `Nerd Font` (or `NF`). Most terminals support this, but ymmv. + * If you work in a proportional context (GUI elements or edit a presentation etc) pick a font with `Nerd Font Propo` (or `NFP`). + +### Ligatures + +Ligatures are generally preserved in the patched fonts. +Nerd Fonts `v2.0.0` had no ligatures in the `Nerd Font Mono` fonts, this has been dropped with `v2.1.0`. +If you have a ligature-aware terminal and don't want ligatures you can (usually) disable them in the terminal settings. + +### Explanation + +Once you narrow down your font choice of family (`Droid Sans`, `Inconsolata`, etc) and style (`bold`, `italic`, etc) you have 2 main choices: + +#### `Option 1: Download already patched font` + + * For a stable version download a font package from the [release page](https://github.com/ryanoasis/nerd-fonts/releases) + * Direct links for [DepartureMono.zip](https://github.com/ryanoasis/nerd-fonts/releases/latest/download/DepartureMono.zip) or [DepartureMono.tar.xz](https://github.com/ryanoasis/nerd-fonts/releases/latest/download/DepartureMono.tar.xz) + +#### `Option 2: Patch your own font` + + * Patch your own variations with the various options provided by the font patcher (i.e. not include all symbols for smaller font size) + +For more information see: [The FAQ](https://github.com/ryanoasis/nerd-fonts/wiki/FAQ-and-Troubleshooting#which-font) + +[SIL-RFN]:http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web_fonts_and_RFNs#14cbfd4a + diff --git a/src/game_state.zig b/src/game_state.zig index bcadd37..0582f4a 100644 --- a/src/game_state.zig +++ b/src/game_state.zig @@ -6,6 +6,8 @@ pub const GameState = struct { r: u8 = 0xff, g: u8 = 0xff, b: u8 = 0xff, + SCREEN_WIDTH: u32 = 640, + SCREEN_HEIGHT: u32 = 480, slime_factory: sf.SlimeFactory = undefined, renderer: *sdl.struct_SDL_Renderer = undefined, diff --git a/src/main.zig b/src/main.zig index 543f8c2..ff71409 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6,6 +6,8 @@ const assert = std.debug.assert; const SCREEN_WIDTH = 640; const SCREEN_HEIGHT = 480; const log = sdl.SDL_Log; +const GameText = @import("./text.zig").GameText; +const RGBAColor = @import("./utils/rgb_color.zig").RGBAColor; var window: *sdl.struct_SDL_Window = undefined; // var screen_surface: *sdl.struct_SDL_Surface = undefined; @@ -24,8 +26,11 @@ pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); - const texture = try atils.loadTexture(renderer, "assets/png_test.png"); - defer sdl.SDL_DestroyTexture(texture); + 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(); @@ -86,13 +91,10 @@ pub fn main() !void { } _ = 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, - }); - + _ = sdl.SDL_RenderCopy(renderer, bg_texture, null, null); game_state.update_tick(); + var text = try GameText.loadFromRenderedText("Test 123", RGBAColor.whiteSmoke().tosdl()); + try text.render(&game_state); // 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 }; @@ -105,7 +107,7 @@ pub fn main() !void { // _ = sdl.SDL_BlitScaled(zig_image, &src_rect, screen_surface, &dest_rect); // _ = sdl.SDL_UpdateWindowSurface(window); - sdl.SDL_Delay(117); + sdl.SDL_Delay(100); } return; } @@ -138,11 +140,17 @@ fn init() !void { log("Unable to initialize SDL Image: %s\n", sdl.IMG_GetError()); return error.SDLInitializationFailed; } + + if (sdl.TTF_Init() < 0) { + log("Unable to initialize SDL TTF: %s\n", sdl.TTF_GetError()); + return error.SDLInitializationFailed; + } } fn close() void { sdl.SDL_DestroyRenderer(renderer); sdl.SDL_DestroyWindow(window); sdl.IMG_Quit(); + sdl.TTF_Quit(); sdl.SDL_Quit(); } diff --git a/src/sdl.zig b/src/sdl.zig index aa3f9e5..2c7f306 100644 --- a/src/sdl.zig +++ b/src/sdl.zig @@ -1,4 +1,5 @@ pub const c = @cImport({ @cInclude("SDL2/SDL.h"); @cInclude("SDL2/SDL_image.h"); + @cInclude("SDL2/SDL_ttf.h"); }); diff --git a/src/slime_factory.zig b/src/slime_factory.zig index 90b0f94..6f2df38 100644 --- a/src/slime_factory.zig +++ b/src/slime_factory.zig @@ -3,12 +3,19 @@ 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"; +const rgba = @import("./utils/rgb_color.zig").RGBAColor; pub const Slime = struct { total_frames: u8 = 6, x: i32 = 64, - y: i32 = 64, + y: i32 = 300, f: u16 = 0, + color: rgba = .{ + .r = 0xd5, + .g = 0x0f, + .b = 0x65, + .a = 110, + }, }; pub const SlimeFactory = struct { @@ -17,6 +24,11 @@ pub const SlimeFactory = struct { pub fn init(allocator: std.mem.Allocator, renderer: *sdl.struct_SDL_Renderer) !SlimeFactory { const texture = try atils.loadTexture(renderer, slime_sprite_sheet_texture_path); + const val = sdl.SDL_SetTextureBlendMode(texture, sdl.SDL_BLENDMODE_BLEND); + if (val != 0) { + sdl.SDL_Log("Failed to set blend mode: %s", sdl.SDL_GetError()); + return error.FailedToSetBlendMode; + } return .{ .slimes = std.ArrayList(Slime).init(allocator), .slime_sprite_sheet_texture = texture, @@ -33,12 +45,21 @@ pub const SlimeFactory = struct { } 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); + if (sf.slimes.items.len == 0) return; + + const slime_color = sf.slimes.items[0].color; + + _ = sdl.SDL_SetTextureColorMod( + sf.slime_sprite_sheet_texture, + slime_color.r, + slime_color.g, + slime_color.b, + ); + _ = sdl.SDL_SetTextureAlphaMod(sf.slime_sprite_sheet_texture, slime_color.a); 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 }; + const dest_rect: sdl.struct_SDL_Rect = .{ .x = slime.x, .y = slime.y, .w = 64, .h = 64 }; _ = 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; } diff --git a/src/text.zig b/src/text.zig new file mode 100644 index 0000000..a8ee724 --- /dev/null +++ b/src/text.zig @@ -0,0 +1,84 @@ +const sdl = @import("./sdl.zig").c; +const std = @import("std"); +const GameState = @import("./game_state.zig").GameState; + +var font: ?*sdl.TTF_Font = null; +var text_texture: ?*sdl.SDL_Texture = null; +var text_init = false; + +pub const GameText = struct { + text: []const u8, + color: sdl.SDL_Color, + surface: *sdl.SDL_Surface, + w: u32, + h: u32, + + pub fn loadFromRenderedText(text: []const u8, color: sdl.SDL_Color) !GameText { + const c_text: [*c]const u8 = @ptrCast(text); + const text_surface: [*c]sdl.SDL_Surface = sdl.TTF_RenderText_Solid(font.?, c_text, color) orelse { + sdl.SDL_Log("Error loading text surface: %s\n", sdl.SDL_GetError()); + return error.FailedToRenderSurface; + }; + + return .{ + .surface = text_surface, + .text = text, + .color = color, + .w = @intCast(text_surface.*.w), + .h = @intCast(text_surface.*.h), + }; + } + + pub fn deinit(self: *GameText) void { + sdl.SDL_FreeSurface(self.text_surface); + } + + pub fn setBlendMode(self: *GameText, blend: sdl.SDL_BlendMode) !void { + _ = self; + _ = blend; + return error.Unimplemented; + } + + pub fn setAlpha(self: *GameText, alpha: u8) !void { + _ = self; + _ = alpha; + return error.Unimplemented; + } + + pub fn render(self: *GameText, game_state: *GameState) !void { + if (!text_init) { + return error.TextNotInitialized; + } + + if (text_texture != null) { + sdl.SDL_DestroyTexture(text_texture); + text_texture = null; + } + + // TODO dont create new texture if unchanged + text_texture = sdl.SDL_CreateTextureFromSurface(game_state.renderer, self.surface) orelse { + sdl.SDL_Log("Error loading text texture: %s\n", sdl.SDL_GetError()); + return error.FailedToRenderTexture; + }; + + const srcr = sdl.SDL_Rect{ .x = 0, .y = 0, .w = @intCast(self.w), .h = @intCast(self.h) }; + const destr = sdl.SDL_Rect{ .x = @intCast(game_state.SCREEN_WIDTH - self.w), .y = 10, .w = @intCast(self.w), .h = @intCast(self.h) }; + _ = sdl.SDL_RenderCopy(game_state.renderer, text_texture, &srcr, &destr); + } + + pub fn initFont() !void { + const loaded_font = sdl.TTF_OpenFont("./assets/fonts/DepartureMonoNF-Regular.ttf", 24) orelse { + sdl.SDL_Log("Error loading ttf font: %s\n", sdl.TTF_GetError()); + return error.FailedToLoadFont; + }; + + font = loaded_font; + text_init = true; + } + + pub fn deinitFont() void { + sdl.SDL_DestroyTexture(text_texture); + sdl.TTF_CloseFont(font); + text_init = false; + } +}; diff --git a/src/utils/rgb_color.zig b/src/utils/rgb_color.zig new file mode 100644 index 0000000..c4c598d --- /dev/null +++ b/src/utils/rgb_color.zig @@ -0,0 +1,116 @@ +const sdl = @import("../sdl.zig").c; + +pub const RGBAColor = struct { + r: u8, + g: u8, + b: u8, + a: u8, + + pub fn white() RGBAColor { + return .{ + .r = 0xff, + .g = 0xff, + .b = 0xff, + .a = 0xff, + }; + } + + pub fn black() RGBAColor { + return .{ + .r = 0x00, + .g = 0x00, + .b = 0x00, + .a = 0xff, + }; + } + + pub fn red() RGBAColor { + return .{ + .r = 0xff, + .g = 0x00, + .b = 0x00, + .a = 0xff, + }; + } + + pub fn green() RGBAColor { + return .{ + .r = 0x00, + .g = 0xff, + .b = 0x00, + .a = 0xff, + }; + } + + pub fn blue() RGBAColor { + return .{ + .r = 0x00, + .g = 0x00, + .b = 0xff, + .a = 0xff, + }; + } + + pub fn yellow() RGBAColor { + return .{ + .r = 0xff, + .g = 0xff, + .b = 0x00, + .a = 0xff, + }; + } + + pub fn cyan() RGBAColor { + return .{ + .r = 0x00, + .g = 0xff, + .b = 0xff, + .a = 0xff, + }; + } + + pub fn magenta() RGBAColor { + return .{ + .r = 0xff, + .g = 0x00, + .b = 0xff, + .a = 0xff, + }; + } + + pub fn whiteSmoke() RGBAColor { + return .{ + .r = 0xf5, + .g = 0xf5, + .b = 0xf5, + .a = 0xff, + }; + } + + pub fn orange() RGBAColor { + return .{ + .r = 0xff, + .g = 0xa5, + .b = 0x00, + .a = 0xff, + }; + } + + pub fn pink() RGBAColor { + return .{ + .r = 0xff, + .g = 0xc0, + .b = 0xcb, + .a = 0xff, + }; + } + + pub fn tosdl(self: *const RGBAColor) sdl.SDL_Color { + return .{ + .r = self.r, + .g = self.g, + .b = self.b, + .a = self.a, + }; + } +};