Added exit/completion of the game/level.

This commit is contained in:
Sander Schobers 2023-06-05 05:56:54 +02:00
parent bbd8a56209
commit f97496a343
9 changed files with 77 additions and 34 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -7,6 +7,8 @@ pub const Object = @import("engine/object.zig").Object;
pub const OpaquePtr = @import("engine/opaque_ptr.zig").OpaquePtr; pub const OpaquePtr = @import("engine/opaque_ptr.zig").OpaquePtr;
pub const Point = @import("engine/point.zig").Point; pub const Point = @import("engine/point.zig").Point;
pub const PointF = @import("engine/point_f.zig").PointF; pub const PointF = @import("engine/point_f.zig").PointF;
pub const pt = Point.init;
pub const ptF = PointF.init;
pub const Rectangle = @import("engine/rectangle.zig").Rectangle; pub const Rectangle = @import("engine/rectangle.zig").Rectangle;
pub const RectangleF = @import("engine/rectangle_f.zig").RectangleF; pub const RectangleF = @import("engine/rectangle_f.zig").RectangleF;
pub const Scene = @import("engine/scene.zig").Scene; pub const Scene = @import("engine/scene.zig").Scene;

View File

@ -5,6 +5,7 @@ pub const Point = struct {
x: i64, x: i64,
y: i64, y: i64,
pub const One = Point{ .x = 1, .y = 1 };
pub const Zero = Point{ .x = 0, .y = 0 }; pub const Zero = Point{ .x = 0, .y = 0 };
pub fn init(x: i64, y: i64) Point { pub fn init(x: i64, y: i64) Point {

View File

@ -6,6 +6,8 @@ pub const PointF = struct {
x: f32, x: f32,
y: f32, y: f32,
pub const Half = PointF{ .x = 0.5, .y = 0.5 };
pub const One = PointF{ .x = 1, .y = 1 };
pub const Zero = PointF{ .x = 0, .y = 0 }; pub const Zero = PointF{ .x = 0, .y = 0 };
pub fn init(x: f32, y: f32) PointF { pub fn init(x: f32, y: f32) PointF {

View File

@ -12,12 +12,18 @@ pub const Game = struct {
Right, Right,
}; };
pub const State = enum {
InProgress,
Over,
Completed,
};
pub const TileSize = 32; pub const TileSize = 32;
prng: std.rand.DefaultPrng = std.rand.DefaultPrng.init(0), prng: std.rand.DefaultPrng = std.rand.DefaultPrng.init(0),
level: Level, level: Level,
isOver: bool = false, state: State = .InProgress,
starsCollected: usize = 0, starsCollected: usize = 0,
starsTotal: usize, starsTotal: usize,
@ -70,7 +76,7 @@ pub const Game = struct {
} }
pub fn drawSpriteFrame(self: *Game, spriteName: []const u8, frame: usize, x: i64, y: i64) void { 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()); self.drawSpriteFrameP(spriteName, frame, engine.pt(x, y).float());
} }
pub fn drawSpriteFrameP(self: *Game, spriteName: []const u8, frame: usize, position: engine.PointF) void { pub fn drawSpriteFrameP(self: *Game, spriteName: []const u8, frame: usize, position: engine.PointF) void {
@ -87,13 +93,13 @@ pub const Game = struct {
} }
pub fn movePlayer(self: *Game, dt: f32) void { pub fn movePlayer(self: *Game, dt: f32) void {
if (self.isOver) return; if (self.state != .InProgress) return;
const to = self.playerPosition.add(self.playerVelocity.multiply(dt)); const to = self.playerPosition.add(self.playerVelocity.multiply(dt));
self.playerPosition = to; self.playerPosition = to;
if (self.playerPosition.y > 21.5) { if (self.playerPosition.y > 21.5) {
self.isOver = true; self.state = .Over;
return; return;
} }
@ -124,21 +130,22 @@ pub const Game = struct {
} }
self.playerDirection = if (self.playerVelocity.x == 0) self.playerDirection else if (self.playerVelocity.x > 0) Direction.Right else Direction.Left; self.playerDirection = if (self.playerVelocity.x == 0) self.playerDirection else if (self.playerVelocity.x > 0) Direction.Right else Direction.Left;
const playerCenter = self.playerPosition.add(engine.PointF.init(0.5, 0.5)).floor().integer(); const playerCenter = self.playerPosition.add(engine.PointF.Half).floor().integer();
var dx: i64 = -1; var dx: i64 = -1;
while (dx <= 1) : (dx += 1) { while (dx <= 1) : (dx += 1) {
var dy: i64 = -1; var dy: i64 = -1;
while (dy <= 1) : (dy += 1) { while (dy <= 1) : (dy += 1) {
const x = playerCenter.x + dx; const x = playerCenter.x + dx;
const y = playerCenter.y + dy; const y = playerCenter.y + dy;
const collectablePosition = engine.Point.init(x, y); const collectablePosition = engine.pt(x, y);
if (self.level.collectables.get(x, y)) |collectable| { if (self.level.collectables.get(x, y)) |collectable| {
if (self.collected.hasValue(x, y, true)) continue; if (self.collected.hasValue(x, y, true)) continue;
if (self.playerPosition.distance2(collectablePosition.float()) > 1.5) continue; if (self.playerPosition.distance2(collectablePosition.float()) > 1.5) continue;
switch (collectable) { switch (collectable) {
Level.Collectable.Star => { .Exit => self.state = .Completed,
.Star => {
self.starsCollected += 1; self.starsCollected += 1;
self.collected.set(x, y, true) catch {}; self.collected.set(x, y, true) catch {};
}, },
@ -177,25 +184,25 @@ pub const Game = struct {
// return values are in view coordinates // return values are in view coordinates
pub fn tile(self: Game, x: f32, y: f32) engine.PointF { pub fn tile(self: Game, x: f32, y: f32) engine.PointF {
return self.tileP(engine.PointF.init(x, y)); return self.tileP(engine.ptF(x, y));
} }
pub fn tileCenter(self: Game, x: i64, y: i64) engine.PointF { pub fn tileCenter(self: Game, x: i64, y: i64) engine.PointF {
return self.tileCenterP(engine.Point.init(x, y)); return self.tileCenterP(engine.pt(x, y));
} }
pub fn tileCenterP(self: Game, position: engine.Point) engine.PointF { pub fn tileCenterP(self: Game, position: engine.Point) engine.PointF {
const center = engine.PointF.init(0.5, 0.5); const center = engine.PointF.Half;
return self.tileP(position.float().add(center)); return self.tileP(position.float().add(center));
} }
pub fn tileP(self: Game, position: engine.PointF) engine.PointF { pub fn tileP(self: Game, position: engine.PointF) engine.PointF {
const relative = position.subtract(self.boundsInTiles.min); const relative = position.subtract(self.boundsInTiles.min);
return engine.PointF.init(relative.x / self.boundsInTiles.width(), relative.y / self.boundsInTiles.height()); return engine.ptF(relative.x / self.boundsInTiles.width(), relative.y / self.boundsInTiles.height());
} }
pub fn tileTopLeft(self: Game, x: i64, y: i64) engine.PointF { pub fn tileTopLeft(self: Game, x: i64, y: i64) engine.PointF {
return self.tileTopLeftP(engine.Point.init(x, y)); return self.tileTopLeftP(engine.pt(x, y));
} }
pub fn tileTopLeftP(self: Game, p: engine.Point) engine.PointF { pub fn tileTopLeftP(self: Game, p: engine.Point) engine.PointF {

View File

@ -8,6 +8,7 @@ pub const Level = struct {
}; };
pub const Collectable = enum { pub const Collectable = enum {
Exit,
Star, Star,
}; };
@ -44,9 +45,10 @@ pub const Level = struct {
for (line, 0..) |tile, column| { for (line, 0..) |tile, column| {
var x = @intCast(i64, column); var x = @intCast(i64, column);
switch (tile) { switch (tile) {
'P' => character = engine.Point.init(x, y), 'P' => character = engine.pt(x, y),
'S' => try collectables.set(x, y, Collectable.Star), 'S' => try collectables.set(x, y, .Star),
'x' => try tiles.set(x, y, Tile.Grass), 'E' => try collectables.set(x, y, .Exit),
'x' => try tiles.set(x, y, .Grass),
else => {}, else => {},
} }
} }

View File

@ -24,7 +24,7 @@ pub const GameScene = struct {
} }
pub fn handle(self: *GameScene, ctx: *Context, event: *allegro.Event) !void { pub fn handle(self: *GameScene, ctx: *Context, event: *allegro.Event) !void {
if (self.game.isOver) { if (self.game.state != .InProgress) {
switch (event.type) { switch (event.type) {
allegro.c.ALLEGRO_EVENT_KEY_DOWN => { allegro.c.ALLEGRO_EVENT_KEY_DOWN => {
switch (event.keyboard.keycode) { switch (event.keyboard.keycode) {
@ -50,7 +50,7 @@ pub const GameScene = struct {
const maxFallVelocity: f32 = 23; const maxFallVelocity: f32 = 23;
const maxHorizontalWalkVelocity: f32 = 7; // tiles/s const maxHorizontalWalkVelocity: f32 = 7; // tiles/s
if (self.game.isOver) { if (self.game.state != .InProgress) {
self.game.playerIdleAnimation.tick(t); self.game.playerIdleAnimation.tick(t);
return; return;
} }
@ -116,13 +116,16 @@ pub const GameScene = struct {
const randomDirtOffset = self.game.randomTileOffset(x, 99, 3); const randomDirtOffset = self.game.randomTileOffset(x, 99, 3);
self.game.drawSpriteFrame("tiles_dirt_32", 0 + randomDirtOffset, x, 19); self.game.drawSpriteFrame("tiles_dirt_32", 0 + randomDirtOffset, x, 19);
self.game.drawSpriteFrame("tiles_dirt_32", 3 + randomDirtOffset, x, 20); self.game.drawSpriteFrame("tiles_dirt_32", 3 + randomDirtOffset, x, 20);
}
var y = tileBounds.min.y; var y = tileBounds.min.y;
while (y <= tileBounds.max.y) : (y += 1) { while (y <= tileBounds.max.y) : (y += 1) {
x = tileBounds.min.x;
while (x < tileBounds.max.x) : (x += 1) {
if (tiles.get(x, y)) |tile| { if (tiles.get(x, y)) |tile| {
switch (tile) { switch (tile) {
game.Level.Tile.Grass => { .Grass => {
const ordinals = tiles.getOrdinals(x, y, game.Level.Tile.Grass); const ordinals = tiles.getOrdinals(x, y, .Grass);
var offset: usize = 17; var offset: usize = 17;
switch (ordinals.count()) { switch (ordinals.count()) {
1 => { 1 => {
@ -156,10 +159,14 @@ pub const GameScene = struct {
if (self.game.collected.hasValue(x, y, true)) continue; if (self.game.collected.hasValue(x, y, true)) continue;
switch (collectable) { switch (collectable) {
game.Level.Collectable.Star => { .Star => {
const distanceToPlayer = engine.Point.init(x, y).float().distance(self.game.playerPosition); const distanceToPlayer = engine.pt(x, y).float().distance(self.game.playerPosition);
self.game.drawSpriteFrame("item_star_32", self.game.starAnimation.currentOffset(@floatToInt(usize, @mod(distanceToPlayer * 0.54, 1) * 16)), x, y); self.game.drawSpriteFrame("item_star_32", self.game.starAnimation.currentOffset(@floatToInt(usize, @mod(distanceToPlayer * 0.54, 1) * 16)), x, y);
}, },
.Exit => {
const position = self.game.tileP(engine.pt(x, y).float().subtract(engine.ptF(0.5, 1)));
renderer.drawTextureV("exit_portal", position.x, position.y);
},
} }
} }
} }
@ -167,11 +174,11 @@ pub const GameScene = struct {
const playerDirectionFrameOffset: usize = if (self.game.playerDirection == game.Game.Direction.Left) 0 else 12; const playerDirectionFrameOffset: usize = if (self.game.playerDirection == game.Game.Direction.Left) 0 else 12;
if (self.game.playerVelocity.y != 0) { if (self.game.playerVelocity.y != 0) {
self.game.drawSpriteFrameP("character_lion_48", self.game.playerFallingAnimation.current + playerDirectionFrameOffset, self.game.playerPosition.add(engine.PointF.init(-0.25, -0.25))); self.game.drawSpriteFrameP("character_lion_48", self.game.playerFallingAnimation.current + playerDirectionFrameOffset, self.game.playerPosition.add(engine.ptF(-0.25, -0.25)));
} else if (self.game.playerVelocity.x != 0 and self.game.playerVelocity.y == 0) { } else if (self.game.playerVelocity.x != 0 and self.game.playerVelocity.y == 0) {
self.game.drawSpriteFrameP("character_lion_48", self.game.playerWalkingAnimation.current + playerDirectionFrameOffset, self.game.playerPosition.add(engine.PointF.init(-0.25, -0.25))); self.game.drawSpriteFrameP("character_lion_48", self.game.playerWalkingAnimation.current + playerDirectionFrameOffset, self.game.playerPosition.add(engine.ptF(-0.25, -0.25)));
} else { } else {
self.game.drawSpriteFrameP("character_lion_48", self.game.playerIdleAnimation.current + playerDirectionFrameOffset, self.game.playerPosition.add(engine.PointF.init(-0.25, -0.25))); self.game.drawSpriteFrameP("character_lion_48", self.game.playerIdleAnimation.current + playerDirectionFrameOffset, self.game.playerPosition.add(engine.ptF(-0.25, -0.25)));
} }
ctx.renderer.printTextV("default", ctx.palette.background.text, 0.01, 0.01, .Left, "Stars collected: {d}", .{self.game.starsCollected}); ctx.renderer.printTextV("default", ctx.palette.background.text, 0.01, 0.01, .Left, "Stars collected: {d}", .{self.game.starsCollected});
@ -181,15 +188,25 @@ pub const GameScene = struct {
ctx.renderer.drawTintedSpriteFrameV("pie_charts_24", percentage - 1, ctx.palette.background.text, 0.97, 0.01); ctx.renderer.drawTintedSpriteFrameV("pie_charts_24", percentage - 1, ctx.palette.background.text, 0.97, 0.01);
} }
if (self.game.isOver) { const textColor = allegro.mapRgb(0x48, 0x91, 0x00);
const textColor = allegro.mapRgb(0x48, 0x91, 0x00);
if (self.game.state != .InProgress) {
ctx.renderer.textures.get("opaque").?.drawTintedScaled(allegro.mapRgba(0, 0, 0, 191), 0, 0, @intToFloat(f32, ctx.renderer.display.width()), @intToFloat(f32, ctx.renderer.display.height())); ctx.renderer.textures.get("opaque").?.drawTintedScaled(allegro.mapRgba(0, 0, 0, 191), 0, 0, @intToFloat(f32, ctx.renderer.display.width()), @intToFloat(f32, ctx.renderer.display.height()));
ctx.renderer.drawTextV("default", textColor, 0.5, 0.2, .Center, "Game over");
ctx.renderer.drawSpriteFrameV("text_balloons", 0, 0.48, 0.40); if (self.game.state == .Over) {
ctx.renderer.drawTextV("small", ctx.palette.background.text, 0.547, 0.436, .Center, "Do you want to"); ctx.renderer.drawTextV("default", textColor, 0.5, 0.2, .Center, "Game over");
ctx.renderer.drawTextV("small", ctx.palette.background.text, 0.547, 0.465, .Center, "try again?"); }
ctx.renderer.drawSpriteFrameV("character_lion_48", self.game.playerIdleAnimation.current + 12, 0.44, 0.57); if (self.game.state == .Completed) {
ctx.renderer.drawTextV("small", textColor, 0.5, 0.7, .Center, "[enter] try again | [escape] quit"); ctx.renderer.drawTextV("default", textColor, 0.5, 0.2, .Center, "Level completed");
}
if (self.game.starsCollected < self.game.starsTotal) {
ctx.renderer.drawSpriteFrameV("text_balloons", 0, 0.45, 0.40);
ctx.renderer.drawTextV("small", ctx.palette.background.text, 0.517, 0.436, .Center, "Do you want to");
ctx.renderer.drawTextV("small", ctx.palette.background.text, 0.517, 0.465, .Center, "try again?");
ctx.renderer.drawSpriteFrameV("character_lion_48", self.game.playerIdleAnimation.current + 12, 0.41, 0.57);
ctx.renderer.drawTextV("small", textColor, 0.5, 0.7, .Center, "[enter] try again | [escape] quit");
}
} }
if (ctx.showDebug) { if (ctx.showDebug) {

View File

@ -44,6 +44,7 @@ pub fn main() !void {
allegro.setNewBitmapFlags(allegro.NewBitmapFlags{ .MIN_LINEAR = false, .MAG_LINEAR = false }); allegro.setNewBitmapFlags(allegro.NewBitmapFlags{ .MIN_LINEAR = false, .MAG_LINEAR = false });
try renderer.textures.addFromFile("character_lion_48", paths.AssetsDir ++ "/images/character_lion_48.png"); try renderer.textures.addFromFile("character_lion_48", paths.AssetsDir ++ "/images/character_lion_48.png");
try renderer.textures.addFromFile("exit_portal", paths.AssetsDir ++ "/images/exit_portal.png");
try renderer.textures.addFromFile("item_star_32", paths.AssetsDir ++ "/images/item_star_32.png"); try renderer.textures.addFromFile("item_star_32", paths.AssetsDir ++ "/images/item_star_32.png");
try renderer.textures.addFromFile("pie_charts_24", paths.AssetsDir ++ "/images/pie_charts_24.png"); try renderer.textures.addFromFile("pie_charts_24", paths.AssetsDir ++ "/images/pie_charts_24.png");
try renderer.textures.addFromFile("text_balloons", paths.AssetsDir ++ "/images/text_balloons.png"); try renderer.textures.addFromFile("text_balloons", paths.AssetsDir ++ "/images/text_balloons.png");

View File

@ -62,6 +62,17 @@ pub const Renderer = struct {
} }
} }
pub fn drawTexture(self: Renderer, textureName: []const u8, x: f32, y: f32) void {
if (self.textures.get(textureName)) |texture| {
texture.drawScaledUniform(x, y, self.viewport.scale);
}
}
pub fn drawTextureV(self: Renderer, textureName: []const u8, x: f32, y: f32) void {
const screen = self.viewport.viewToScreen(x, y);
self.drawTexture(textureName, 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 { 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); const screen = self.viewport.viewToScreen(x, y);
self.drawText(fontName, color, screen.x, screen.y, alignment, text); self.drawText(fontName, color, screen.x, screen.y, alignment, text);