diff --git a/build.zig b/build.zig index 85c2680..05ea10f 100644 --- a/build.zig +++ b/build.zig @@ -2,10 +2,11 @@ const std = @import("std"); const GBABuilder = @import("lib/ZigGBA/GBA/builder.zig"); pub fn build(b: *std.build.Builder) void { - const exe = GBABuilder.addGBAExecutable(b, "first", "src/first.zig"); + const exe = GBABuilder.addGBAExecutable(b, "2d_sprites", "src/2d_sprites.zig"); exe.emit_asm = if (b.option(bool, "asm", "emit assembly") orelse false) .emit else .default; const run_cmd = b.addSystemCommand(&.{"zba"}); + run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| run_cmd.addArgs(args); const run_step = b.step("run", "Run test ROM in ZBA"); diff --git a/src/2d_sprites.zig b/src/2d_sprites.zig new file mode 100644 index 0000000..29a0849 --- /dev/null +++ b/src/2d_sprites.zig @@ -0,0 +1,316 @@ +const std = @import("std"); + +const GBA = @import("gba").GBA; +const Mode3 = @import("gba").Mode3; +const LCD = @import("gba").LCD; +const OAM = @import("gba").OAM; +const Input = @import("gba").Input; + +const SquareSpriteTuple = std.meta.Tuple(&[_]type{ Rainbow, Bebe16, Bebe32, Bebe64 }); + +// TODO: ZigGBA should support spaces in the ROM Title IIRC +export var gameHeader linksection(".gbaheader") = GBA.Header.setup("2DSPRITE", "PAOD", "00", 0); + +// 16x16 4BPP Sprite depicting +// denpa artist Nanahira's pet rabbit: Bebe-chan +const Bebe16 = struct { + const Self = @This(); + + const pal = [8]u32{ + 0x00000000, 0x11B32BAF, 0x7FFF3680, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + }; + + const tiles = [32]u32{ + 0x00000000, 0x00000000, 0x02220000, 0x02222000, 0x22223100, 0x24223310, 0x42233310, 0x32333310, + 0x00000000, 0x00000000, 0x00022200, 0x00232220, 0x01333240, 0x01333324, 0x01333334, 0x00133333, + 0x33333100, 0x13313100, 0x33333510, 0x55555100, 0x55511000, 0x11100000, 0x00000000, 0x00000000, + 0x01553333, 0x05555133, 0x05555513, 0x00555155, 0x00155555, 0x00011111, 0x00000000, 0x00000000, + }; + + fn paletteMode(_: Self) GBA.PaletteMode { + return .Color16; + } + + fn size(_: Self) OAM.ObjectSize { + return .Size16x16; + } + fn load(_: Self) void { + // In Memory, Tile Map is laid out like: 1 2 + // 3 4 + + const half = Self.tiles.len / 2; + + GBA.memcpy32(GBA.SPRITE_VRAM, &Self.tiles[0], half * @sizeOf(u32)); // Top + GBA.memcpy32(GBA.SPRITE_VRAM + (half * 0x20), &Self.tiles[half], half * @sizeOf(u32)); // Bottom + + // Palette + GBA.memcpy32(GBA.OBJ_PALETTE_RAM, &Self.pal, Self.pal.len * @sizeOf(u32)); + } +}; + +// Bebe-chan but bigger +const Bebe32 = struct { + const Self = @This(); + + const pal = [8]u32{ + 0x00000000, 0x11B32BAF, 0x7FFF3680, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + }; + + const tiles = [128]u32{ + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x22000000, 0x22000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00222222, 0x00222222, 0x00222222, 0x00222222, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x22220000, 0x22220000, 0x22222200, 0x22222200, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000022, 0x00000022, 0x00002233, 0x00002233, + 0x33110000, 0x33110000, 0x33331100, 0x33331100, 0x33331100, 0x33331100, 0x33331100, 0x33331100, + 0x22222222, 0x22222222, 0x22442222, 0x22442222, 0x44222233, 0x44222233, 0x33223333, 0x33223333, + 0x33224400, 0x33224400, 0x33332244, 0x33332244, 0x33333344, 0x33333344, 0x33333333, 0x33333333, + 0x00113333, 0x00113333, 0x00113333, 0x00113333, 0x00113333, 0x00113333, 0x00001133, 0x00001133, + + 0x33110000, 0x33110000, 0x33110000, 0x33110000, 0x33551100, 0x33551100, 0x55110000, 0x55110000, + 0x33333333, 0x33333333, 0x11333311, 0x11333311, 0x33333333, 0x33333333, 0x55555555, 0x55555555, + 0x33333333, 0x33333333, 0x55113333, 0x55113333, 0x55551133, 0x55551133, 0x55115555, 0x55115555, + 0x00115555, 0x00115555, 0x00555555, 0x00555555, 0x00555555, 0x00555555, 0x00005555, 0x00005555, + 0x11000000, 0x11000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x55555511, 0x55555511, 0x11111100, 0x11111100, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x55555555, 0x55555555, 0x11111111, 0x11111111, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00001155, 0x00001155, 0x00000011, 0x00000011, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + }; + + fn paletteMode(_: Self) GBA.PaletteMode { + return .Color16; + } + + fn size(_: Self) OAM.ObjectSize { + return .Size32x32; + } + + fn load(_: Self) void { + // In Memory, Tile Map is laid out like: 1 2 3 4 + // 5 6 7 8 + // 9 10 11 12 + // 13 14 15 16 + + const quarter = Self.tiles.len / 4; + + GBA.memcpy32(GBA.SPRITE_VRAM, &Self.tiles[0], quarter * @sizeOf(u32)); + GBA.memcpy32(GBA.SPRITE_VRAM + (quarter * 0x10), &Self.tiles[quarter], quarter * @sizeOf(u32)); + GBA.memcpy32(GBA.SPRITE_VRAM + (quarter * 0x20), &Self.tiles[quarter * 2], quarter * @sizeOf(u32)); + GBA.memcpy32(GBA.SPRITE_VRAM + (quarter * 0x30), &Self.tiles[quarter * 3], quarter * @sizeOf(u32)); + + // Palette + GBA.memcpy32(GBA.OBJ_PALETTE_RAM, &Self.pal, Self.pal.len * @sizeOf(u32)); + } +}; + +// The Biggest Bebe-chan yet +const Bebe64 = struct { + const Self = @This(); + + const pal = [8]u32{ + 0x00000000, 0x11B32BAF, 0x7FFF3680, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + }; + + const tiles = [512]u32{ + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x22220000, 0x22220000, 0x22220000, 0x22220000, + 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, + 0x00002222, 0x00002222, 0x00002222, 0x00002222, 0x00002222, 0x00002222, 0x00002222, 0x00002222, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x22220000, 0x22220000, 0x22220000, 0x22220000, + 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, + 0x00002222, 0x00002222, 0x00002222, 0x00002222, 0x22223333, 0x22223333, 0x22223333, 0x22223333, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x11110000, 0x11110000, 0x11110000, 0x11110000, + 0x33331111, 0x33331111, 0x33331111, 0x33331111, 0x33333333, 0x33333333, 0x33333333, 0x33333333, + 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, + 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22224444, 0x22224444, 0x22224444, 0x22224444, + 0x44440000, 0x44440000, 0x44440000, 0x44440000, 0x22224444, 0x22224444, 0x22224444, 0x22224444, + 0x33332222, 0x33332222, 0x33332222, 0x33332222, 0x33333333, 0x33333333, 0x33333333, 0x33333333, + 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, + 0x00001111, 0x00001111, 0x00001111, 0x00001111, 0x00001111, 0x00001111, 0x00001111, 0x00001111, + + 0x11110000, 0x11110000, 0x11110000, 0x11110000, 0x11110000, 0x11110000, 0x11110000, 0x11110000, + 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, + 0x22223333, 0x22223333, 0x22223333, 0x22223333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, + 0x44442222, 0x44442222, 0x44442222, 0x44442222, 0x33332222, 0x33332222, 0x33332222, 0x33332222, + 0x33334444, 0x33334444, 0x33334444, 0x33334444, 0x33333333, 0x33333333, 0x33333333, 0x33333333, + 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, + 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x11113333, 0x11113333, 0x11113333, 0x11113333, + 0x00001111, 0x00001111, 0x00001111, 0x00001111, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x33331111, 0x33331111, 0x33331111, 0x33331111, 0x33331111, 0x33331111, 0x33331111, 0x33331111, + 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33331111, 0x33331111, 0x33331111, 0x33331111, + 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x11113333, 0x11113333, 0x11113333, 0x11113333, + 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, + 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x55551111, 0x55551111, 0x55551111, 0x55551111, + 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, + 0x00001111, 0x00001111, 0x00001111, 0x00001111, 0x00005555, 0x00005555, 0x00005555, 0x00005555, + + 0x11110000, 0x11110000, 0x11110000, 0x11110000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x33335555, 0x33335555, 0x33335555, 0x33335555, 0x55551111, 0x55551111, 0x55551111, 0x55551111, + 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x55555555, 0x55555555, 0x55555555, 0x55555555, + 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x55555555, 0x55555555, 0x55555555, 0x55555555, + 0x11113333, 0x11113333, 0x11113333, 0x11113333, 0x55555555, 0x55555555, 0x55555555, 0x55555555, + 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55551111, 0x55551111, 0x55551111, 0x55551111, + 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x55555555, + 0x00005555, 0x00005555, 0x00005555, 0x00005555, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x11110000, 0x11110000, 0x11110000, 0x11110000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x55551111, 0x55551111, 0x55551111, 0x55551111, 0x11110000, 0x11110000, 0x11110000, 0x11110000, + 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x11111111, 0x11111111, 0x11111111, 0x11111111, + 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x11111111, 0x11111111, 0x11111111, 0x11111111, + 0x55555555, 0x55555555, 0x55555555, 0x55555555, 0x11111111, 0x11111111, 0x11111111, 0x11111111, + 0x11115555, 0x11115555, 0x11115555, 0x11115555, 0x00001111, 0x00001111, 0x00001111, 0x00001111, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + }; + + fn paletteMode(_: Self) GBA.PaletteMode { + return .Color16; + } + + fn size(_: Self) OAM.ObjectSize { + return .Size64x64; + } + + fn load(_: Self) void { + // In Memory, Tile Map is laid out like: 1 2 3 4 5 6 7 8 + // 9 10 11 12 13 14 15 16 + // 17 18 19 20 21 22 23 24 + // 25 26 27 28 29 30 31 32 + + const eighth = Self.tiles.len / 8; + + GBA.memcpy32(GBA.SPRITE_VRAM, &Self.tiles[0], eighth * @sizeOf(u32)); + GBA.memcpy32(GBA.SPRITE_VRAM + (eighth * 0x8), &Self.tiles[eighth], eighth * @sizeOf(u32)); + GBA.memcpy32(GBA.SPRITE_VRAM + (eighth * 0x10), &Self.tiles[eighth * 2], eighth * @sizeOf(u32)); + GBA.memcpy32(GBA.SPRITE_VRAM + (eighth * 0x18), &Self.tiles[eighth * 3], eighth * @sizeOf(u32)); + GBA.memcpy32(GBA.SPRITE_VRAM + (eighth * 0x20), &Self.tiles[eighth * 4], eighth * @sizeOf(u32)); + GBA.memcpy32(GBA.SPRITE_VRAM + (eighth * 0x28), &Self.tiles[eighth * 5], eighth * @sizeOf(u32)); + GBA.memcpy32(GBA.SPRITE_VRAM + (eighth * 0x30), &Self.tiles[eighth * 6], eighth * @sizeOf(u32)); + GBA.memcpy32(GBA.SPRITE_VRAM + (eighth * 0x38), &Self.tiles[eighth * 7], eighth * @sizeOf(u32)); + + // Palette + GBA.memcpy32(GBA.OBJ_PALETTE_RAM, &Self.pal, Self.pal.len * @sizeOf(u32)); + } +}; + +// 8x8 4bpp Sprite depicting a Rainbow +// (it's more of a gradiant, I don't know english, whatever.) +// source: Laziness? I can't draw, especially within the confines of 8x8 pixels +const Rainbow = struct { + const Self = @This(); + + const pal = [8]u32{ + 0x001F0000, 0x03FF01FF, 0x03E003EF, 0x7FE03FE0, 0x00007DE0, 0x00000000, 0x00000000, 0x00000000, + }; + + const tiles = [8]u32{ + 0x11111111, 0x22222222, 0x33333333, 0x44444444, 0x55555555, 0x66666666, 0x77777777, 0x88888888, + }; + + inline fn paletteMode(_: Self) GBA.PaletteMode { + return .Color16; + } + + inline fn size(_: Self) OAM.ObjectSize { + return .Size8x8; + } + + inline fn load(_: Self) void { + // 8x8 Sprites don't differ in any meaningful way between 1D and 2D mapping + + // Copy Tile Mapping + GBA.memcpy32(GBA.SPRITE_VRAM, &Self.tiles, Self.tiles.len * @sizeOf(u32)); + + // Copy Palette + GBA.memcpy32(GBA.OBJ_PALETTE_RAM, &Self.pal, Self.pal.len * @sizeOf(u32)); + } +}; + +const square_sprites = withoutThisMethodZigComplains(); + +fn withoutThisMethodZigComplains() SquareSpriteTuple { + return .{ Rainbow{}, Bebe16{}, Bebe32{}, Bebe64{} }; +} + +pub fn main() noreturn { + LCD.setupDisplayControl(.{ + .mode = .Mode0, // No Affine BGs + .objectLayer = .Show, // Enable Sprites + .objVramCharacterMapping = .TwoDimension, // 2D Sprite Mapping + }); + + OAM.init(); + + square_sprites[0].load(); // Copy Mapping to VRAM and Palette to PALRAM + + var sprite: *OAM.Attribute = OAM.allocate(); + sprite.paletteMode = square_sprites[0].paletteMode(); + sprite.palette = 0; + sprite.tileIndex = 0; + sprite.setSize(square_sprites[0].size()); + + var x: i32 = 96; + var y: i32 = 32; + + var i: i32 = 0; + + while (true) { + LCD.naiveVSync(); + Input.readInput(); + + const shoulder = Input.getShoulderJustPressed(); + if (shoulder != 0) { + i = (i + shoulder) & 0b11; + + // Index must be comptime known unfortunately + // so we need this ugly switch statement + switch (i) { + 0 => { + square_sprites[0].load(); + sprite.setSize(square_sprites[0].size()); + }, + 1 => { + square_sprites[1].load(); + sprite.setSize(square_sprites[1].size()); + }, + 2 => { + square_sprites[2].load(); + sprite.setSize(square_sprites[2].size()); + }, + 3 => { + square_sprites[3].load(); + sprite.setSize(square_sprites[3].size()); + }, + else => unreachable, + } + } + + x += Input.getHorizontal() * 2; + y += Input.getVertical() * 2; + + sprite.setPosition(x, y); + + OAM.update(1); + } +} diff --git a/src/first.zig b/src/first.zig deleted file mode 100644 index 8401577..0000000 --- a/src/first.zig +++ /dev/null @@ -1,18 +0,0 @@ -const GBA = @import("gba").GBA; -const Mode3 = @import("gba").Mode3; -const LCD = @import("gba").LCD; - -export var gameHeader linksection(".gbaheader") = GBA.Header.setup("FIRST", "AFSE", "00", 0); - -pub fn main() noreturn { - LCD.setupDisplayControl(.{ - .mode = .Mode3, - .backgroundLayer2 = .Show, - }); - - Mode3.setPixel(120, 80, GBA.toNativeColor(31, 0, 0)); - Mode3.setPixel(136, 80, GBA.toNativeColor(0, 31, 0)); - Mode3.setPixel(120, 96, GBA.toNativeColor(0, 0, 31)); - - while (true) {} -}