0457-Bomb-新增地图模块

环境

  • Time 2024-03-22
  • Zig 0.12.0-dev.3152+90c1a2c41
  • WSL-Ubuntu 22.04.3 LTS
  • raylib 5.0

前言

说明

参考资料:

  1. 《游戏开发:世嘉新人培训教材》
  2. 图片素材: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);
    }
};

效果

效果和之前一致,没有增加新的功能。

bomb

总结

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

附录

posted @ 2026-08-16 08:55  jiangbo4444  阅读(5)  评论(0)    收藏  举报