Progress over >1 day.
- Added debug overlay. - Added game scene. * Rendering of background, tiles, items & player. * Added levels (reading from text file). * No collision detection yet. - Automatic resizing of fonts. - Added sprites (animate textures). - Lots of utility methods everywhere....
This commit is contained in:
parent
0fb3650a9a
commit
7a89b05f8e
@ -9,26 +9,34 @@ pub const Bitmap = struct {
|
||||
destroyBitmap(self);
|
||||
}
|
||||
|
||||
pub fn draw(self: Bitmap, x: f32, y: f32, flags: DrawFlags) void {
|
||||
drawBitmap(self, x, y, flags);
|
||||
pub fn draw(self: Bitmap, x: f32, y: f32) void {
|
||||
drawBitmap(self, x, y, DrawFlags{});
|
||||
}
|
||||
|
||||
pub fn drawCentered(self: Bitmap, x: f32, y: f32) void {
|
||||
self.draw(x - 0.5 * @intToFloat(f32, self.width()), y - 0.5 * @intToFloat(f32, self.height()), DrawFlags{});
|
||||
}
|
||||
|
||||
pub fn drawCenteredScaled(self: Bitmap, x: f32, y: f32, s: f32) void {
|
||||
pub fn drawCenteredScaledUniform(self: Bitmap, x: f32, y: f32, s: f32) void {
|
||||
const w = s * @intToFloat(f32, self.width());
|
||||
const h = s * @intToFloat(f32, self.height());
|
||||
self.drawScaled(x - 0.5 * w, y - 0.5 * h, w, h, DrawFlags{});
|
||||
self.drawScaled(x - 0.5 * w, y - 0.5 * h, w, h);
|
||||
}
|
||||
|
||||
pub fn drawScaled(self: Bitmap, x: f32, y: f32, w: f32, h: f32, flags: DrawFlags) void {
|
||||
drawScaledBitmap(self, 0, 0, @intToFloat(f32, self.width()), @intToFloat(f32, self.height()), x, y, w, h, flags);
|
||||
pub fn drawScaled(self: Bitmap, x: f32, y: f32, w: f32, h: f32) void {
|
||||
drawScaledBitmap(self, 0, 0, @intToFloat(f32, self.width()), @intToFloat(f32, self.height()), x, y, w, h, DrawFlags{});
|
||||
}
|
||||
|
||||
pub fn drawTinted(self: Bitmap, tint: Color, x: f32, y: f32, flags: DrawFlags) void {
|
||||
drawTintedBitmap(self, tint, x, y, flags);
|
||||
pub fn drawScaledUniform(self: Bitmap, x: f32, y: f32, s: f32) void {
|
||||
const sourceW = @intToFloat(f32, self.width());
|
||||
const sourceH = @intToFloat(f32, self.height());
|
||||
const scaledW = s * sourceW;
|
||||
const scaledH = s * sourceH;
|
||||
drawScaledBitmap(self, 0, 0, sourceW, sourceH, x, y, scaledW, scaledH, DrawFlags{});
|
||||
}
|
||||
|
||||
pub fn drawTinted(self: Bitmap, tint: Color, x: f32, y: f32) void {
|
||||
drawTintedBitmap(self, tint, x, y, DrawFlags{});
|
||||
}
|
||||
|
||||
pub fn drawTintedCentered(self: Bitmap, tint: Color, x: f32, y: f32) void {
|
||||
@ -38,17 +46,21 @@ pub const Bitmap = struct {
|
||||
pub fn drawTintedCenteredScaled(self: Bitmap, tint: Color, x: f32, y: f32, s: f32) void {
|
||||
const w = s * @intToFloat(f32, self.width());
|
||||
const h = s * @intToFloat(f32, self.height());
|
||||
self.drawTintedScaled(tint, x - 0.5 * w, y - 0.5 * h, w, h, DrawFlags{});
|
||||
self.drawTintedScaled(tint, x - 0.5 * w, y - 0.5 * h, w, h);
|
||||
}
|
||||
|
||||
pub fn drawTintedScaled(self: Bitmap, tint: Color, x: f32, y: f32, w: f32, h: f32, flags: DrawFlags) void {
|
||||
drawTintedScaledBitmap(self, tint, 0, 0, @intToFloat(f32, self.width()), @intToFloat(f32, self.height()), x, y, w, h, flags);
|
||||
pub fn drawTintedScaled(self: Bitmap, tint: Color, x: f32, y: f32, w: f32, h: f32) void {
|
||||
drawTintedScaledBitmap(self, tint, 0, 0, @intToFloat(f32, self.width()), @intToFloat(f32, self.height()), x, y, w, h, DrawFlags{});
|
||||
}
|
||||
|
||||
pub fn height(self: Bitmap) i32 {
|
||||
return getBitmapHeight(self);
|
||||
}
|
||||
|
||||
pub fn sub(self: Bitmap, x: i32, y: i32, w: i32, h: i32) !Bitmap {
|
||||
return createSubBitmap(self, x, y, w, h);
|
||||
}
|
||||
|
||||
pub fn width(self: Bitmap) i32 {
|
||||
return getBitmapWidth(self);
|
||||
}
|
||||
@ -396,6 +408,14 @@ pub fn createEventQueue() !EventQueue {
|
||||
return error.FailedToCreateEventQueue;
|
||||
}
|
||||
|
||||
pub fn createSubBitmap(bitmap: Bitmap, x: i32, y: i32, width: i32, height: i32) !Bitmap {
|
||||
const sub = c.al_create_sub_bitmap(bitmap.native, x, y, width, height);
|
||||
if (sub) |native| {
|
||||
return Bitmap{ .native = native };
|
||||
}
|
||||
return error.FailedToCreateSubBitmap;
|
||||
}
|
||||
|
||||
pub fn createTimer(interval: f32) !Timer {
|
||||
const timer = c.al_create_timer(interval);
|
||||
if (timer) |native| {
|
||||
@ -652,6 +672,14 @@ pub fn registerEventSource(queue: EventQueue, source: EventSource) void {
|
||||
c.al_register_event_source(queue.native, source.native);
|
||||
}
|
||||
|
||||
pub fn saveBitmap(path: [*:0]const u8, bitmap: Bitmap) bool {
|
||||
return c.al_save_bitmap(path, bitmap.native);
|
||||
}
|
||||
|
||||
pub fn saveBitmapF(file: File, ident: []const u8, bitmap: Bitmap) bool {
|
||||
return c.al_save_bitmap_f(file.native, &ident[0], bitmap.native);
|
||||
}
|
||||
|
||||
pub fn setDisplayFlag(display: Display, flag: NewDisplayFlags, on: bool) bool {
|
||||
return c.al_set_display_flag(display.native, @bitCast(c_int, flag), on);
|
||||
}
|
||||
|
91
src/assets/fonts/Pixellari-OFL.txt
Normal file
91
src/assets/fonts/Pixellari-OFL.txt
Normal file
@ -0,0 +1,91 @@
|
||||
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.
|
BIN
src/assets/fonts/Pixellari.ttf
Normal file
BIN
src/assets/fonts/Pixellari.ttf
Normal file
Binary file not shown.
BIN
src/assets/images/background_jungle.png
Normal file
BIN
src/assets/images/background_jungle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 123 KiB |
BIN
src/assets/images/character_lion_48.png
Normal file
BIN
src/assets/images/character_lion_48.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.9 KiB |
BIN
src/assets/images/item_star_32.png
Normal file
BIN
src/assets/images/item_star_32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
BIN
src/assets/images/text_balloons.png
Normal file
BIN
src/assets/images/text_balloons.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
BIN
src/assets/images/tiles_dirt_32.png
Normal file
BIN
src/assets/images/tiles_dirt_32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
BIN
src/assets/images/tiles_grass_32.png
Normal file
BIN
src/assets/images/tiles_grass_32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
21
src/assets/levels/level1.txt
Normal file
21
src/assets/levels/level1.txt
Normal file
@ -0,0 +1,21 @@
|
||||
x x
|
||||
x x
|
||||
x x
|
||||
x S S x
|
||||
x xxxxxx x
|
||||
x x
|
||||
x x
|
||||
x x
|
||||
x xxxxxx x
|
||||
x x
|
||||
x x
|
||||
x xxxxxx x
|
||||
x x
|
||||
x x
|
||||
x x
|
||||
xxxxxxxxxx x
|
||||
x SSSS x
|
||||
x SSSSS x
|
||||
x P S S S S x
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
|
@ -1,26 +1,27 @@
|
||||
const std = @import("std");
|
||||
const allegro = @import("allegro");
|
||||
const engine = @import("engine.zig");
|
||||
const paths = @import("paths.zig");
|
||||
const Palette = @import("palette.zig").Palette;
|
||||
const Scene = @import("scene.zig").Scene;
|
||||
const Renderer = @import("renderer.zig").Renderer;
|
||||
|
||||
pub const Context = struct {
|
||||
const DefaultDisplayWidth = 1280;
|
||||
const DefaultDisplayHeight = 720;
|
||||
const DefaultDisplayWidth = 960;
|
||||
const DefaultDisplayHeight = 540;
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
palette: Palette = undefined,
|
||||
shouldQuit: bool = false,
|
||||
showFPS: bool = false,
|
||||
showFPS: bool = true,
|
||||
showDebug: bool = true,
|
||||
scene: ?Scene = null,
|
||||
|
||||
display: allegro.Display,
|
||||
events: allegro.EventQueue,
|
||||
renderer: Renderer,
|
||||
|
||||
viewport: engine.Viewport,
|
||||
fps: engine.FPS,
|
||||
fonts: engine.Fonts,
|
||||
textures: engine.Textures,
|
||||
keys: engine.Keys(c_int),
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) !Context {
|
||||
if (!allegro.init()) {
|
||||
@ -41,29 +42,24 @@ pub const Context = struct {
|
||||
if (!allegro.initTtfAddon()) {
|
||||
return error.FailedToInitialize;
|
||||
}
|
||||
const events = try allegro.createEventQueue();
|
||||
|
||||
allegro.setNewDisplayOption(allegro.NewDisplayOption.VSYNC, 1, allegro.OptionImportance.SUGGEST);
|
||||
allegro.setNewDisplayOption(allegro.NewDisplayOption.SAMPLE_BUFFERS, 1, allegro.OptionImportance.REQUIRE);
|
||||
allegro.setNewDisplayOption(allegro.NewDisplayOption.SAMPLES, 4, allegro.OptionImportance.REQUIRE);
|
||||
allegro.setNewDisplayFlags(allegro.NewDisplayFlags{ .RESIZABLE = true });
|
||||
|
||||
const viewport = engine.Viewport.init(DefaultDisplayWidth, DefaultDisplayHeight);
|
||||
const display = try allegro.createDisplay(DefaultDisplayWidth, DefaultDisplayHeight);
|
||||
|
||||
const events = try allegro.createEventQueue();
|
||||
|
||||
events.registerDisplay(display);
|
||||
events.registerKeyboard();
|
||||
events.registerMouse();
|
||||
|
||||
return Context{
|
||||
.allocator = allocator,
|
||||
.display = display,
|
||||
.events = events,
|
||||
.viewport = viewport,
|
||||
.renderer = Renderer.init(allocator, display),
|
||||
.fps = engine.FPS{},
|
||||
.fonts = engine.Fonts.init(allocator),
|
||||
.textures = engine.Textures.init(allocator),
|
||||
.keys = engine.Keys(c_int).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
@ -76,21 +72,24 @@ pub const Context = struct {
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Context) void {
|
||||
self.events.destroy();
|
||||
self.display.destroy();
|
||||
self.exitScene();
|
||||
self.fonts.deinit();
|
||||
self.textures.deinit();
|
||||
self.renderer.deinit();
|
||||
self.events.destroy();
|
||||
}
|
||||
|
||||
pub fn registerFonts(self: *Context) !void {
|
||||
const viewport = self.renderer.viewport;
|
||||
const fonts = &self.renderer.fonts;
|
||||
try fonts.addFromFileTTF("default", paths.AssetsDir ++ "/fonts/Pixellari.ttf", viewport.scaledInteger(16));
|
||||
try fonts.addFromFileTTF("extra-small", paths.AssetsDir ++ "/fonts/Pixellari.ttf", viewport.scaledInteger(12));
|
||||
try fonts.addFromFileTTF("large", paths.AssetsDir ++ "/fonts/Pixellari.ttf", viewport.scaledInteger(32));
|
||||
try fonts.addFromFileTTF("debug", paths.AssetsDir ++ "/fonts/Cabin-Regular.ttf", viewport.scaledInteger(16));
|
||||
}
|
||||
|
||||
pub fn quit(self: *Context) void {
|
||||
self.shouldQuit = true;
|
||||
}
|
||||
|
||||
pub fn resized(self: *Context, width: i32, height: i32) void {
|
||||
self.viewport.update(width, height);
|
||||
}
|
||||
|
||||
pub fn switchToScene(self: *Context, comptime SceneType: type, build: ?*const fn (*SceneType) void) !void {
|
||||
self.exitScene();
|
||||
|
||||
@ -98,10 +97,4 @@ pub const Context = struct {
|
||||
self.scene = scene;
|
||||
scene.enter(self);
|
||||
}
|
||||
|
||||
pub fn toggleFullScreen(self: *Context) void {
|
||||
var displayFlags = allegro.getDisplayFlags(self.display);
|
||||
_ = allegro.setDisplayFlag(self.display, allegro.NewDisplayFlags{ .FULLSCREEN_WINDOW = true }, !displayFlags.FULLSCREEN_WINDOW);
|
||||
self.viewport.update(self.display.width(), self.display.height());
|
||||
}
|
||||
};
|
||||
|
@ -2,11 +2,14 @@ const assets = @import("engine/assets.zig");
|
||||
|
||||
pub const Fonts = assets.Fonts;
|
||||
pub const FPS = @import("engine/fps.zig").FPS;
|
||||
pub const Keys = @import("engine/keys.zig").Keys;
|
||||
pub const OpaquePtr = @import("engine/opaque_ptr.zig").OpaquePtr;
|
||||
pub const Point = @import("engine/point.zig").Point;
|
||||
pub const PointF = @import("engine/point_f.zig").PointF;
|
||||
pub const Rectangle = @import("engine/rectangle.zig").Rectangle;
|
||||
pub const RectangleF = @import("engine/rectangle_f.zig").RectangleF;
|
||||
pub const Scene = @import("engine/scene.zig").Scene;
|
||||
pub const Sprite = assets.Sprite;
|
||||
pub const Sprites = assets.Sprites;
|
||||
pub const Textures = assets.Textures;
|
||||
pub const Viewport = @import("engine/viewport.zig").Viewport;
|
||||
|
@ -52,6 +52,9 @@ pub const Fonts = struct {
|
||||
|
||||
pub fn addFromFileTTF(self: *Fonts, name: []const u8, path: [*:0]const u8, size: i32) !void {
|
||||
const font = try allegro.loadTtfFont(path, size, allegro.LoadTtfFontFlags{});
|
||||
if (self.assets.get(name)) |f| {
|
||||
f.destroy();
|
||||
}
|
||||
try self.assets.put(name, font);
|
||||
}
|
||||
|
||||
@ -95,3 +98,80 @@ pub const Textures = struct {
|
||||
return self.assets.get(name);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Sprite = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
frames: []allegro.Bitmap,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, bitmap: allegro.Bitmap, width: i32, height: i32) !Sprite {
|
||||
const bitmapWidth = bitmap.width();
|
||||
const bitmapHeight = bitmap.height();
|
||||
|
||||
const horizontal = @divTrunc(bitmapWidth, width);
|
||||
const vertical = @divTrunc(bitmapHeight, height);
|
||||
const n = @intCast(usize, horizontal * vertical);
|
||||
const frames = try allocator.alloc(allegro.Bitmap, n);
|
||||
var i: i32 = 0;
|
||||
while (i < n) : (i += 1) {
|
||||
const left = @mod(i, horizontal) * width;
|
||||
const top = @divTrunc(i, horizontal) * width;
|
||||
frames[@intCast(usize, i)] = try bitmap.sub(left, top, width, height);
|
||||
}
|
||||
|
||||
return Sprite{
|
||||
.allocator = allocator,
|
||||
.frames = frames,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: Sprite) void {
|
||||
for (self.frames) |f| {
|
||||
f.destroy();
|
||||
}
|
||||
self.allocator.free(self.frames);
|
||||
}
|
||||
|
||||
pub fn frame(self: Sprite, i: usize) allegro.Bitmap {
|
||||
return self.frames[i];
|
||||
}
|
||||
};
|
||||
|
||||
pub const Sprites = struct {
|
||||
fn deinitAsset(sprite: Sprite) void {
|
||||
sprite.deinit();
|
||||
}
|
||||
const Map = Assets(Sprite, deinitAsset);
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
assets: Map,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) Sprites {
|
||||
return Sprites{
|
||||
.allocator = allocator,
|
||||
.assets = Map.init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Sprites) void {
|
||||
self.assets.deinit();
|
||||
}
|
||||
|
||||
pub fn add(self: *Sprites, name: []const u8, bitmap: allegro.Bitmap, width: i32, height: i32) !void {
|
||||
try self.assets.put(name, try Sprite.init(self.allocator, bitmap, width, height));
|
||||
}
|
||||
|
||||
pub fn addFromTextures(self: *Sprites, textures: Textures, name: []const u8, width: i32, height: i32) !void {
|
||||
try self.add(name, textures.get(name).?, width, height);
|
||||
}
|
||||
|
||||
pub fn get(self: Sprites, name: []const u8) ?Sprite {
|
||||
return self.assets.get(name);
|
||||
}
|
||||
|
||||
pub fn getFrame(self: Sprites, name: []const u8, i: usize) ?allegro.Bitmap {
|
||||
if (self.assets.get(name)) |sprite| {
|
||||
return sprite.frame(i);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
@ -20,6 +20,8 @@ pub const FPS = struct {
|
||||
}
|
||||
|
||||
pub fn fps(self: FPS) usize {
|
||||
if (self.framesCounted == 0) return 0;
|
||||
|
||||
return @floatToInt(usize, @intToFloat(f32, self.framesCounted) / self.total);
|
||||
}
|
||||
};
|
||||
|
34
src/engine/keys.zig
Normal file
34
src/engine/keys.zig
Normal file
@ -0,0 +1,34 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn Keys(comptime KeyType: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
pressed: std.AutoHashMap(KeyType, bool),
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) Self {
|
||||
return Self{
|
||||
.pressed = std.AutoHashMap(KeyType, bool).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.pressed.deinit();
|
||||
}
|
||||
|
||||
pub fn isKeyPressed(self: Self, key: KeyType) bool {
|
||||
if (self.pressed.get(key)) |pressed| {
|
||||
return pressed;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn press(self: *Self, key: KeyType) !void {
|
||||
try self.pressed.put(key, true);
|
||||
}
|
||||
|
||||
pub fn release(self: *Self, key: KeyType) !void {
|
||||
try self.pressed.put(key, false);
|
||||
}
|
||||
};
|
||||
}
|
@ -1,9 +1,12 @@
|
||||
const std = @import("std");
|
||||
const PointF = @import("point_f.zig").PointF;
|
||||
|
||||
pub const Point = struct {
|
||||
x: i64,
|
||||
y: i64,
|
||||
|
||||
pub const Zero = Point{ .x = 0, .y = 0 };
|
||||
|
||||
pub fn init(x: i64, y: i64) Point {
|
||||
return Point{ .x = x, .y = y };
|
||||
}
|
||||
@ -16,6 +19,16 @@ pub const Point = struct {
|
||||
return Point{ .x = self.x + other.x, .y = self.y + other.y };
|
||||
}
|
||||
|
||||
pub fn distance(self: Point, to: Point) i64 {
|
||||
return std.math.sqrt(self.distance2(to));
|
||||
}
|
||||
|
||||
pub fn distance2(self: Point, to: Point) i64 {
|
||||
const dx = to.x - self.x;
|
||||
const dy = to.y - self.y;
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
pub fn float(self: Point) PointF {
|
||||
return PointF{ .x = @intToFloat(f32, self.x), .y = @intToFloat(f32, self.y) };
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ pub const PointF = struct {
|
||||
x: f32,
|
||||
y: f32,
|
||||
|
||||
pub const Zero = Point{ .x = 0, .y = 0 };
|
||||
|
||||
pub fn init(x: f32, y: f32) PointF {
|
||||
return PointF{ .x = x, .y = y };
|
||||
}
|
||||
@ -18,6 +20,16 @@ pub const PointF = struct {
|
||||
return PointF{ .x = std.math.ceil(self.x), .y = std.math.ceil(self.y) };
|
||||
}
|
||||
|
||||
pub fn distance(self: PointF, to: PointF) f32 {
|
||||
return std.math.sqrt(self.distance2(to));
|
||||
}
|
||||
|
||||
pub fn distance2(self: PointF, to: PointF) f32 {
|
||||
const dx = to.x - self.x;
|
||||
const dy = to.y - self.y;
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
pub fn floor(self: PointF) PointF {
|
||||
return PointF{ .x = std.math.floor(self.x), .y = std.math.floor(self.y) };
|
||||
}
|
||||
@ -30,6 +42,10 @@ pub const PointF = struct {
|
||||
return PointF{ .x = self.x * factor, .y = self.y * factor };
|
||||
}
|
||||
|
||||
pub fn round(self: PointF) PointF {
|
||||
return PointF{ .x = std.math.round(self.x), .y = std.math.round(self.y) };
|
||||
}
|
||||
|
||||
pub fn subtract(self: PointF, other: PointF) PointF {
|
||||
return PointF{ .x = self.x - other.x, .y = self.y - other.y };
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
const Point = @import("point.zig").Point;
|
||||
const RectangleF = @import("rectangle_f.zig").RectangleF;
|
||||
|
||||
pub const Rectangle = struct {
|
||||
min: Point,
|
||||
@ -65,6 +66,10 @@ pub const Rectangle = struct {
|
||||
return Point.init(@divTrunc(self.min.x + self.max.x, 2), @divTrunc(self.min.y + self.max.y, 2));
|
||||
}
|
||||
|
||||
pub fn float(self: Rectangle) RectangleF {
|
||||
return RectangleF{ .min = self.min.float(), .max = self.max.float() };
|
||||
}
|
||||
|
||||
pub fn height(self: Rectangle) i64 {
|
||||
return self.max.y - self.min.y;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
const PointF = @import("point_f.zig").PointF;
|
||||
const Rectangle = @import("rectangle.zig").Rectangle;
|
||||
|
||||
pub const RectangleF = struct {
|
||||
min: PointF,
|
||||
@ -69,6 +70,10 @@ pub const RectangleF = struct {
|
||||
return self.max.y - self.min.y;
|
||||
}
|
||||
|
||||
pub fn integer(self: RectangleF) Rectangle {
|
||||
return Rectangle{ .min = self.min.integer(), .max = self.max.integer() };
|
||||
}
|
||||
|
||||
pub fn width(self: RectangleF) f32 {
|
||||
return self.max.x - self.min.x;
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ pub fn Scene(comptime Context: type, comptime Event: type) type {
|
||||
enter: *const fn (Self, *Context) void,
|
||||
exit: *const fn (Self, *Context) void,
|
||||
handle: *const fn (Self, *Context, Event) anyerror!void,
|
||||
update: *const fn (Self, *Context, f32) void,
|
||||
tick: *const fn (Self, *Context, f32, f32) void,
|
||||
render: *const fn (Self, *Context) void,
|
||||
};
|
||||
|
||||
@ -29,14 +29,14 @@ pub fn Scene(comptime Context: type, comptime Event: type) type {
|
||||
try self.virtualTable.handle(self, ctx, event);
|
||||
}
|
||||
|
||||
pub fn update(self: Self, ctx: *Context, dt: f32) void {
|
||||
self.virtualTable.update(self, ctx, dt);
|
||||
}
|
||||
|
||||
pub fn render(self: Self, ctx: *Context) void {
|
||||
self.virtualTable.render(self, ctx);
|
||||
}
|
||||
|
||||
pub fn tick(self: Self, ctx: *Context, t: f32, dt: f32) void {
|
||||
self.virtualTable.tick(self, ctx, t, dt);
|
||||
}
|
||||
|
||||
pub fn makeOpaque(comptime SceneType: type, object: *SceneType) Self {
|
||||
return Self{ .object = @ptrToInt(object), .virtualTable = .{
|
||||
.enter = struct {
|
||||
@ -57,12 +57,12 @@ pub fn Scene(comptime Context: type, comptime Event: type) type {
|
||||
try scene.handle(ctx, event);
|
||||
}
|
||||
}.handle,
|
||||
.update = struct {
|
||||
fn update(self: Self, ctx: *Context, dt: f32) void {
|
||||
.tick = struct {
|
||||
fn tick(self: Self, ctx: *Context, t: f32, dt: f32) void {
|
||||
const scene = @intToPtr(*SceneType, self.object);
|
||||
scene.update(ctx, dt);
|
||||
scene.tick(ctx, t, dt);
|
||||
}
|
||||
}.update,
|
||||
}.tick,
|
||||
.render = struct {
|
||||
fn render(self: Self, ctx: *Context) void {
|
||||
const scene = @intToPtr(*SceneType, self.object);
|
||||
|
@ -1,3 +1,4 @@
|
||||
const std = @import("std");
|
||||
const PointF = @import("point_f.zig").PointF;
|
||||
const RectangleF = @import("rectangle_f.zig").RectangleF;
|
||||
|
||||
@ -30,6 +31,20 @@ pub const Viewport = struct {
|
||||
return self.bounds.center();
|
||||
}
|
||||
|
||||
pub fn scaledInteger(self: Viewport, i: i32) i32 {
|
||||
return @floatToInt(i32, std.math.round(@intToFloat(f32, i) * self.scale));
|
||||
}
|
||||
|
||||
pub fn viewToScreen(self: Viewport, x: f32, y: f32) PointF {
|
||||
const screenX = self.bounds.min.x + x * self.bounds.width();
|
||||
const screenY = self.bounds.min.y + y * self.bounds.height();
|
||||
return PointF.init(screenX, screenY);
|
||||
}
|
||||
|
||||
pub fn viewToScreenP(self: Viewport, position: PointF) PointF {
|
||||
return self.viewToScreen(position.x, position.y);
|
||||
}
|
||||
|
||||
pub fn update(self: *Viewport, width: i32, height: i32) void {
|
||||
self.actualWidth = width;
|
||||
self.actualHeight = height;
|
||||
|
3
src/game.zig
Normal file
3
src/game.zig
Normal file
@ -0,0 +1,3 @@
|
||||
pub const Animation = @import("game/animation.zig").Animation;
|
||||
pub const Game = @import("game/game.zig").Game;
|
||||
pub const Level = @import("game/level.zig").Level;
|
46
src/game/animation.zig
Normal file
46
src/game/animation.zig
Normal file
@ -0,0 +1,46 @@
|
||||
const allegro = @import("allegro");
|
||||
const engine = @import("../engine.zig");
|
||||
|
||||
pub const Animation = struct {
|
||||
sprite: engine.Sprite,
|
||||
current: usize,
|
||||
begin: usize,
|
||||
end: usize,
|
||||
interval: f32,
|
||||
lastUpdate: f32,
|
||||
|
||||
pub fn init(sprite: engine.Sprite, interval: f32) Animation {
|
||||
return Animation.initPartialLoop(sprite, interval, 0, sprite.frames.len);
|
||||
}
|
||||
|
||||
pub fn initPartialLoop(sprite: engine.Sprite, interval: f32, begin: usize, end: usize) Animation {
|
||||
return Animation{
|
||||
.sprite = sprite,
|
||||
.current = begin,
|
||||
.begin = begin,
|
||||
.end = end,
|
||||
.interval = interval,
|
||||
.lastUpdate = 0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn currentFrame(self: *Animation) allegro.Bitmap {
|
||||
return self.sprite.frames[self.current];
|
||||
}
|
||||
|
||||
pub fn reset(self: *Animation) void {
|
||||
self.current = self.begin;
|
||||
}
|
||||
|
||||
pub fn tick(self: *Animation, t: f32) void {
|
||||
const delta = t - self.lastUpdate;
|
||||
|
||||
const skip = @floatToInt(usize, @divFloor(delta, self.interval));
|
||||
self.lastUpdate = t - @rem(delta, self.interval);
|
||||
|
||||
self.current = (self.current + skip);
|
||||
while (self.current >= self.end) {
|
||||
self.current = self.begin;
|
||||
}
|
||||
}
|
||||
};
|
106
src/game/game.zig
Normal file
106
src/game/game.zig
Normal file
@ -0,0 +1,106 @@
|
||||
const std = @import("std");
|
||||
const engine = @import("../engine.zig");
|
||||
const Animation = @import("animation.zig").Animation;
|
||||
const Renderer = @import("../renderer.zig").Renderer;
|
||||
const Level = @import("level.zig").Level;
|
||||
|
||||
pub const Game = struct {
|
||||
pub const Direction = enum {
|
||||
Left,
|
||||
Right,
|
||||
};
|
||||
|
||||
level: Level,
|
||||
health: i64 = 4,
|
||||
playerPosition: engine.PointF,
|
||||
playerIdleAnimation: Animation,
|
||||
playerWalkingAnimation: Animation,
|
||||
playerDirection: Direction,
|
||||
isPlayerWalking: bool,
|
||||
|
||||
renderer: *Renderer,
|
||||
// current viewport translated to tiles.
|
||||
boundsInTiles: engine.RectangleF,
|
||||
|
||||
pub fn init(level: Level, renderer: *Renderer) Game {
|
||||
const playerPosition = level.character.float();
|
||||
return Game{
|
||||
.level = level,
|
||||
.playerPosition = playerPosition,
|
||||
.playerIdleAnimation = Animation.initPartialLoop(renderer.sprites.get("character_lion_48").?, 0.25, 0, 4),
|
||||
.playerWalkingAnimation = Animation.initPartialLoop(renderer.sprites.get("character_lion_48").?, 0.125, 4, 8),
|
||||
.playerDirection = Direction.Right,
|
||||
.isPlayerWalking = false,
|
||||
.renderer = renderer,
|
||||
.boundsInTiles = Game.calculateBoundsInTiles(playerPosition),
|
||||
};
|
||||
}
|
||||
|
||||
fn calculateBoundsInTiles(position: engine.PointF) engine.RectangleF {
|
||||
return engine.RectangleF.initRelative(
|
||||
position.x - 15,
|
||||
@min(position.y - 8.875, 4.125),
|
||||
30,
|
||||
16.875,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn drawSpriteFrame(self: *Game, spriteName: []const u8, frame: usize, x: i64, y: i64) void {
|
||||
self.drawSpriteFrameP(spriteName, frame, engine.Point.init(x, y).float());
|
||||
}
|
||||
|
||||
pub fn drawSpriteFrameP(self: *Game, spriteName: []const u8, frame: usize, position: engine.PointF) void {
|
||||
const view = self.tileP(position);
|
||||
self.renderer.drawSpriteFrameV(spriteName, frame, view.x, view.y);
|
||||
}
|
||||
|
||||
pub fn moveCharacter(self: *Game, distance: f32) void {
|
||||
self.playerPosition.x += distance;
|
||||
self.isPlayerWalking = true;
|
||||
self.playerDirection = if (distance > 0) Direction.Right else Direction.Left;
|
||||
|
||||
self.boundsInTiles = Game.calculateBoundsInTiles(self.playerPosition);
|
||||
}
|
||||
|
||||
// return values are in view coordinates
|
||||
pub fn tile(self: Game, x: f32, y: f32) engine.PointF {
|
||||
return self.tileP(engine.PointF.init(x, y));
|
||||
}
|
||||
|
||||
pub fn tileCenter(self: Game, x: i64, y: i64) engine.PointF {
|
||||
return self.tileCenterP(engine.Point.init(x, y));
|
||||
}
|
||||
|
||||
pub fn tileCenterP(self: Game, position: engine.Point) engine.PointF {
|
||||
const center = engine.PointF.init(0.5, 0.5);
|
||||
return self.tileP(position.float().add(center));
|
||||
}
|
||||
|
||||
pub fn tileP(self: Game, position: engine.PointF) engine.PointF {
|
||||
const relative = position.subtract(self.boundsInTiles.min);
|
||||
return engine.PointF.init(relative.x / self.boundsInTiles.width(), relative.y / self.boundsInTiles.height());
|
||||
}
|
||||
|
||||
pub fn tileTopLeft(self: Game, x: i64, y: i64) engine.PointF {
|
||||
return self.tileTopLeftP(engine.Point.init(x, y));
|
||||
}
|
||||
|
||||
pub fn tileTopLeftP(self: Game, p: engine.Point) engine.PointF {
|
||||
return self.tileP(p.float());
|
||||
}
|
||||
|
||||
pub fn tilesInView(self: Game) engine.Rectangle {
|
||||
// tiles that are partially or completely in view.
|
||||
return engine.RectangleF.initAbsolute(
|
||||
std.math.floor(self.boundsInTiles.min.x),
|
||||
std.math.floor(self.boundsInTiles.min.y),
|
||||
std.math.floor(self.boundsInTiles.max.x) + 1,
|
||||
std.math.floor(self.boundsInTiles.max.y) + 1,
|
||||
).integer();
|
||||
}
|
||||
|
||||
pub fn tick(self: *Game, t: f32, dt: f32) void {
|
||||
_ = dt;
|
||||
self.playerAnimation.tick(t);
|
||||
}
|
||||
};
|
127
src/game/level.zig
Normal file
127
src/game/level.zig
Normal file
@ -0,0 +1,127 @@
|
||||
const std = @import("std");
|
||||
const engine = @import("../engine.zig");
|
||||
|
||||
pub const Level = struct {
|
||||
pub fn Column(comptime Value: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
values: std.AutoHashMap(i64, Value),
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) Self {
|
||||
return Self{
|
||||
.values = std.AutoHashMap(i64, Value).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: Self) void {
|
||||
self.values.deinit();
|
||||
}
|
||||
|
||||
pub fn get(self: Self, row: i64) ?Value {
|
||||
return self.values.get(row);
|
||||
}
|
||||
|
||||
pub fn set(self: *Self, row: i64, value: Value) !void {
|
||||
try self.values.put(row, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn Components(comptime Value: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
columns: std.ArrayList(Column(Value)),
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) Self {
|
||||
return Self{
|
||||
.allocator = allocator,
|
||||
.columns = std.ArrayList(Column(Value)).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: Self) void {
|
||||
for (self.columns.items) |item| {
|
||||
item.deinit();
|
||||
}
|
||||
self.columns.deinit();
|
||||
}
|
||||
|
||||
pub fn column(self: Self, i: i64) *Column(Value) {
|
||||
return &self.columns.items[@intCast(usize, i)];
|
||||
}
|
||||
|
||||
fn ensureColumns(self: *Self, n: usize) !void {
|
||||
while (self.columns.items.len < n) {
|
||||
try self.columns.append(Column(Value).init(self.allocator));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(self: *Self, c: i64, r: i64, value: Value) !void {
|
||||
try self.ensureColumns(@intCast(usize, c) + 1);
|
||||
try self.column(c).set(r, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const Tile = enum {
|
||||
Grass,
|
||||
};
|
||||
|
||||
pub const Collectable = enum {
|
||||
Star,
|
||||
};
|
||||
|
||||
character: engine.Point,
|
||||
tiles: Components(Tile),
|
||||
collectables: Components(Collectable),
|
||||
|
||||
pub fn load(allocator: std.mem.Allocator, path: []const u8) !Level {
|
||||
const path_ = try std.fs.realpathAlloc(allocator, path);
|
||||
defer allocator.free(path_);
|
||||
|
||||
const file = try std.fs.openFileAbsolute(path_, .{ .mode = .read_only });
|
||||
defer file.close();
|
||||
|
||||
const data = try file.readToEndAlloc(allocator, 1024 * 1024);
|
||||
defer allocator.free(data);
|
||||
|
||||
return try Level.loadFromMemory(allocator, data);
|
||||
}
|
||||
|
||||
pub fn loadFromMemory(allocator: std.mem.Allocator, data: []const u8) !Level {
|
||||
const n = std.mem.count(u8, data, "\n");
|
||||
_ = n;
|
||||
|
||||
var character: ?engine.Point = null;
|
||||
var tiles = Components(Tile).init(allocator);
|
||||
var collectables = Components(Collectable).init(allocator);
|
||||
|
||||
var lines = std.mem.split(u8, data, "\n");
|
||||
var y: i64 = 0;
|
||||
while (lines.next()) |line| {
|
||||
defer y += 1;
|
||||
|
||||
try tiles.ensureColumns(line.len);
|
||||
try collectables.ensureColumns(line.len);
|
||||
|
||||
for (line, 0..) |tile, column| {
|
||||
var x = @intCast(i64, column);
|
||||
switch (tile) {
|
||||
'P' => character = engine.Point.init(x, y),
|
||||
'S' => try collectables.set(x, y, Collectable.Star),
|
||||
'x' => try tiles.set(x, y, Tile.Grass),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Level{
|
||||
.character = character.?,
|
||||
.tiles = tiles,
|
||||
.collectables = collectables,
|
||||
};
|
||||
}
|
||||
};
|
117
src/game_scene.zig
Normal file
117
src/game_scene.zig
Normal file
@ -0,0 +1,117 @@
|
||||
const std = @import("std");
|
||||
const allegro = @import("allegro");
|
||||
const engine = @import("engine.zig");
|
||||
const game = @import("game.zig");
|
||||
const paths = @import("paths.zig");
|
||||
const Context = @import("context.zig").Context;
|
||||
const Renderer = @import("renderer.zig").Renderer;
|
||||
|
||||
pub const GameScene = struct {
|
||||
game: game.Game = undefined,
|
||||
|
||||
pub fn enter(self: *GameScene, ctx: *Context) void {
|
||||
const level = game.Level.load(ctx.allocator, paths.AssetsDir ++ "/levels/level1.txt") catch unreachable;
|
||||
self.game = game.Game.init(level, &ctx.renderer);
|
||||
}
|
||||
|
||||
pub fn exit(self: *GameScene, ctx: *Context) void {
|
||||
_ = ctx;
|
||||
_ = self;
|
||||
}
|
||||
|
||||
pub fn handle(self: *GameScene, ctx: *Context, event: *allegro.Event) !void {
|
||||
_ = event;
|
||||
_ = ctx;
|
||||
_ = self;
|
||||
}
|
||||
|
||||
pub fn tick(self: *GameScene, ctx: *Context, t: f32, dt: f32) void {
|
||||
const speed: f32 = 5; // tiles/s
|
||||
|
||||
if (ctx.keys.isKeyPressed(allegro.c.ALLEGRO_KEY_LEFT)) {
|
||||
self.game.moveCharacter(-speed * dt);
|
||||
self.game.playerWalkingAnimation.tick(t);
|
||||
} else if (ctx.keys.isKeyPressed(allegro.c.ALLEGRO_KEY_RIGHT)) {
|
||||
self.game.moveCharacter(speed * dt);
|
||||
self.game.playerWalkingAnimation.tick(t);
|
||||
} else {
|
||||
if (self.game.isPlayerWalking) {
|
||||
self.game.playerWalkingAnimation.reset();
|
||||
}
|
||||
self.game.isPlayerWalking = false;
|
||||
self.game.playerIdleAnimation.tick(t);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(self: *GameScene, ctx: *Context) void {
|
||||
allegro.clearToColor(ctx.palette.background.background);
|
||||
|
||||
const renderer = ctx.renderer;
|
||||
const viewport = renderer.viewport;
|
||||
|
||||
const center = viewport.center();
|
||||
_ = center;
|
||||
const bounds = viewport.bounds;
|
||||
const scale = viewport.scale;
|
||||
_ = scale;
|
||||
const background = renderer.textures.get("background_jungle").?;
|
||||
const backgroundDisplacementFactor = -0.01;
|
||||
|
||||
const backgroundCenter = self.game.playerPosition.x * backgroundDisplacementFactor;
|
||||
const backgroundLeft = @mod(backgroundCenter, 1.0) - 1;
|
||||
|
||||
background.drawScaled(bounds.min.x + backgroundLeft * bounds.width(), bounds.min.y, bounds.width(), bounds.height());
|
||||
background.drawScaled(bounds.min.x + (backgroundLeft + 1) * bounds.width(), bounds.min.y, bounds.width(), bounds.height());
|
||||
|
||||
const tileBounds = self.game.tilesInView();
|
||||
|
||||
var x = tileBounds.min.x;
|
||||
while (x < tileBounds.max.x) : (x += 1) {
|
||||
self.game.drawSpriteFrame("tiles_dirt_32", 0, x, 19);
|
||||
self.game.drawSpriteFrame("tiles_dirt_32", 3, x, 20);
|
||||
|
||||
if (x < 0) continue;
|
||||
|
||||
const tiles = self.game.level.tiles.column(x);
|
||||
const collectables = self.game.level.collectables.column(x);
|
||||
var y = tileBounds.min.y;
|
||||
while (y < tileBounds.max.y) : (y += 1) {
|
||||
if (tiles.get(y)) |tile| {
|
||||
switch (tile) {
|
||||
game.Level.Tile.Grass => {
|
||||
self.game.drawSpriteFrame("tiles_grass_32", 1, x, y - 1);
|
||||
self.game.drawSpriteFrame("tiles_grass_32", 5, x, y);
|
||||
},
|
||||
// else => {},
|
||||
}
|
||||
}
|
||||
|
||||
if (collectables.get(y)) |collectable| {
|
||||
switch (collectable) {
|
||||
game.Level.Collectable.Star => {
|
||||
const distanceToPlayer = engine.Point.init(x, y).float().distance(self.game.playerPosition);
|
||||
self.game.drawSpriteFrame("item_star_32", @floatToInt(usize, @mod(distanceToPlayer * 0.54, 1) * 16), x, y);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const playerDirectionFrameOffset: usize = if (self.game.playerDirection == game.Game.Direction.Left) 0 else 8;
|
||||
if (self.game.isPlayerWalking) {
|
||||
self.game.drawSpriteFrameP("character_lion_48", self.game.playerWalkingAnimation.current + playerDirectionFrameOffset, self.game.playerPosition.add(engine.PointF.init(-0.25, -0.25)));
|
||||
} else {
|
||||
self.game.drawSpriteFrameP("character_lion_48", self.game.playerIdleAnimation.current + playerDirectionFrameOffset, self.game.playerPosition.add(engine.PointF.init(-0.25, -0.25)));
|
||||
}
|
||||
|
||||
if (ctx.showDebug) {
|
||||
ctx.renderer.printTextV("debug", ctx.palette.background.text, 0.01, 0.1, Renderer.TextAlignment.Left, "Character: ({d}, {d})", .{ self.game.playerPosition.x, self.game.playerPosition.y });
|
||||
ctx.renderer.printTextV("debug", ctx.palette.background.text, 0.01, 0.15, Renderer.TextAlignment.Left, "Tiles: ({d}, {d}) -> ({d}, {d})", .{ tileBounds.min.x, tileBounds.min.y, tileBounds.max.x, tileBounds.max.y });
|
||||
|
||||
// const characterTopLeft = self.game.tileP(self.game.playerPosition);
|
||||
// const characterTopLeftScreen = renderer.viewport.viewToScreenP(characterTopLeft);
|
||||
// const tileWidthScreen = 32 * viewport.scale;
|
||||
// renderer.textures.get("opaque").?.drawTintedScaled(allegro.mapRgba(127, 127, 127, 127), characterTopLeftScreen.x, characterTopLeftScreen.y, tileWidthScreen, tileWidthScreen);
|
||||
}
|
||||
}
|
||||
};
|
64
src/main.zig
64
src/main.zig
@ -1,17 +1,14 @@
|
||||
const std = @import("std");
|
||||
const allegro = @import("allegro");
|
||||
const engine = @import("engine.zig");
|
||||
const game = @import("game.zig");
|
||||
const paths = @import("paths.zig");
|
||||
const Context = @import("context.zig").Context;
|
||||
const GameScene = @import("game_scene.zig").GameScene;
|
||||
const Palette = @import("palette.zig").Palette;
|
||||
const Renderer = @import("renderer.zig").Renderer;
|
||||
const TitleScene = @import("title_scene.zig").TitleScene;
|
||||
|
||||
fn currentFile() []const u8 {
|
||||
return @src().file;
|
||||
}
|
||||
|
||||
const sourceDir = std.fs.path.dirname(currentFile()).?;
|
||||
const assetsDir = sourceDir ++ "/assets";
|
||||
|
||||
fn hexColor(hex: []const u8) allegro.Color {
|
||||
return allegro.Color.initFromHex(hex);
|
||||
}
|
||||
@ -38,41 +35,69 @@ pub fn main() !void {
|
||||
.background = .{ .background = hexColor("#fffbff"), .text = hexColor("#2b0052") },
|
||||
.outline = hexColor("#7c757e"),
|
||||
};
|
||||
const renderer = &context.renderer;
|
||||
|
||||
allegro.setNewBitmapFlags(allegro.NewBitmapFlags{ .MIN_LINEAR = true, .MAG_LINEAR = true });
|
||||
try context.textures.addFromFile("opaque", assetsDir ++ "/images/opaque.png");
|
||||
try context.textures.addFromFile("title_untitled", assetsDir ++ "/images/title_untitled.png");
|
||||
try renderer.textures.addFromFile("opaque", paths.AssetsDir ++ "/images/opaque.png");
|
||||
try renderer.textures.addFromFile("title_untitled", paths.AssetsDir ++ "/images/title_untitled.png");
|
||||
try renderer.textures.addFromFile("background_jungle", paths.AssetsDir ++ "/images/background_jungle.png");
|
||||
try renderer.textures.addFromFile("character_lion_48", paths.AssetsDir ++ "/images/character_lion_48.png");
|
||||
try renderer.textures.addFromFile("item_star_32", paths.AssetsDir ++ "/images/item_star_32.png");
|
||||
try renderer.textures.addFromFile("text_balloons", paths.AssetsDir ++ "/images/text_balloons.png");
|
||||
try renderer.textures.addFromFile("tiles_dirt_32", paths.AssetsDir ++ "/images/tiles_dirt_32.png");
|
||||
try renderer.textures.addFromFile("tiles_grass_32", paths.AssetsDir ++ "/images/tiles_grass_32.png");
|
||||
|
||||
try renderer.sprites.addFromTextures(renderer.textures, "character_lion_48", 48, 48);
|
||||
try renderer.sprites.addFromTextures(renderer.textures, "item_star_32", 32, 32);
|
||||
try renderer.sprites.addFromTextures(renderer.textures, "tiles_dirt_32", 32, 32);
|
||||
try renderer.sprites.addFromTextures(renderer.textures, "tiles_grass_32", 32, 32);
|
||||
|
||||
allegro.convertMemoryBitmaps();
|
||||
|
||||
try context.fonts.addFromFileTTF("sub", assetsDir ++ "/fonts/Cabin-Regular.ttf", 16);
|
||||
try context.fonts.addFromFileTTF("default", assetsDir ++ "/fonts/Cabin-Regular.ttf", 32);
|
||||
try context.registerFonts();
|
||||
|
||||
try context.switchToScene(TitleScene, null);
|
||||
try context.switchToScene(GameScene, null);
|
||||
|
||||
var t = allegro.getTime();
|
||||
var fpsBuffer = [_]u8{0} ** 32;
|
||||
_ = fpsBuffer;
|
||||
while (!context.shouldQuit) {
|
||||
const scene = if (context.scene) |scene| scene else {
|
||||
break;
|
||||
};
|
||||
|
||||
const newT = allegro.getTime();
|
||||
const deltaT = @floatCast(f32, newT - t);
|
||||
t = newT;
|
||||
context.fps.update(deltaT);
|
||||
scene.tick(&context, @floatCast(f32, t), deltaT);
|
||||
|
||||
while (!context.events.isEmpty()) {
|
||||
var event: allegro.Event = undefined;
|
||||
_ = context.events.get(&event);
|
||||
switch (event.type) {
|
||||
allegro.c.ALLEGRO_EVENT_DISPLAY_CLOSE => context.quit(),
|
||||
allegro.c.ALLEGRO_EVENT_DISPLAY_RESIZE => {
|
||||
context.resized(event.display.width, event.display.height);
|
||||
context.renderer.resized(event.display.width, event.display.height);
|
||||
try context.registerFonts();
|
||||
_ = allegro.acknowledgeResize(allegro.Display{ .native = event.display.source.? });
|
||||
},
|
||||
allegro.c.ALLEGRO_EVENT_KEY_CHAR => {
|
||||
switch (event.keyboard.keycode) {
|
||||
allegro.c.ALLEGRO_KEY_ESCAPE => context.quit(),
|
||||
allegro.c.ALLEGRO_KEY_F9 => context.showFPS = !context.showFPS,
|
||||
allegro.c.ALLEGRO_KEY_F11 => context.toggleFullScreen(),
|
||||
allegro.c.ALLEGRO_KEY_F10 => context.showDebug = !context.showDebug,
|
||||
allegro.c.ALLEGRO_KEY_F11 => renderer.toggleFullScreen(),
|
||||
allegro.c.ALLEGRO_KEY_F12 => try renderer.takeScreenshot(),
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
allegro.c.ALLEGRO_EVENT_KEY_DOWN => {
|
||||
try context.keys.press(event.keyboard.keycode);
|
||||
},
|
||||
allegro.c.ALLEGRO_EVENT_KEY_UP => {
|
||||
try context.keys.release(event.keyboard.keycode);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
if (context.shouldQuit) {
|
||||
@ -81,18 +106,9 @@ pub fn main() !void {
|
||||
try scene.handle(&context, &event);
|
||||
}
|
||||
|
||||
const newT = allegro.getTime();
|
||||
const deltaT = @floatCast(f32, newT - t);
|
||||
t = newT;
|
||||
scene.update(&context, deltaT);
|
||||
context.fps.update(deltaT);
|
||||
|
||||
scene.render(&context);
|
||||
if (context.showFPS) {
|
||||
if (context.fonts.get("default")) |font| {
|
||||
const fps = try std.fmt.bufPrintZ(fpsBuffer[0..], "FPS: {}", .{context.fps.fps()});
|
||||
allegro.drawText(font, context.palette.background.text, 10, 10, allegro.DrawTextFlags.ALIGN_LEFT, fps);
|
||||
}
|
||||
renderer.printTextV("debug", context.palette.background.text, 0.01, 0.01, Renderer.TextAlignment.Left, "FPS: {}", .{context.fps.fps()});
|
||||
}
|
||||
|
||||
allegro.flipDisplay();
|
||||
|
@ -1,4 +1,5 @@
|
||||
const allegro = @import("allegro");
|
||||
const game = @import("game.zig");
|
||||
const Context = @import("context.zig").Context;
|
||||
|
||||
pub const MainMenuScene = struct {
|
||||
@ -31,6 +32,7 @@ pub const MainMenuScene = struct {
|
||||
const center = ctx.viewport.center();
|
||||
ctx.textures.get("title_untitled").?.drawTintedCenteredScaled(ctx.palette.background.text, center.x, ctx.viewport.bounds.min.y + ctx.viewport.scale * 100, 0.5 * ctx.viewport.scale);
|
||||
|
||||
ctx.renderer.drawTextV("default", ctx.palette.background.text, 0, 0.3, game.Renderer.TextAlignment.Center, "Play game");
|
||||
allegro.drawText(ctx.fonts.get("default").?, ctx.palette.background.text, center.x, ctx.viewport.bounds.min.y + ctx.viewport.scale * 200, allegro.DrawTextFlags.ALIGN_CENTER, "Play game");
|
||||
}
|
||||
};
|
||||
|
8
src/paths.zig
Normal file
8
src/paths.zig
Normal file
@ -0,0 +1,8 @@
|
||||
const std = @import("std");
|
||||
|
||||
fn currentFile() []const u8 {
|
||||
return @src().file;
|
||||
}
|
||||
|
||||
pub const SourceDir = std.fs.path.dirname(currentFile()).?;
|
||||
pub const AssetsDir = SourceDir ++ "/assets";
|
96
src/renderer.zig
Normal file
96
src/renderer.zig
Normal file
@ -0,0 +1,96 @@
|
||||
const std = @import("std");
|
||||
const allegro = @import("allegro");
|
||||
const engine = @import("engine.zig");
|
||||
const Context = @import("context.zig").Context;
|
||||
|
||||
pub const Renderer = struct {
|
||||
textFormattingBuffer: [1024]u8 = [_]u8{0} ** 1024,
|
||||
|
||||
display: allegro.Display,
|
||||
viewport: engine.Viewport,
|
||||
fonts: engine.Fonts,
|
||||
textures: engine.Textures,
|
||||
sprites: engine.Sprites,
|
||||
|
||||
pub const TextAlignment = enum {
|
||||
Center,
|
||||
Left,
|
||||
Right,
|
||||
|
||||
pub fn toDrawTextFlags(self: TextAlignment) allegro.DrawTextFlags {
|
||||
return switch (self) {
|
||||
.Center => allegro.DrawTextFlags.ALIGN_CENTER,
|
||||
.Left => allegro.DrawTextFlags.ALIGN_LEFT,
|
||||
.Right => allegro.DrawTextFlags.ALIGN_RIGHT,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, display: allegro.Display) Renderer {
|
||||
const viewport = engine.Viewport.init(display.width(), display.height());
|
||||
|
||||
return Renderer{
|
||||
.display = display,
|
||||
.viewport = viewport,
|
||||
.fonts = engine.Fonts.init(allocator),
|
||||
.textures = engine.Textures.init(allocator),
|
||||
.sprites = engine.Sprites.init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Renderer) void {
|
||||
self.fonts.deinit();
|
||||
self.textures.deinit();
|
||||
self.sprites.deinit();
|
||||
self.display.destroy();
|
||||
}
|
||||
|
||||
pub fn drawText(self: Renderer, fontName: []const u8, color: allegro.Color, x: f32, y: f32, alignment: TextAlignment, text: [*:0]const u8) void {
|
||||
if (self.fonts.get(fontName)) |font| {
|
||||
font.draw(color, x, y, alignment.toDrawTextFlags(), text);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drawSpriteFrame(self: Renderer, spriteName: []const u8, frame: usize, x: f32, y: f32) void {
|
||||
if (self.sprites.getFrame(spriteName, frame)) |sprite| {
|
||||
sprite.drawScaledUniform(x, y, self.viewport.scale);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drawSpriteFrameV(self: Renderer, spriteName: []const u8, frame: usize, x: f32, y: f32) void {
|
||||
const screen = self.viewport.viewToScreen(x, y);
|
||||
self.drawSpriteFrame(spriteName, frame, screen.x, screen.y);
|
||||
}
|
||||
|
||||
pub fn drawTextV(self: Renderer, fontName: []const u8, color: allegro.Color, x: f32, y: f32, alignment: TextAlignment, text: [*:0]const u8) void {
|
||||
const screen = self.viewport.viewToScreen(x, y);
|
||||
self.drawText(fontName, color, screen.x, screen.y, alignment, text);
|
||||
}
|
||||
|
||||
pub fn printText(self: *Renderer, font: []const u8, color: allegro.Color, x: f32, y: f32, alignment: TextAlignment, comptime fmt: []const u8, args: anytype) void {
|
||||
const text = std.fmt.bufPrintZ(&self.textFormattingBuffer, fmt, args) catch {
|
||||
return;
|
||||
};
|
||||
self.drawText(font, color, x, y, alignment, text);
|
||||
}
|
||||
|
||||
pub fn printTextV(self: *Renderer, font: []const u8, color: allegro.Color, x: f32, y: f32, alignment: TextAlignment, comptime fmt: []const u8, args: anytype) void {
|
||||
const screen = self.viewport.viewToScreen(x, y).round();
|
||||
self.printText(font, color, screen.x, screen.y, alignment, fmt, args);
|
||||
}
|
||||
|
||||
pub fn resized(self: *Renderer, width: i32, height: i32) void {
|
||||
self.viewport.update(width, height);
|
||||
}
|
||||
|
||||
pub fn takeScreenshot(self: Renderer) !void {
|
||||
const screen = try allegro.getBackbuffer(self.display);
|
||||
_ = allegro.saveBitmap("screenshot.png", screen);
|
||||
}
|
||||
|
||||
pub fn toggleFullScreen(self: *Renderer) void {
|
||||
var displayFlags = allegro.getDisplayFlags(self.display);
|
||||
_ = allegro.setDisplayFlag(self.display, allegro.NewDisplayFlags{ .FULLSCREEN_WINDOW = true }, !displayFlags.FULLSCREEN_WINDOW);
|
||||
self.viewport.update(self.display.width(), self.display.height());
|
||||
}
|
||||
};
|
@ -28,7 +28,8 @@ pub const TitleScene = struct {
|
||||
_ = self;
|
||||
}
|
||||
|
||||
pub fn update(self: *TitleScene, ctx: *Context, dt: f32) void {
|
||||
pub fn tick(self: *TitleScene, ctx: *Context, t: f32, dt: f32) void {
|
||||
_ = t;
|
||||
_ = self;
|
||||
_ = dt;
|
||||
_ = ctx;
|
||||
|
Loading…
Reference in New Issue
Block a user