integrate tiled
This commit is contained in:
parent
61e5edb8cf
commit
e5e4e429b6
37
build.zig
37
build.zig
@ -14,6 +14,7 @@ pub fn build(b: *std.Build) !void {
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = true
|
||||
}),
|
||||
});
|
||||
const exe_mod = exe.root_module;
|
||||
@ -36,6 +37,42 @@ pub fn build(b: *std.Build) !void {
|
||||
const fontstash_dependency = b.dependency("fontstash", .{});
|
||||
exe_mod.addIncludePath(fontstash_dependency.path("src"));
|
||||
|
||||
const libxml2_dependency = b.dependency("libxml2", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.linkage = .static,
|
||||
});
|
||||
|
||||
{
|
||||
const libtmx_dependency = b.dependency("libtmx", .{});
|
||||
const libtmx = b.addLibrary(.{
|
||||
.name = "tmx",
|
||||
.root_module = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = true
|
||||
}),
|
||||
});
|
||||
libtmx.installHeader(libtmx_dependency.path("src/tmx.h"), "tmx.h");
|
||||
libtmx.root_module.addCSourceFiles(.{
|
||||
.root = libtmx_dependency.path("src"),
|
||||
.files = &.{
|
||||
"tmx.c",
|
||||
"tmx_utils.c",
|
||||
"tmx_err.c",
|
||||
"tmx_xml.c",
|
||||
"tmx_mem.c",
|
||||
"tmx_hash.c"
|
||||
},
|
||||
.flags = &.{
|
||||
"-fno-delete-null-pointer-checks"
|
||||
}
|
||||
});
|
||||
libtmx.linkLibrary(libxml2_dependency.artifact("xml"));
|
||||
|
||||
exe_mod.linkLibrary(libtmx);
|
||||
}
|
||||
|
||||
const sokol_dependency = b.dependency("sokol", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
|
||||
@ -29,6 +29,14 @@
|
||||
.url = "git+https://github.com/memononen/fontstash.git#b5ddc9741061343740d85d636d782ed3e07cf7be",
|
||||
.hash = "N-V-__8AAA9xHgAxdLYPmlNTy6qzv9IYqiIePEHQUOPWYQ_6",
|
||||
},
|
||||
.libtmx = .{
|
||||
.url = "git+https://github.com/baylej/tmx.git#11ffdcdc9bd65669f1a8dbd3a0362a324dda2e0c",
|
||||
.hash = "N-V-__8AAKQvBQCTT3Q6_we7vTVX-MkAWDZ91YkUev040IRo",
|
||||
},
|
||||
.libxml2 = .{
|
||||
.url = "git+https://github.com/allyourcodebase/libxml2.git?ref=2.14.3-4#86c4742a9becd6c86dc79180f806ed344fd2a727",
|
||||
.hash = "libxml2-2.14.3-4-qHdjhn9FAACpyisv_5DDFVQlegox6QE3mTpdlr44RcbT",
|
||||
},
|
||||
},
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
|
||||
23
src/assets/tiled/first.tmx
Normal file
23
src/assets/tiled/first.tmx
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.10" tiledversion="1.11.2" orientation="orthogonal" renderorder="right-down" width="20" height="15" tilewidth="8" tileheight="8" infinite="0" nextlayerid="2" nextobjectid="1">
|
||||
<tileset firstgid="1" source="tileset.tsx"/>
|
||||
<layer id="1" name="Tile Layer 1" width="20" height="15">
|
||||
<data encoding="csv">
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
</data>
|
||||
</layer>
|
||||
</map>
|
||||
14
src/assets/tiled/main.tiled-project
Normal file
14
src/assets/tiled/main.tiled-project
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"automappingRulesFile": "",
|
||||
"commands": [
|
||||
],
|
||||
"compatibilityVersion": 1100,
|
||||
"extensionsPath": "extensions",
|
||||
"folders": [
|
||||
"."
|
||||
],
|
||||
"properties": [
|
||||
],
|
||||
"propertyTypes": [
|
||||
]
|
||||
}
|
||||
36
src/assets/tiled/main.tiled-session
Normal file
36
src/assets/tiled/main.tiled-session
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"Map/SizeTest": {
|
||||
"height": 4300,
|
||||
"width": 2
|
||||
},
|
||||
"activeFile": "",
|
||||
"expandedProjectPaths": [
|
||||
],
|
||||
"fileStates": {
|
||||
"first.tmx": {
|
||||
"scale": 3,
|
||||
"selectedLayer": 0,
|
||||
"viewCenter": {
|
||||
"x": 141.49999999999997,
|
||||
"y": 68
|
||||
}
|
||||
}
|
||||
},
|
||||
"last.imagePath": "/home/rokas/code/games/game-2025-12-13/src/assets/kenney-micro-roguelike",
|
||||
"map.height": 15,
|
||||
"map.lastUsedFormat": "tmx",
|
||||
"map.tileHeight": 8,
|
||||
"map.tileWidth": 8,
|
||||
"map.width": 20,
|
||||
"openFiles": [
|
||||
],
|
||||
"project": "main.tiled-project",
|
||||
"recentFiles": [
|
||||
"first.tmx"
|
||||
],
|
||||
"tileset.lastUsedFormat": "tsx",
|
||||
"tileset.tileSize": {
|
||||
"height": 8,
|
||||
"width": 8
|
||||
}
|
||||
}
|
||||
4
src/assets/tiled/tileset.tsx
Normal file
4
src/assets/tiled/tileset.tsx
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<tileset version="1.10" tiledversion="1.11.2" name="tileset" tilewidth="8" tileheight="8" tilecount="160" columns="16">
|
||||
<image source="../kenney-micro-roguelike/colored_tilemap_packed.png" width="128" height="80"/>
|
||||
</tileset>
|
||||
@ -1,5 +1,7 @@
|
||||
const GenerationalArrayList = @import("./generational_array_list.zig").GenerationalArrayList;
|
||||
|
||||
const Gfx = @import("./graphics.zig");
|
||||
|
||||
const Math = @import("./math.zig");
|
||||
const Vec2 = Math.Vec2;
|
||||
|
||||
@ -9,9 +11,14 @@ pub const List = GenerationalArrayList(Entity);
|
||||
pub const Id = List.Id;
|
||||
|
||||
pub const Type = enum {
|
||||
nil,
|
||||
player
|
||||
};
|
||||
|
||||
type: Type,
|
||||
position: Vec2
|
||||
position: Vec2,
|
||||
|
||||
render_tile: ?union(enum) {
|
||||
position: Vec2,
|
||||
id: Gfx.TileId
|
||||
} = null
|
||||
|
||||
54
src/game.zig
54
src/game.zig
@ -1,10 +1,12 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const Math = @import("./math.zig");
|
||||
const Vec2 = Math.Vec2;
|
||||
const Vec4 = Math.Vec4;
|
||||
const rgb = Math.rgb;
|
||||
const rgb_hex = Math.rgb_hex;
|
||||
|
||||
const Timer = @import("./timer.zig");
|
||||
const Window = @import("./window.zig");
|
||||
@ -12,6 +14,8 @@ const imgui = @import("./imgui.zig");
|
||||
const Gfx = @import("./graphics.zig");
|
||||
const Entity = @import("./entity.zig");
|
||||
|
||||
const tiled = @import("./tiled.zig");
|
||||
|
||||
const Game = @This();
|
||||
|
||||
pub const Input = struct {
|
||||
@ -46,9 +50,46 @@ pub fn init(gpa: Allocator) !Game {
|
||||
|
||||
_ = try game.entities.insert(gpa, .{
|
||||
.type = .player,
|
||||
.position = .init(0, 0)
|
||||
.position = .init(0, 0),
|
||||
.render_tile = .{ .id = .player },
|
||||
});
|
||||
|
||||
const manager = try tiled.ResourceManager.init();
|
||||
defer manager.deinit();
|
||||
|
||||
try manager.loadTilesetFromBuffer(@embedFile("assets/tiled/tileset.tsx"), "tileset.tsx");
|
||||
|
||||
const map = try manager.loadMapFromBuffer(@embedFile("assets/tiled/first.tmx"));
|
||||
defer map.deinit();
|
||||
|
||||
var layer_iter = map.iterLayers();
|
||||
while (layer_iter.next()) |layer| {
|
||||
if (layer.layer.visible == 0) {
|
||||
continue;
|
||||
}
|
||||
if (layer.layer.type != @intFromEnum(tiled.Layer.Type.layer)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const map_width = map.map.width;
|
||||
for (0..map.map.height) |y| {
|
||||
for (0..map_width) |x| {
|
||||
|
||||
const tile = map.getTile(layer, x, y) orelse continue;
|
||||
|
||||
if (tile.getPropertyString("type")) |tile_type| {
|
||||
_ = tile_type; // autofix
|
||||
}
|
||||
|
||||
_ = try game.entities.insert(gpa, .{
|
||||
.type = .nil,
|
||||
.position = Vec2.init(@floatFromInt(x), @floatFromInt(y)).multiply(tile_size),
|
||||
.render_tile = .{ .position = tile.getUpperLeft().divide(tile_size) },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return game;
|
||||
}
|
||||
|
||||
@ -96,8 +137,8 @@ fn drawGrid(self: *Game, size: Vec2, color: Vec4) void {
|
||||
}
|
||||
|
||||
pub fn tick(self: *Game, input: Input) !void {
|
||||
Gfx.drawRectangle(.init(0, 0), .init(self.canvas_size.x, self.canvas_size.y), rgb(255, 255, 255));
|
||||
self.drawGrid(tile_size, rgb(200, 200, 200));
|
||||
Gfx.drawRectangle(.init(0, 0), .init(self.canvas_size.x, self.canvas_size.y), rgb_hex("#222323").?);
|
||||
self.drawGrid(tile_size, rgb(20, 20, 20));
|
||||
|
||||
self.timers.now += input.dt;
|
||||
|
||||
@ -128,8 +169,13 @@ pub fn tick(self: *Game, input: Input) !void {
|
||||
// const velocity = input.move.multiplyScalar(100);
|
||||
// entity.position = entity.position.add(velocity.multiplyScalar(input.dt));
|
||||
entity.position = entity.position.add(move.multiply(tile_size));
|
||||
}
|
||||
|
||||
Gfx.drawTileById(.player, entity.position, tile_size, rgb(255, 255, 255));
|
||||
if (entity.render_tile) |render_tile| {
|
||||
switch (render_tile) {
|
||||
.id => |tile_id| Gfx.drawTileById(tile_id, entity.position, tile_size, rgb(255, 255, 255)),
|
||||
.position => |position| Gfx.drawTile(position, entity.position, tile_size, rgb(255, 255, 255)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -774,10 +774,6 @@ fn makeImageFromMemory(image_datas: []const []const u8) !sg.Image {
|
||||
return try makeImageWithMipMaps(stbi_images.items);
|
||||
}
|
||||
|
||||
fn tileCoordToQuad(coord: Vec2) Rect {
|
||||
_ = coord; // autofix
|
||||
}
|
||||
|
||||
pub fn init(options: Options) !void {
|
||||
dpi_scale = sapp.dpiScale();
|
||||
|
||||
@ -940,7 +936,12 @@ pub fn drawTile(tile_coord: Vec2, pos: Vec2, size: Vec2, tint: Vec4) void {
|
||||
nearest_sampler
|
||||
);
|
||||
|
||||
const tile_quad = Rect.init(tile_coord.x, tile_coord.y, 1, 1).multiply(tile_size).divide(tilemap_size);
|
||||
var tile_quad = Rect.init(
|
||||
tile_coord.x,
|
||||
tile_coord.y,
|
||||
1,
|
||||
1
|
||||
).multiply(tile_size).divide(tilemap_size);
|
||||
|
||||
const top_left = pos;
|
||||
const top_right = pos.add(.{ .x = size.x, .y = 0 });
|
||||
@ -950,33 +951,30 @@ pub fn drawTile(tile_coord: Vec2, pos: Vec2, size: Vec2, tint: Vec4) void {
|
||||
sgl.beginQuads();
|
||||
defer sgl.end();
|
||||
|
||||
v2fT2Color(
|
||||
sgl.c4f(tint.x, tint.y, tint.z, tint.w);
|
||||
sgl.v2fT2f(
|
||||
top_left.x,
|
||||
top_left.y,
|
||||
tile_quad.left(),
|
||||
tile_quad.top(),
|
||||
tint
|
||||
);
|
||||
v2fT2Color(
|
||||
sgl.v2fT2f(
|
||||
top_right.x,
|
||||
top_right.y,
|
||||
tile_quad.right(),
|
||||
tile_quad.top(),
|
||||
tint
|
||||
);
|
||||
v2fT2Color(
|
||||
sgl.v2fT2f(
|
||||
bottom_right.x,
|
||||
bottom_right.y,
|
||||
tile_quad.right(),
|
||||
tile_quad.bottom(),
|
||||
tint
|
||||
);
|
||||
v2fT2Color(
|
||||
sgl.v2fT2f(
|
||||
bottom_left.x,
|
||||
bottom_left.y,
|
||||
tile_quad.left(),
|
||||
tile_quad.bottom(),
|
||||
tint
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -109,8 +109,6 @@ pub fn main() !void {
|
||||
.width = 640,
|
||||
.height = 480,
|
||||
.icon = .{ .sokol_default = true },
|
||||
.high_dpi = true,
|
||||
.sample_count = 4,
|
||||
.window_title = "Game",
|
||||
.logger = .{ .func = Window.sokolLogCallback },
|
||||
});
|
||||
|
||||
155
src/tiled.zig
Normal file
155
src/tiled.zig
Normal file
@ -0,0 +1,155 @@
|
||||
const std = @import("std");
|
||||
const c = @cImport({
|
||||
@cInclude("stdlib.h");
|
||||
@cInclude("tmx.h");
|
||||
});
|
||||
|
||||
const math = @import("math.zig");
|
||||
const Vec2 = math.Vec2;
|
||||
|
||||
const log = std.log.scoped(.tiled);
|
||||
|
||||
pub const TmxError = error {
|
||||
MakeResourceManager,
|
||||
LoadTileset,
|
||||
LoadMap,
|
||||
};
|
||||
|
||||
pub fn init() void {
|
||||
c.tmx_alloc_func = c.realloc;
|
||||
c.tmx_free_func = c.free;
|
||||
}
|
||||
|
||||
pub const ResourceManager = struct {
|
||||
manager: *anyopaque,
|
||||
|
||||
pub fn init() !ResourceManager {
|
||||
const manager = c.tmx_make_resource_manager();
|
||||
if (manager == null) {
|
||||
log.err("tmx_make_resource_manager: {s}", .{c.tmx_strerr()});
|
||||
return TmxError.MakeResourceManager;
|
||||
}
|
||||
|
||||
return ResourceManager{
|
||||
.manager = manager.?,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn loadTilesetFromBuffer(self: *const ResourceManager, tileset: []const u8, key: [*:0]const u8) !void {
|
||||
const success = c.tmx_load_tileset_buffer(self.manager, tileset.ptr, @intCast(tileset.len), key);
|
||||
if (success != 1) {
|
||||
log.err("tmx_load_tileset_buffer: {s}", .{c.tmx_strerr()});
|
||||
return TmxError.LoadTileset;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn loadMapFromBuffer(self: *const ResourceManager, map: []const u8) !Map {
|
||||
const map_handle = c.tmx_rcmgr_load_buffer(self.manager, map.ptr, @intCast(map.len));
|
||||
if (map_handle == null) {
|
||||
log.err("tmx_rcmgr_load_buffer: {s}", .{c.tmx_strerr()});
|
||||
return TmxError.LoadMap;
|
||||
}
|
||||
|
||||
return Map{
|
||||
.map = map_handle.?
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const ResourceManager) void {
|
||||
c.tmx_free_resource_manager(self.manager);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Tile = struct {
|
||||
tile: *c.tmx_tile,
|
||||
|
||||
pub fn getPropertyString(self: Tile, key: [*:0]const u8) ?[*:0]const u8 {
|
||||
const maybe_prop = c.tmx_get_property(self.tile.properties, key);
|
||||
if (maybe_prop == null) {
|
||||
return null;
|
||||
}
|
||||
const prop: *c.tmx_property = maybe_prop.?;
|
||||
if (prop.type != c.PT_STRING) {
|
||||
return null;
|
||||
}
|
||||
return prop.value.string;
|
||||
}
|
||||
|
||||
pub fn getUpperLeft(self: Tile) Vec2 {
|
||||
return Vec2.init(
|
||||
@floatFromInt(self.tile.ul_x),
|
||||
@floatFromInt(self.tile.ul_y),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
pub const TileWithFlags = struct {
|
||||
tile: Tile,
|
||||
flags: u32
|
||||
};
|
||||
|
||||
pub const Map = struct {
|
||||
map: *c.tmx_map,
|
||||
|
||||
pub fn deinit(self: *const Map) void {
|
||||
c.tmx_map_free(self.map);
|
||||
}
|
||||
|
||||
pub fn iterLayers(self: *const Map) Layer.Iterator {
|
||||
return Layer.Iterator{
|
||||
.current = self.map.ly_head
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getTile(self: *const Map, layer: Layer, x: usize, y: usize) ?Tile {
|
||||
if (self.getTileWithFlags(layer, x, y)) |tile_with_flags| {
|
||||
return tile_with_flags.tile;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn getTileWithFlags(self: *const Map, layer: Layer, x: usize, y: usize) ?TileWithFlags {
|
||||
if (layer.layer.type != @intFromEnum(Layer.Type.layer)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const gid = layer.layer.content.gids[(y*self.map.width) + x];
|
||||
const flags = gid & ~FLIP_BITS_REMOVAL;
|
||||
const maybe_tile = self.map.tiles[gid & FLIP_BITS_REMOVAL];
|
||||
if (maybe_tile == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return TileWithFlags{
|
||||
.tile = Tile{ .tile = maybe_tile.? },
|
||||
.flags = flags
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Layer = struct {
|
||||
layer: *c.tmx_layer,
|
||||
|
||||
pub const Type = enum(c_uint) {
|
||||
none = c.L_NONE,
|
||||
layer = c.L_LAYER,
|
||||
object_group = c.L_OBJGR,
|
||||
image = c.L_IMAGE,
|
||||
group = c.L_GROUP
|
||||
};
|
||||
|
||||
pub const Iterator = struct {
|
||||
current: ?*c.tmx_layer,
|
||||
|
||||
pub fn next(self: *Iterator) ?Layer {
|
||||
if (self.current) |current| {
|
||||
self.current = current.next;
|
||||
return Layer{ .layer = current };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
pub const FLIP_BITS_REMOVAL: u32 = c.TMX_FLIP_BITS_REMOVAL;
|
||||
@ -5,6 +5,7 @@ const sokol = @import("sokol");
|
||||
const sapp = sokol.app;
|
||||
|
||||
const Gfx = @import("./graphics.zig");
|
||||
const Tiled = @import("./tiled.zig");
|
||||
|
||||
const Math = @import("./math.zig");
|
||||
const Vec2 = Math.Vec2;
|
||||
@ -227,6 +228,8 @@ released_keys: std.EnumSet(KeyCode) = .initEmpty(),
|
||||
pressed_keys_at: std.EnumMap(KeyCode, Nanoseconds) = .init(.{}),
|
||||
|
||||
pub fn init(self: *Window, gpa: Allocator) !void {
|
||||
Tiled.init();
|
||||
|
||||
var events: std.ArrayList(Event) = .empty;
|
||||
errdefer events.deinit(gpa);
|
||||
try events.ensureTotalCapacityPrecise(gpa, 50);
|
||||
@ -296,13 +299,17 @@ pub fn frame(self: *Window) !void {
|
||||
}
|
||||
self.events.clearRetainingCapacity();
|
||||
|
||||
// TODO: Render to a lower resolution instead of scaling.
|
||||
// To avoid pixel bleeding in spritesheet artifacts
|
||||
const window_size: Vec2 = .init(sapp.widthf(), sapp.heightf());
|
||||
const scale = @min(
|
||||
const scale = @floor(@min(
|
||||
window_size.x / self.game.canvas_size.x,
|
||||
window_size.y / self.game.canvas_size.y,
|
||||
);
|
||||
));
|
||||
|
||||
const filler_size: Vec2 = Vec2.sub(window_size, self.game.canvas_size.multiplyScalar(scale)).multiplyScalar(0.5);
|
||||
var filler_size: Vec2 = Vec2.sub(window_size, self.game.canvas_size.multiplyScalar(scale)).multiplyScalar(0.5);
|
||||
filler_size.x = @round(filler_size.x);
|
||||
filler_size.y = @round(filler_size.y);
|
||||
|
||||
const input = self.game.getInput(self);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user