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