0457-Bomb-新增地图模块
环境
- Time 2024-03-22
- Zig 0.12.0-dev.3152+90c1a2c41
- WSL-Ubuntu 22.04.3 LTS
- raylib 5.0
前言
说明
参考资料:
- 《游戏开发:世嘉新人培训教材》
- 图片素材:https://www.ituring.com.cn/book/1742
目标
随着 map.zig 中的代码越来越多,新增一个 map 模块,来存储地图相关的操作。
map.zig
const std = @import("std");
const engine = @import("engine.zig");
const core = @import("map/core.zig");
const world = @import("map/world.zig");
const Player = @import("map/player.zig").Player;
pub const Direction = @import("map/player.zig").Direction;
pub fn init() void {
core.init();
}
pub fn deinit() void {
core.deinit();
}
const stageConfig = [_]core.StageConfig{
.{ .enemy = 2, .brickRate = 90, .power = 4, .bomb = 6 },
.{ .enemy = 3, .brickRate = 80, .power = 1, .bomb = 0 },
.{ .enemy = 6, .brickRate = 30, .power = 0, .bomb = 1 },
};
var maxBombNumber: usize = 1;
pub const Map = struct {
world: world.World,
pub fn init(level: usize) ?Map {
_ = level;
var initWorld = world.World.init(stageConfig[0]) orelse return null;
initWorld.players[0] = Player.genPlayer(1, 1);
return Map{ .world = initWorld };
}
pub fn update(self: *Map) void {
self.world.update();
}
pub fn player1(self: Map) *Player {
return &self.world.players[0];
}
pub fn control(self: Map, speed: usize, direction: Direction) void {
if (direction == .west) {
var p1 = self.world.players[0];
p1.x -|= speed;
if (!self.isCollisionX(p1, p1.getCell().x -| 1, p1.getCell().y))
self.world.players[0].x -|= speed;
}
if (direction == .east) {
var p1 = self.world.players[0];
p1.x += speed;
if (!self.isCollisionX(p1, p1.getCell().x + 1, p1.getCell().y))
self.world.players[0].x +|= speed;
}
if (direction == .north) {
var p1 = self.world.players[0];
p1.y -|= speed;
if (!self.isCollisionY(p1, p1.getCell().x, p1.getCell().y -| 1))
self.world.players[0].y -|= speed;
}
if (direction == .south) {
var p1 = self.world.players[0];
p1.y += speed;
if (!self.isCollisionY(p1, p1.getCell().x, p1.getCell().y + 1))
self.world.players[0].y += speed;
}
}
fn isCollisionX(self: Map, player: Player, x: usize, y: usize) bool {
const rect = player.toCollisionRec();
for (0..3) |i| {
if (self.world.isCollision(x, y + i -| 1, rect)) return true;
} else return false;
}
fn isCollisionY(self: Map, player: Player, x: usize, y: usize) bool {
const rect = player.toCollisionRec();
for (0..3) |i| {
if (self.world.isCollision(x + i -| 1, y, rect)) return true;
} else return false;
}
pub fn setBomb(self: *Map, player: *Player) void {
if (player.bombNumber >= maxBombNumber) return;
const pos = player.getCell();
const cell = self.world.indexRef(pos.x, pos.y);
if (!cell.contains(.wall) and !cell.contains(.brick)) {
cell.insertTimedType(.bomb, engine.time());
}
}
pub fn draw(self: Map) void {
self.world.draw();
}
pub fn deinit(self: *Map) void {
self.world.deinit();
}
};
world.zig
const std = @import("std");
const engine = @import("../engine.zig");
const core = @import("core.zig");
const Player = @import("player.zig").Player;
fn genMap(world: *World, config: core.StageConfig) void {
var bricks: [core.getSize()]usize = undefined;
var brickNumber: usize = 0;
var floors: [core.getSize()]usize = undefined;
var floorNumber: usize = 0;
for (0..world.height) |y| {
for (0..world.width) |x| {
world.data[x + y * world.width] = if (core.isFixWall(x, y))
core.MapUnit.init(.wall)
else if (core.isFixSpace(x, y))
core.MapUnit.init(.space)
else if (engine.random(100) < config.brickRate) label: {
bricks[brickNumber] = x << 16 | y;
brickNumber += 1;
break :label core.MapUnit.init(.brick);
} else label: {
floors[floorNumber] = x << 16 | y;
floorNumber += 1;
break :label core.MapUnit.init(.space);
};
}
}
genItem(world, bricks[0..brickNumber], config);
genPlayer(world, floors[0..floorNumber], config);
}
fn genItem(self: *World, bricks: []usize, cfg: core.StageConfig) void {
for (0..cfg.bomb + cfg.power) |i| {
const swapped = engine.randomW(i, bricks.len);
const tmp = bricks[i];
bricks[i] = bricks[swapped];
bricks[swapped] = tmp;
const x = bricks[i] >> 16 & 0xFFFF;
const item: core.MapType = if (i < cfg.power) .power else .item;
self.data[x + (bricks[i] & 0xFFFF) * self.width].insert(item);
}
}
fn genPlayer(world: *World, floors: []usize, cfg: core.StageConfig) void {
for (0..cfg.enemy) |i| {
const swapped = engine.randomW(i, floors.len);
const tmp = floors[i];
floors[i] = floors[swapped];
floors[swapped] = tmp;
const x = floors[i] >> 16 & 0xFFFF;
world.players[1 + i] = Player.genEnemy(x, floors[i] & 0xFFFF);
}
}
pub const World = struct {
width: usize = core.getWidth(),
height: usize = core.getHeight(),
unit: usize,
data: []core.MapUnit = core.getMapData(),
players: []Player,
pub fn init(config: core.StageConfig) ?World {
const number = config.enemy + 1;
const players = engine.allocator.alloc(Player, number) catch |e| {
std.log.info("create players error: {}", .{e});
return null;
};
var map = World{ .unit = core.getMapUnit(), .players = players };
genMap(&map, config);
return map;
}
pub fn update(self: *World) void {
const time = engine.time();
for (self.data, 0..) |*value, idx| {
if (value.contains(.bomb)) {
if (time > value.time + 3000) {
self.explosion(value, idx);
}
}
if (value.contains(.explosion)) {
if (time > value.time + 700) {
value.remove(.explosion);
}
}
if (value.contains(.fireX)) {
if (time > value.time + 700) {
value.remove(.fireX);
}
}
if (value.contains(.fireY)) {
if (time > value.time + 700) {
value.remove(.fireY);
}
}
}
}
fn explosion(self: *World, mapUnit: *core.MapUnit, idx: usize) void {
const time = engine.time();
mapUnit.remove(.bomb);
mapUnit.insertTimedType(.explosion, time);
// 左
doExplosion(&self.data[idx -| 1], .fireX, time);
// 右
doExplosion(&self.data[idx + 1], .fireX, time);
// 上
doExplosion(&self.data[idx - self.width], .fireY, time);
// 下
doExplosion(&self.data[idx + self.width], .fireY, time);
}
fn doExplosion(mapUnit: *core.MapUnit, mapType: core.MapType, time: usize) void {
if (mapUnit.contains(.wall)) return;
if (mapUnit.contains(.brick)) mapUnit.remove(.brick);
mapUnit.insertTimedType(mapType, time);
}
pub fn isCollision(self: World, x: usize, y: usize, rect: engine.Rectangle) bool {
const cell = self.index(x, y);
if (!cell.contains(.wall) and !cell.contains(.brick)) return false;
const rec = engine.Rectangle{ .x = x, .y = y, .width = 1, .height = 1 };
return engine.isCollision(rec.scale(self.unit), rect);
}
pub fn draw(self: World) void {
for (0..self.height) |y| {
for (0..self.width) |x| {
self.data[x + y * self.width].draw(x, y);
}
}
for (self.players) |value| value.draw();
}
fn index(self: World, x: usize, y: usize) core.MapUnit {
return self.data[x + y * self.width];
}
pub fn indexRef(self: *World, x: usize, y: usize) *core.MapUnit {
return &self.data[x + y * self.width];
}
pub fn size(self: World) usize {
return self.width * self.height;
}
pub fn deinit(self: World) void {
engine.allocator.free(self.players);
}
};
player.zig
const std = @import("std");
const engine = @import("../engine.zig");
const core = @import("core.zig");
pub const Direction = enum { north, south, west, east };
const speedUnit = 1000;
pub const Player = struct {
x: usize,
y: usize,
bombNumber: usize = 0,
type: core.MapType,
pub fn genEnemy(x: usize, y: usize) Player {
return init(x, y, .enemy);
}
pub fn genPlayer(x: usize, y: usize) Player {
return init(x, y, .player1);
}
fn init(x: usize, y: usize, t: core.MapType) Player {
return Player{
.x = x * core.getMapUnit() * speedUnit,
.y = y * core.getMapUnit() * speedUnit,
.type = t,
};
}
pub fn getCell(self: Player) engine.Vector {
const unit = core.getMapUnit();
return .{
.x = (self.x / speedUnit + (unit / 2)) / unit,
.y = (self.y / speedUnit + (unit / 2)) / unit,
};
}
pub fn draw(self: Player) void {
const x = self.x / speedUnit;
core.drawXY(self.type, x, self.y / speedUnit);
}
pub fn toCollisionRec(self: Player) engine.Rectangle {
return engine.Rectangle{
.x = self.x / speedUnit + 5,
.y = self.y / speedUnit + 5,
.width = core.getMapUnit() - 10,
.height = core.getMapUnit() - 7,
};
}
};
core.zig
const std = @import("std");
const engine = @import("../engine.zig");
var tilemap: engine.Tilemap = undefined;
pub fn init() void {
tilemap = engine.Tilemap.init("map.png", 32);
}
pub fn deinit() void {
tilemap.deinit();
}
pub const MapType = enum(u8) {
space = 9,
wall = 7,
brick = 8,
item = 2,
power = 3,
bomb = 10,
fireX = 4,
fireY = 5,
explosion = 11,
player1 = 1,
player2 = 0,
enemy = 6,
};
pub const StageConfig = struct {
enemy: usize,
brickRate: usize,
power: usize,
bomb: usize,
};
const width = 19;
const height = 15;
var data: [width * height]MapUnit = undefined;
pub fn getWidth() usize {
return width;
}
pub fn getHeight() usize {
return height;
}
pub fn getSize() usize {
return getWidth() * getHeight();
}
pub fn getMapData() []MapUnit {
return &data;
}
pub fn getMapUnit() usize {
return tilemap.unit;
}
pub fn isFixWall(x: usize, y: usize) bool {
if (x == 0 or y == 0) return true;
if (x == width - 1 or y == height - 1) return true;
if (x % 2 == 0 and y % 2 == 0) return true;
return false;
}
pub fn isFixSpace(x: usize, y: usize) bool {
return y + x < 4;
}
fn drawTile(mapType: MapType, x: usize, y: usize) void {
tilemap.drawTile(@intFromEnum(mapType), x, y);
}
pub fn drawXY(mapType: MapType, x: usize, y: usize) void {
tilemap.drawXY(@intFromEnum(mapType), x, y);
}
const MapTypes = std.enums.EnumSet(MapType);
pub const MapUnit = struct {
mapTypes: MapTypes,
time: usize = std.math.maxInt(usize),
pub fn init(mapType: MapType) MapUnit {
return .{ .mapTypes = MapTypes.initOne(mapType) };
}
pub fn contains(self: MapUnit, mapType: MapType) bool {
return self.mapTypes.contains(mapType);
}
pub fn remove(self: *MapUnit, mapType: MapType) void {
self.mapTypes.remove(mapType);
}
pub fn insert(self: *MapUnit, mapType: MapType) void {
self.mapTypes.insert(mapType);
}
pub fn insertTimedType(self: *MapUnit, mapType: MapType, time: usize) void {
self.insert(mapType);
self.time = time;
}
pub fn draw(self: MapUnit, x: usize, y: usize) void {
if (self.contains(.wall)) return drawTile(.wall, x, y);
if (self.contains(.brick)) return drawTile(.brick, x, y);
drawTile(.space, x, y);
if (self.contains(.power)) drawTile(.power, x, y);
if (self.contains(.item)) drawTile(.item, x, y);
if (self.contains(.bomb)) drawTile(.bomb, x, y);
if (self.contains(.explosion)) drawTile(.explosion, x, y);
if (self.contains(.fireX)) drawTile(.fireX, x, y);
if (self.contains(.fireY)) drawTile(.fireY, x, y);
}
};
效果
效果和之前一致,没有增加新的功能。

总结
新增了地图模块,包含了 map.zig,world.zig,player.zig 和 core.zig。

浙公网安备 33010602011771号