add box hole
This commit is contained in:
parent
01c6d334f7
commit
930a7dc275
11
README.md
11
README.md
@ -1,5 +1,16 @@
|
|||||||
# Gaem
|
# Gaem
|
||||||
|
|
||||||
|
Plan:
|
||||||
|
|
||||||
|
- Push boxes into goals
|
||||||
|
- Push boxes into a hole
|
||||||
|
- Collect optional coins
|
||||||
|
- Filtered goals and holes
|
||||||
|
- A set of levels
|
||||||
|
- Main menu
|
||||||
|
- Transitions between levels
|
||||||
|
- Particle effects
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
Linux and Windows:
|
Linux and Windows:
|
||||||
|
|||||||
174
src/app.zig
174
src/app.zig
@ -62,15 +62,16 @@ const Point = struct {
|
|||||||
|
|
||||||
const Entity = struct {
|
const Entity = struct {
|
||||||
type: Type,
|
type: Type,
|
||||||
layer: Layer,
|
|
||||||
|
|
||||||
pos: Point = .{ .x = -1, .y = -1 },
|
|
||||||
visual_pos: Vec2 = .init(0, 0),
|
visual_pos: Vec2 = .init(0, 0),
|
||||||
|
visual_layer: Layer,
|
||||||
sprite: ?Gfx.Sprite.Id = null,
|
sprite: ?Gfx.Sprite.Id = null,
|
||||||
|
|
||||||
player_direction: Direction = .down,
|
player_direction: Direction = .down,
|
||||||
player_frame_index: u32 = 0,
|
player_frame_index: u32 = 0,
|
||||||
|
|
||||||
|
pos: Point = .{ .x = -1, .y = -1 },
|
||||||
|
box_hole_id: Entity.Id = .nil,
|
||||||
|
|
||||||
const Type = enum {
|
const Type = enum {
|
||||||
nil,
|
nil,
|
||||||
|
|
||||||
@ -97,6 +98,7 @@ const UndoFrame = struct {
|
|||||||
id: Entity.Id,
|
id: Entity.Id,
|
||||||
pos: Point,
|
pos: Point,
|
||||||
player_direction: Direction,
|
player_direction: Direction,
|
||||||
|
box_hole_id: Entity.Id,
|
||||||
};
|
};
|
||||||
|
|
||||||
entities_buffer: [8]EntityState,
|
entities_buffer: [8]EntityState,
|
||||||
@ -125,6 +127,15 @@ const UndoFrame = struct {
|
|||||||
pub fn isEmpty(self: UndoFrame) bool {
|
pub fn isEmpty(self: UndoFrame) bool {
|
||||||
return self.entities_len == 0;
|
return self.entities_len == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn apply(self: *const UndoFrame, world: *World) void {
|
||||||
|
for (self.entities()) |e_state| {
|
||||||
|
var e = world.entities.getAssumeExists(e_state.id);
|
||||||
|
e.pos = e_state.pos;
|
||||||
|
e.player_direction = e_state.player_direction;
|
||||||
|
e.box_hole_id = e_state.box_hole_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const World = struct {
|
const World = struct {
|
||||||
@ -157,16 +168,66 @@ const World = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn findEntity(self: *World, pos: Point, layer: Entity.Layer) Entity.Id {
|
const Iterator = struct {
|
||||||
var entity_iter = self.entities.iterator();
|
iter: Entity.SlotMap.Iterator,
|
||||||
while (entity_iter.next()) |entity_id| {
|
opts: Options,
|
||||||
const entity = self.entities.getAssumeExists(entity_id);
|
|
||||||
if (entity.layer == layer and entity.pos.eql(pos)) {
|
const Options = struct {
|
||||||
return entity_id;
|
pos: ?Point = null,
|
||||||
|
type: ?Entity.Type = null
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn next(self: *Iterator) ?Entity.Id {
|
||||||
|
while (self.iter.next()) |id| {
|
||||||
|
const e = self.iter.slot_map.getAssumeExists(id);
|
||||||
|
if (self.opts.type) |e_type| {
|
||||||
|
if (e.type != e_type) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return .nil;
|
if (self.opts.pos) |pos| {
|
||||||
|
if (!e.pos.eql(pos)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn iterEntities(self: *World, opts: Iterator.Options) Iterator {
|
||||||
|
return Iterator{
|
||||||
|
.iter = self.entities.iterator(),
|
||||||
|
.opts = opts
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn findEntityId(self: *World, opts: Iterator.Options) Entity.Id {
|
||||||
|
var iter = self.iterEntities(opts);
|
||||||
|
return iter.next() orelse .nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn findEntity(self: *World, opts: Iterator.Options) ?*Entity {
|
||||||
|
return self.entities.get(self.findEntityId(opts));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn entityExists(self: *World, opts: Iterator.Options) bool {
|
||||||
|
return self.entities.exists(self.findEntityId(opts));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hasBoxInHole(self: *World, hole_id: Entity.Id) bool {
|
||||||
|
var iter = self.entities.iterator();
|
||||||
|
while (iter.next()) |entity_id| {
|
||||||
|
const entity = self.entities.getAssumeExists(entity_id);
|
||||||
|
if (entity.box_hole_id == hole_id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -179,45 +240,62 @@ const TickContext = struct {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const e = self.world.entities.get(id) orelse return false;
|
const this = self.world.entities.get(id) orelse return false;
|
||||||
|
|
||||||
const next_pos = e.pos.add(dir);
|
const next_pos = this.pos.add(dir);
|
||||||
|
var box_hole_id = this.box_hole_id;
|
||||||
|
|
||||||
if (e.type == .player or e.type == .box) {
|
var iter = self.world.iterEntities(.{ .pos = next_pos });
|
||||||
const destination_entity_id = self.world.findEntity(next_pos, e.layer);
|
while (iter.next()) |other_id| {
|
||||||
if (self.world.entities.get(destination_entity_id)) |destination_entity| {
|
const other = self.world.entities.getAssumeExists(other_id);
|
||||||
if (destination_entity.type == .box) {
|
if (other.type == .wall) {
|
||||||
if (!self.moveEntity(destination_entity_id, dir)) {
|
return false;
|
||||||
|
}
|
||||||
|
if (other.type == .box_hole) {
|
||||||
|
if (!self.world.hasBoxInHole(other_id)) {
|
||||||
|
if (this.type == .box and box_hole_id == Entity.Id.nil) {
|
||||||
|
box_hole_id = other_id;
|
||||||
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (destination_entity.type == .wall) {
|
}
|
||||||
|
if (other.type == .box) {
|
||||||
|
if (other.box_hole_id == Entity.Id.nil) {
|
||||||
|
if (this.type == .player) {
|
||||||
|
if (!self.moveEntity(other_id, dir)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!self.undo_frame.pushEntity(.{
|
if (!self.undo_frame.pushEntity(.{
|
||||||
.id = id,
|
.id = id,
|
||||||
.pos = e.pos,
|
.pos = this.pos,
|
||||||
.player_direction = e.player_direction
|
.player_direction = this.player_direction,
|
||||||
|
.box_hole_id = this.box_hole_id
|
||||||
})) {
|
})) {
|
||||||
log.warn("Failed to push entity undo", .{});
|
log.warn("Failed to push entity undo", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.type == .player) {
|
if (this.type == .player) {
|
||||||
if (dir.x < 0) {
|
if (dir.x < 0) {
|
||||||
e.player_direction = .left;
|
this.player_direction = .left;
|
||||||
} else if (dir.x > 0) {
|
} else if (dir.x > 0) {
|
||||||
e.player_direction = .right;
|
this.player_direction = .right;
|
||||||
} else if (dir.y > 0) {
|
} else if (dir.y > 0) {
|
||||||
e.player_direction = .down;
|
this.player_direction = .down;
|
||||||
} else if (dir.y < 0) {
|
} else if (dir.y < 0) {
|
||||||
e.player_direction = .up;
|
this.player_direction = .up;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
e.pos = next_pos;
|
this.pos = next_pos;
|
||||||
|
this.box_hole_id = box_hole_id;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -237,6 +315,7 @@ roboto_font: Font,
|
|||||||
tilesheet: Tilesheet,
|
tilesheet: Tilesheet,
|
||||||
|
|
||||||
player_sprites: PlayerSprites,
|
player_sprites: PlayerSprites,
|
||||||
|
hole_with_box_sprite: Gfx.Sprite.Id,
|
||||||
animation_timer: Platform.Nanoseconds,
|
animation_timer: Platform.Nanoseconds,
|
||||||
|
|
||||||
footstep_buffer: Audio.BufferId,
|
footstep_buffer: Audio.BufferId,
|
||||||
@ -307,6 +386,7 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
|||||||
.animation_timer = 0,
|
.animation_timer = 0,
|
||||||
.tilesheet = tilesheet,
|
.tilesheet = tilesheet,
|
||||||
.footstep_buffer = footstep_sound,
|
.footstep_buffer = footstep_sound,
|
||||||
|
.hole_with_box_sprite = tilesheet.get(6, 4),
|
||||||
.sfx_bus = Audio.addBus(.{ .label = "sfx" }),
|
.sfx_bus = Audio.addBus(.{ .label = "sfx" }),
|
||||||
.world = undefined,
|
.world = undefined,
|
||||||
.levels = .empty,
|
.levels = .empty,
|
||||||
@ -409,7 +489,7 @@ fn loadLevel(
|
|||||||
|
|
||||||
_ = level.world.spawnEntity(Entity{
|
_ = level.world.spawnEntity(Entity{
|
||||||
.type = .nil,
|
.type = .nil,
|
||||||
.layer = .ground,
|
.visual_layer = .ground,
|
||||||
.pos = .{
|
.pos = .{
|
||||||
.x = @intCast(ox),
|
.x = @intCast(ox),
|
||||||
.y = @intCast(oy),
|
.y = @intCast(oy),
|
||||||
@ -435,6 +515,7 @@ fn loadLevel(
|
|||||||
entity_type = .coin;
|
entity_type = .coin;
|
||||||
} else if (std.mem.eql(u8, props_type, "box_hole")) {
|
} else if (std.mem.eql(u8, props_type, "box_hole")) {
|
||||||
entity_type = .box_hole;
|
entity_type = .box_hole;
|
||||||
|
layer = .on_ground;
|
||||||
} else if (std.mem.eql(u8, props_type, "box_goal")) {
|
} else if (std.mem.eql(u8, props_type, "box_goal")) {
|
||||||
entity_type = .box_goal;
|
entity_type = .box_goal;
|
||||||
layer = .on_ground;
|
layer = .on_ground;
|
||||||
@ -445,7 +526,7 @@ fn loadLevel(
|
|||||||
if (entity_type != null) {
|
if (entity_type != null) {
|
||||||
_ = level.world.spawnEntity(Entity{
|
_ = level.world.spawnEntity(Entity{
|
||||||
.type = entity_type.?,
|
.type = entity_type.?,
|
||||||
.layer = layer,
|
.visual_layer = layer,
|
||||||
.pos = .{
|
.pos = .{
|
||||||
.x = @intCast(ox),
|
.x = @intCast(ox),
|
||||||
.y = @intCast(oy),
|
.y = @intCast(oy),
|
||||||
@ -511,11 +592,7 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
|
|||||||
|
|
||||||
if (input.undo) {
|
if (input.undo) {
|
||||||
if (self.undo_stack.pop()) |undo_frame| {
|
if (self.undo_stack.pop()) |undo_frame| {
|
||||||
for (undo_frame.entities()) |e_state| {
|
undo_frame.apply(&self.world);
|
||||||
var e = self.world.entities.getAssumeExists(e_state.id);
|
|
||||||
e.pos = e_state.pos;
|
|
||||||
e.player_direction = e_state.player_direction;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -546,11 +623,9 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
|
|||||||
} else if (entity.type == .box_goal) {
|
} else if (entity.type == .box_goal) {
|
||||||
goal_count += 1;
|
goal_count += 1;
|
||||||
|
|
||||||
if (self.world.entities.get(self.world.findEntity(entity.pos, .surface))) |above_entity| {
|
if (self.world.entityExists(.{ .pos = entity.pos, .type = .box })) {
|
||||||
if (above_entity.type == .box) {
|
|
||||||
box_at_goal_count += 1;
|
box_at_goal_count += 1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if (entity.type == .box) {
|
} else if (entity.type == .box) {
|
||||||
interpolate_pos = true;
|
interpolate_pos = true;
|
||||||
}
|
}
|
||||||
@ -591,13 +666,33 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
|
|||||||
var entity_iter = self.world.entities.iterator();
|
var entity_iter = self.world.entities.iterator();
|
||||||
while (entity_iter.next()) |entity_id| {
|
while (entity_iter.next()) |entity_id| {
|
||||||
const entity = self.world.entities.getAssumeExists(entity_id);
|
const entity = self.world.entities.getAssumeExists(entity_id);
|
||||||
if (entity.layer != layer) {
|
if (entity.visual_layer != layer) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity.sprite) |sprite| {
|
var sprite = entity.sprite;
|
||||||
|
|
||||||
|
const box_hole_margin = 0.2;
|
||||||
|
if (entity.type == .box) {
|
||||||
|
const box_hole_id = self.world.findEntityId(.{ .type = .box_hole, .pos = entity.pos });
|
||||||
|
if (self.world.entities.get(box_hole_id)) |box_hole| {
|
||||||
|
if (entity.box_hole_id == box_hole_id and box_hole.visual_pos.distance(entity.visual_pos) < box_hole_margin) {
|
||||||
|
sprite = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (entity.type == .box_hole) {
|
||||||
|
var box_iter = self.world.iterEntities(.{ .type = .box, .pos = entity.pos });
|
||||||
|
while (box_iter.next()) |box_id| {
|
||||||
|
const box = self.world.entities.getAssumeExists(box_id);
|
||||||
|
if (box.visual_pos.distance(entity.visual_pos) < box_hole_margin) {
|
||||||
|
sprite = self.hole_with_box_sprite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sprite != null) {
|
||||||
Gfx.drawSprite(
|
Gfx.drawSprite(
|
||||||
sprite,
|
sprite.?,
|
||||||
entity.visual_pos.multiply(tile_size),
|
entity.visual_pos.multiply(tile_size),
|
||||||
tile_size,
|
tile_size,
|
||||||
.white
|
.white
|
||||||
@ -610,6 +705,7 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
|
|||||||
self.current_level += 1;
|
self.current_level += 1;
|
||||||
self.world.deinit(plt.gpa);
|
self.world.deinit(plt.gpa);
|
||||||
self.world = try self.levels.items[self.current_level].world.clone(plt.gpa);
|
self.world = try self.levels.items[self.current_level].world.clone(plt.gpa);
|
||||||
|
self.undo_stack.clearRetainingCapacity();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user