diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bitmap.h | 228 | ||||
-rw-r--r-- | src/common.h | 281 | ||||
-rw-r--r-- | src/gba-buttons.c | 602 | ||||
-rw-r--r-- | src/main.c | 1184 | ||||
-rw-r--r-- | src/sprites.h | 98 |
5 files changed, 1212 insertions, 1181 deletions
diff --git a/src/bitmap.h b/src/bitmap.h new file mode 100644 index 0000000..0befe5d --- /dev/null +++ b/src/bitmap.h | |||
@@ -0,0 +1,228 @@ | |||
1 | #ifndef GBAEXP_BITMAP_H | ||
2 | #define GBAEXP_BITMAP_H | ||
3 | |||
4 | #include "bd-font.c" | ||
5 | #include "common.c" | ||
6 | |||
7 | // Using bd-font, an 8x8 bitmap font. | ||
8 | static void | ||
9 | put_char(int x, int y, Color clr, u8 chr) { | ||
10 | for (size_t i = 0; i < 8; ++i) { | ||
11 | for (size_t j = 0; j < 8; ++j) { | ||
12 | if ((font[chr][i] >> (7 - j)) & 0x1) { | ||
13 | FRAMEBUFFER[y + i][x + j] = clr; | ||
14 | } | ||
15 | } | ||
16 | } | ||
17 | } | ||
18 | |||
19 | static void | ||
20 | put_text(int x, int y, Color clr, char *msg) { | ||
21 | int count = 0; | ||
22 | while (*msg) { | ||
23 | put_char(x + count, y, clr, *msg++); | ||
24 | count += 8; | ||
25 | } | ||
26 | } | ||
27 | |||
28 | // Draws a line with the given color between (x0,y0) and (x1,y1) using the | ||
29 | // Bresenham's line drawing algorithm using exclusively integer arithmetic. | ||
30 | static void | ||
31 | draw_line(int x0, int y0, int x1, int y1, Color clr) { | ||
32 | // Pointer to the initial position of the screen buffer where we will start | ||
33 | // writing our data. | ||
34 | vu16 *destination = (u16*)(SCREEN_BUFFER + y0 * SCREEN_WIDTH + x0); | ||
35 | |||
36 | // Adjust the step direction and calculate deltas. | ||
37 | int x_step; | ||
38 | int y_step; | ||
39 | int dx; | ||
40 | int dy; | ||
41 | if (x0 > x1) { | ||
42 | x_step = -1; | ||
43 | dx = x0 - x1; | ||
44 | } else { | ||
45 | x_step = 1; | ||
46 | dx = x1 - x0; | ||
47 | } | ||
48 | if (y0 > y1) { | ||
49 | y_step = -SCREEN_WIDTH; | ||
50 | dy = y0 - y1; | ||
51 | } else { | ||
52 | y_step = +SCREEN_WIDTH; | ||
53 | dy = y1 - y0; | ||
54 | } | ||
55 | |||
56 | if(dy == 0) { | ||
57 | // Horizontal line. | ||
58 | for(int i = 0; i <= dx; i++) { | ||
59 | destination[i * x_step] = clr; | ||
60 | } | ||
61 | } else if(dx == 0) { | ||
62 | // Vertical line. | ||
63 | for(int i = 0; i <= dy; i++) { | ||
64 | destination[i * y_step] = clr; | ||
65 | } | ||
66 | } else if (dx >= dy){ | ||
67 | // Positive slope. | ||
68 | int diff = 2 * dy - dx; | ||
69 | for (int i = 0; i <= dx; ++i) { | ||
70 | *destination = clr; | ||
71 | if (diff >= 0) { | ||
72 | destination += y_step; | ||
73 | diff -= 2 * dx; | ||
74 | } | ||
75 | destination += x_step; | ||
76 | diff += 2 * dy; | ||
77 | } | ||
78 | } else { | ||
79 | // Negative slope. | ||
80 | int diff = 2 * dx - dy; | ||
81 | for (int i = 0; i <= dy; ++i) { | ||
82 | *destination = clr; | ||
83 | if (diff >= 0) { | ||
84 | destination += x_step; | ||
85 | diff -= 2 * dy; | ||
86 | } | ||
87 | destination += y_step; | ||
88 | diff += 2 * dx; | ||
89 | } | ||
90 | } | ||
91 | } | ||
92 | |||
93 | static inline void | ||
94 | draw_rect(int x0, int y0, int x1, int y1, Color clr) { | ||
95 | if (x0 > x1) { | ||
96 | int tmp = x0; | ||
97 | x0 = x1; | ||
98 | x1 = tmp; | ||
99 | } | ||
100 | if (y0 > y1) { | ||
101 | int tmp = y0; | ||
102 | y0 = y1; | ||
103 | y1 = tmp; | ||
104 | } | ||
105 | int dx = x1 - x0; | ||
106 | int dy = y1 - y0; | ||
107 | for (int i = 0; i <= dx; ++i) { | ||
108 | int x = x0 + i; | ||
109 | FRAMEBUFFER[y0][x] = clr; | ||
110 | FRAMEBUFFER[y1][x] = clr; | ||
111 | } | ||
112 | for (int j = 0; j <= dy; ++j) { | ||
113 | int y = y0 + j; | ||
114 | FRAMEBUFFER[y][x0] = clr; | ||
115 | FRAMEBUFFER[y][x1] = clr; | ||
116 | } | ||
117 | } | ||
118 | |||
119 | static inline void | ||
120 | draw_fill_rect(int x0, int y0, int x1, int y1, Color clr) { | ||
121 | if (x0 > x1) { | ||
122 | int tmp = x0; | ||
123 | x0 = x1; | ||
124 | x1 = tmp; | ||
125 | } | ||
126 | if (y0 > y1) { | ||
127 | int tmp = y0; | ||
128 | y0 = y1; | ||
129 | y1 = tmp; | ||
130 | } | ||
131 | int dx = x1 - x0; | ||
132 | int dy = y1 - y0; | ||
133 | for (int i = 0; i <= dx; ++i) { | ||
134 | for (int j = 0; j <= dy; ++j) { | ||
135 | int x = x0 + i; | ||
136 | int y = y0 + j; | ||
137 | FRAMEBUFFER[y][x] = clr; | ||
138 | } | ||
139 | } | ||
140 | } | ||
141 | |||
142 | // In Mode4 the buffer is of 8 bytes per pixel instead of 16. We can't write the | ||
143 | // color directly, instead the color is stored in the palette memory at | ||
144 | // `MEM_PAL`. Note that in this mode MEM_PAL[0] is the background color. This | ||
145 | // plotter takes an index to a color stored in MEM_PAL[col_index]. Because the | ||
146 | // GBA needs to meet memory alignment requirements, we can't write a u8 into | ||
147 | // memory, instead we need to read a u16 word, mask and or the corresponding | ||
148 | // bits and wave the updated u16. | ||
149 | static void | ||
150 | put_pixel_m4(int x, int y, u8 col_index, vu16 *buffer) { | ||
151 | int buffer_index = (y * SCREEN_WIDTH + x) / 2; | ||
152 | vu16 *destination = &buffer[buffer_index]; | ||
153 | // Odd pixels will go to the top 8 bits of the destination. Even pixels to | ||
154 | // the lower 8 bits. | ||
155 | int odd = x & 0x1; | ||
156 | if(odd) { | ||
157 | *destination= (*destination & 0xFF) | (col_index << 8); | ||
158 | } else { | ||
159 | *destination= (*destination & ~0xFF) | col_index; | ||
160 | } | ||
161 | } | ||
162 | |||
163 | static void | ||
164 | draw_fill_rect_m4(int x0, int y0, int x1, int y1, u8 col_index, vu16 *buffer) { | ||
165 | int ix, iy; | ||
166 | for(iy = y0; iy < y1; iy++) { | ||
167 | for(ix = x0; ix < x1; ix++) { | ||
168 | put_pixel_m4(ix, iy, col_index, buffer); | ||
169 | } | ||
170 | } | ||
171 | } | ||
172 | |||
173 | void | ||
174 | draw_logo(void) { | ||
175 | int side = 60; | ||
176 | int line = 35; | ||
177 | int height = side * 0.5; | ||
178 | int x = SCREEN_WIDTH / 2 - height / 2; | ||
179 | int y = SCREEN_HEIGHT / 2; | ||
180 | |||
181 | // Draw red triangle. | ||
182 | draw_line(x + height - 1, y - side / 2, x, y - 1, COLOR_RED); | ||
183 | draw_line(x + height - 1, y + side / 2, x, y + 1, COLOR_RED); | ||
184 | draw_line(x + height - 1, y - side / 2 + 1, x, y, COLOR_RED); | ||
185 | draw_line(x + height - 1, y + side / 2 - 1, x, y, COLOR_RED); | ||
186 | |||
187 | // Draw white triangle. | ||
188 | draw_line(x, y - side / 2, x, y + side / 2, COLOR_WHITE); | ||
189 | draw_line(x + 1, y - side / 2, x + height, y - 1, COLOR_WHITE); | ||
190 | draw_line(x + 1, y + side / 2, x + height, y + 1, COLOR_WHITE); | ||
191 | |||
192 | // Draw white line at triangle tip. | ||
193 | draw_line(x + height, y - side / 2, x + height, y + side / 2, COLOR_WHITE); | ||
194 | draw_line(x + height + 1, y - side / 2, x + height + 1, y + side / 2, COLOR_WHITE); | ||
195 | |||
196 | // Double triangle line. | ||
197 | draw_line(x - 1, y - side / 2, x - 1, y + side / 2, COLOR_WHITE); | ||
198 | draw_line(x + 1, y - side / 2 + 1, x + height, y, COLOR_WHITE); | ||
199 | draw_line(x + 1, y + side / 2 - 1, x + height, y, COLOR_WHITE); | ||
200 | |||
201 | // Draw white lines. | ||
202 | draw_line(x - line, y, x, y, COLOR_WHITE); | ||
203 | draw_line(x + height, y, x + height + line, y, COLOR_WHITE); | ||
204 | draw_line(x - line, y + 1, x, y + 1, COLOR_WHITE); | ||
205 | draw_line(x + height, y + 1, x + height + line, y + 1, COLOR_WHITE); | ||
206 | } | ||
207 | |||
208 | void | ||
209 | copy_font_to_tile_memory(Tile *tile) { | ||
210 | // Hex to bits translation table. | ||
211 | const u32 conversion_u32[16] = { | ||
212 | 0x00000000, 0x00001000, 0x00000100, 0x00001100, | ||
213 | 0x00000010, 0x00001010, 0x00000110, 0x00001110, | ||
214 | 0x00000001, 0x00001001, 0x00000101, 0x00001101, | ||
215 | 0x00000011, 0x00001011, 0x00000111, 0x00001111, | ||
216 | }; | ||
217 | for (size_t i = 0; i < 250; ++i) { | ||
218 | for (size_t j = 0; j < 8; ++j) { | ||
219 | u8 row = font[i][j]; | ||
220 | u32 tile_idx = 0x00000000; | ||
221 | tile_idx = conversion_u32[row & 0xF] << 16; | ||
222 | tile_idx |= conversion_u32[(row >> 4) & 0xF]; | ||
223 | (tile + i)->data[j] = tile_idx; | ||
224 | } | ||
225 | } | ||
226 | } | ||
227 | |||
228 | #endif // GBAEXP_BITMAP_H | ||
diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..35b4619 --- /dev/null +++ b/src/common.h | |||
@@ -0,0 +1,281 @@ | |||
1 | #ifndef GBAEXP_COMMON_H | ||
2 | #define GBAEXP_COMMON_H | ||
3 | |||
4 | #include "shorthand.h" | ||
5 | |||
6 | // | ||
7 | // Memory sections. | ||
8 | // | ||
9 | |||
10 | // Defines for the different memory sections in the GBA. | ||
11 | #define MEM_SROM 0x00000000 | ||
12 | #define MEM_EW 0x02000000 | ||
13 | #define MEM_IW 0x03000000 | ||
14 | #define MEM_IO 0x04000000 | ||
15 | #define MEM_PAL 0x05000000 | ||
16 | #define MEM_VRAM 0x06000000 | ||
17 | #define MEM_OAM 0x07000000 | ||
18 | #define MEM_PAK 0x08000000 | ||
19 | #define MEM_CART 0x0E000000 | ||
20 | |||
21 | // | ||
22 | // Display modes. | ||
23 | // | ||
24 | |||
25 | // Display registers. | ||
26 | #define DISP_CTRL *((vu32*)(MEM_IO + 0x0000)) | ||
27 | #define DISP_STATUS *((vu16*)(MEM_IO + 0x0004)) | ||
28 | #define DISP_VCOUNT *((vu16*)(MEM_IO + 0x0006)) | ||
29 | |||
30 | // The first three bits in the DISP_CTRL are used to control the video mode. | ||
31 | #define DISP_MODE_0 0x0000 | ||
32 | #define DISP_MODE_1 0x0001 | ||
33 | #define DISP_MODE_2 0x0002 | ||
34 | #define DISP_MODE_3 0x0003 | ||
35 | #define DISP_MODE_4 0x0004 | ||
36 | #define DISP_MODE_5 0x0005 | ||
37 | #define DISP_GB (1 << 3) | ||
38 | #define DISP_PAGE (1 << 4) | ||
39 | #define DISP_OAM_HBLANK (1 << 5) | ||
40 | #define DISP_OBJ_1D (1 << 6) | ||
41 | #define DISP_BLANK (1 << 7) | ||
42 | #define DISP_BG_0 (1 << 8) | ||
43 | #define DISP_BG_1 (1 << 9) | ||
44 | #define DISP_BG_2 (1 << 10) | ||
45 | #define DISP_BG_3 (1 << 11) | ||
46 | #define DISP_OBJ (1 << 12) | ||
47 | #define DISP_ENABLE_SPRITES DISP_OBJ | DISP_OBJ_1D | ||
48 | |||
49 | // Registers to control of BG layers. | ||
50 | #define BG_CTRL_0 *((vu16*)(0x04000008 + 0x0002 * 0)) | ||
51 | #define BG_CTRL_1 *((vu16*)(0x04000008 + 0x0002 * 1)) | ||
52 | #define BG_CTRL_2 *((vu16*)(0x04000008 + 0x0002 * 2)) | ||
53 | #define BG_CTRL_3 *((vu16*)(0x04000008 + 0x0002 * 3)) | ||
54 | |||
55 | // Bits to control the background. | ||
56 | #define BG_PRIORITY_0 0x0 | ||
57 | #define BG_PRIORITY_1 0x1 | ||
58 | #define BG_PRIORITY_2 0x2 | ||
59 | #define BG_PRIORITY_3 0x3 | ||
60 | #define BG_CHARBLOCK(N) ((N) << 2) | ||
61 | #define BG_MOSAIC (1 << 6) | ||
62 | #define BG_HIGH_COLOR (1 << 7) | ||
63 | #define BG_SCREENBLOCK(N) ((N) << 8) | ||
64 | #define BG_AFFINE (1 << 0xD) | ||
65 | #define BG_SIZE(N) ((N) << 0xE) | ||
66 | |||
67 | // BG registers for horizontal displacement. | ||
68 | #define BG_H_SCROLL_0 *((vu16*)(0x04000010 + 0x0004 * 0)) | ||
69 | #define BG_H_SCROLL_1 *((vu16*)(0x04000010 + 0x0004 * 1)) | ||
70 | #define BG_H_SCROLL_2 *((vu16*)(0x04000010 + 0x0004 * 2)) | ||
71 | #define BG_H_SCROLL_3 *((vu16*)(0x04000010 + 0x0004 * 3)) | ||
72 | |||
73 | // BG registers for vertical displacement. | ||
74 | #define BG_V_SCROLL_0 *((vu16*)(0x04000012 + 0x0004 * 0)) | ||
75 | #define BG_V_SCROLL_1 *((vu16*)(0x04000012 + 0x0004 * 1)) | ||
76 | #define BG_V_SCROLL_2 *((vu16*)(0x04000012 + 0x0004 * 2)) | ||
77 | #define BG_V_SCROLL_3 *((vu16*)(0x04000012 + 0x0004 * 3)) | ||
78 | |||
79 | // Screen settings. | ||
80 | #define SCREEN_WIDTH 240 | ||
81 | #define SCREEN_HEIGHT 160 | ||
82 | |||
83 | // The GBA in mode 3 expects rbg15 colors in the VRAM, where each component | ||
84 | // (RGB) have a 0--31 range. For example, pure red would be rgb15(31, 0, 0). | ||
85 | typedef u16 Color; | ||
86 | |||
87 | // | ||
88 | // Tile memory access. | ||
89 | // | ||
90 | |||
91 | // NOTE: Only defining 4bpp tiles for now. | ||
92 | typedef struct Tile { | ||
93 | u32 data[8]; | ||
94 | } Tile; | ||
95 | |||
96 | typedef Tile TileBlock[512]; | ||
97 | #define TILE_MEM ((TileBlock*) MEM_VRAM) | ||
98 | |||
99 | typedef u16 ScreenBlock[1024]; | ||
100 | #define SCREENBLOCK_MEM ((ScreenBlock*)MEM_VRAM) | ||
101 | |||
102 | // We can treat the screen as a HxW matrix. With the following macro we can | ||
103 | // write a pixel to the screen at the (x, y) position using: | ||
104 | // | ||
105 | // FRAMEBUFFER[y][x] = color; | ||
106 | // | ||
107 | typedef Color Scanline[SCREEN_WIDTH]; | ||
108 | #define FRAMEBUFFER ((Scanline*)MEM_VRAM) | ||
109 | #define SCREEN_BUFFER ((u16*) MEM_VRAM) | ||
110 | #define PAL_BUFFER_BG ((u16*) MEM_PAL) | ||
111 | #define PAL_BUFFER_SPRITES ((u16*) 0x05000200) | ||
112 | |||
113 | // | ||
114 | // Colors. | ||
115 | // | ||
116 | |||
117 | static inline Color | ||
118 | rgb15(u32 red, u32 green, u32 blue ) { | ||
119 | return (blue << 10) | (green << 5) | red; | ||
120 | } | ||
121 | |||
122 | #define COLOR_RED rgb15(31, 0, 12) | ||
123 | #define COLOR_BLUE rgb15(2, 15, 30) | ||
124 | #define COLOR_CYAN rgb15(0, 30, 30) | ||
125 | #define COLOR_GREY rgb15(4, 4, 4) | ||
126 | #define COLOR_BLACK rgb15(0, 0, 0) | ||
127 | #define COLOR_WHITE rgb15(28, 28, 28) | ||
128 | |||
129 | // | ||
130 | // Sprites. | ||
131 | // | ||
132 | |||
133 | // Using macros instead of aligned structs for setting up OBJ attributes and | ||
134 | // affine parameters. | ||
135 | // TODO: Benchmark if this would be slower or the same that TONC's | ||
136 | // implementation. | ||
137 | #define OBJ_ATTR_0(N) *((vu16*)(MEM_OAM + 0 + 8 * (N))) | ||
138 | #define OBJ_ATTR_1(N) *((vu16*)(MEM_OAM + 2 + 8 * (N))) | ||
139 | #define OBJ_ATTR_2(N) *((vu16*)(MEM_OAM + 4 + 8 * (N))) | ||
140 | #define OBJ_AFFINE_PA(N) *((vs16*)(MEM_OAM + 6 + 8 * 0 + 8 * 4 * (N))) | ||
141 | #define OBJ_AFFINE_PB(N) *((vs16*)(MEM_OAM + 6 + 8 * 1 + 8 * 4 * (N))) | ||
142 | #define OBJ_AFFINE_PC(N) *((vs16*)(MEM_OAM + 6 + 8 * 2 + 8 * 4 * (N))) | ||
143 | #define OBJ_AFFINE_PD(N) *((vs16*)(MEM_OAM + 6 + 8 * 3 + 8 * 4 * (N))) | ||
144 | |||
145 | // OBJ_ATTR_0 parameters | ||
146 | #define OBJ_Y_COORD(N) ((N) & 0xFF) | ||
147 | #define OBJ_NORMAL (0x00 << 0x8) | ||
148 | #define OBJ_AFFINE (0x01 << 0x8) | ||
149 | #define OBJ_HIDDEN (0x02 << 0x8) | ||
150 | #define OBJ_AFFINE_2X (0x03 << 0x8) | ||
151 | #define OBJ_ALPHA_BLEND (0x01 << 0xA) | ||
152 | #define OBJ_WINDOW (0x02 << 0xA) | ||
153 | #define OBJ_SHAPE_SQUARE (0x00 << 0xE) | ||
154 | #define OBJ_SHAPE_WIDE (0x01 << 0xE) | ||
155 | #define OBJ_SHAPE_TALL (0x02 << 0xE) | ||
156 | |||
157 | // OBJ_ATTR_1 parameters | ||
158 | #define OBJ_X_COORD(N) ((N) & 0x1FF) | ||
159 | #define OBJ_AFFINE_IDX(N) ((N) << 0x9) | ||
160 | #define OBJ_H_FLIP (0x01 << 0xC) | ||
161 | #define OBJ_V_FLIP (0x01 << 0xD) | ||
162 | #define OBJ_SIZE_SMALL (0x00 << 0xE) | ||
163 | #define OBJ_SIZE_MID (0x01 << 0xE) | ||
164 | #define OBJ_SIZE_BIG (0x02 << 0xE) | ||
165 | #define OBJ_SIZE_HUGE (0x03 << 0xE) | ||
166 | |||
167 | // OBJ_ATTR_2 parameters | ||
168 | #define OBJ_TILE_INDEX(N) ((N) & 0x3FF) | ||
169 | #define OBJ_PRIORITY(N) ((N) << 0xA) | ||
170 | #define OBJ_PAL_BANK(N) ((N) << 0xC) | ||
171 | |||
172 | static inline void | ||
173 | wait_vsync(void) { | ||
174 | while(DISP_VCOUNT >= 160); | ||
175 | while(DISP_VCOUNT < 160); | ||
176 | } | ||
177 | |||
178 | // | ||
179 | // Mode 4 page flipping | ||
180 | // | ||
181 | |||
182 | static inline void | ||
183 | flip_page(void) { | ||
184 | DISP_CTRL ^= DISP_PAGE; | ||
185 | } | ||
186 | |||
187 | #define SCREEN_PAGE_1 ((vu16*) MEM_VRAM) | ||
188 | #define SCREEN_PAGE_2 ((vu16*) (MEM_VRAM + 0xa000)) | ||
189 | |||
190 | // | ||
191 | // Profiling. | ||
192 | // | ||
193 | |||
194 | #define TIMER_DATA_0 *((vu16*) (0x04000100 + 0x04 * 0)) | ||
195 | #define TIMER_DATA_1 *((vu16*) (0x04000100 + 0x04 * 1)) | ||
196 | #define TIMER_DATA_2 *((vu16*) (0x04000100 + 0x04 * 2)) | ||
197 | #define TIMER_DATA_3 *((vu16*) (0x04000100 + 0x04 * 3)) | ||
198 | #define TIMER_CTRL_0 *((vu16*) (0x04000102 + 0x04 * 0)) | ||
199 | #define TIMER_CTRL_1 *((vu16*) (0x04000102 + 0x04 * 1)) | ||
200 | #define TIMER_CTRL_2 *((vu16*) (0x04000102 + 0x04 * 2)) | ||
201 | #define TIMER_CTRL_3 *((vu16*) (0x04000102 + 0x04 * 3)) | ||
202 | |||
203 | // Timer control bits. | ||
204 | #define TIMER_CTRL_FREQ_0 0 | ||
205 | #define TIMER_CTRL_FREQ_1 1 | ||
206 | #define TIMER_CTRL_FREQ_2 2 | ||
207 | #define TIMER_CTRL_FREQ_3 3 | ||
208 | #define TIMER_CTRL_CASCADE (1 << 2) | ||
209 | #define TIMER_CTRL_IRQ (1 << 6) | ||
210 | #define TIMER_CTRL_ENABLE (1 << 7) | ||
211 | |||
212 | // We use timers 2 and 3 to count the number of cycles since the profile_start | ||
213 | // functions is called. Don't use if the code we are trying to profile make use | ||
214 | // of these timers. | ||
215 | static inline | ||
216 | void profile_start(void) { | ||
217 | TIMER_DATA_2 = 0; | ||
218 | TIMER_DATA_3 = 0; | ||
219 | TIMER_CTRL_2 = 0; | ||
220 | TIMER_CTRL_3 = 0; | ||
221 | TIMER_CTRL_3 = TIMER_CTRL_ENABLE | TIMER_CTRL_CASCADE; | ||
222 | TIMER_CTRL_2 = TIMER_CTRL_ENABLE; | ||
223 | } | ||
224 | |||
225 | static inline | ||
226 | u32 profile_stop(void) { | ||
227 | TIMER_CTRL_2 = 0; | ||
228 | return (TIMER_DATA_3 << 16) | TIMER_DATA_2; | ||
229 | } | ||
230 | |||
231 | // | ||
232 | // Input handling. | ||
233 | // | ||
234 | |||
235 | // Memory address for key input register | ||
236 | #define KEY_INPUTS *((vu16*) 0x04000130) | ||
237 | |||
238 | // Alias for key pressing bits. | ||
239 | #define KEY_A (1 << 0) | ||
240 | #define KEY_B (1 << 1) | ||
241 | #define KEY_SELECT (1 << 2) | ||
242 | #define KEY_START (1 << 3) | ||
243 | #define KEY_RIGHT (1 << 4) | ||
244 | #define KEY_LEFT (1 << 5) | ||
245 | #define KEY_UP (1 << 6) | ||
246 | #define KEY_DOWN (1 << 7) | ||
247 | #define KEY_R (1 << 8) | ||
248 | #define KEY_L (1 << 9) | ||
249 | |||
250 | #define KEY_MASK 0x03FF | ||
251 | |||
252 | // Saving the previous and current key states as globals for now. | ||
253 | static u16 key_curr = 0; | ||
254 | static u16 key_prev = 0; | ||
255 | |||
256 | static inline void | ||
257 | poll_keys(void) { | ||
258 | key_prev = key_curr; | ||
259 | key_curr = ~KEY_INPUTS & KEY_MASK; | ||
260 | } | ||
261 | |||
262 | // Returns true if the given key has been pressed at time of calling and was not | ||
263 | // pressed since the previous call. For example, if a key is being held, this | ||
264 | // function will return `true` only on the frame where the key initially | ||
265 | // activated. | ||
266 | static inline u32 | ||
267 | key_pressed(u32 key) { | ||
268 | return (key_curr & key) & ~(key_prev & key); | ||
269 | } | ||
270 | |||
271 | // Check if the given key is pressed and has been since at least one frame. | ||
272 | static inline u32 | ||
273 | key_hold(u32 key) { | ||
274 | return (key_curr & key) & key_prev & key; | ||
275 | } | ||
276 | |||
277 | // Check if the given key/button is currently pressed. | ||
278 | #define KEY_PRESSED(key) (~(KEY_INPUTS) & key) | ||
279 | |||
280 | |||
281 | #endif // GBAEXP_COMMON_H | ||
diff --git a/src/gba-buttons.c b/src/gba-buttons.c index 951b4dc..dd06351 100644 --- a/src/gba-buttons.c +++ b/src/gba-buttons.c | |||
@@ -1,3 +1,6 @@ | |||
1 | #include "common.h" | ||
2 | #include "sprites.h" | ||
3 | |||
1 | // 1bpp packed | 4 | // 1bpp packed |
2 | u32 gba_btn_a_data[] = { | 5 | u32 gba_btn_a_data[] = { |
3 | 0xc0300804, 0x844242c2, 0x030c1020, 0x21424243, | 6 | 0xc0300804, 0x844242c2, 0x030c1020, 0x21424243, |
@@ -133,11 +136,6 @@ u32 gba_btn_fx_startselect[] = { | |||
133 | 0x01800000, 0x00000000, 0x00000000, 0x00000004, | 136 | 0x01800000, 0x00000000, 0x00000000, 0x00000004, |
134 | }; | 137 | }; |
135 | 138 | ||
136 | typedef struct SpriteAnimation { | ||
137 | size_t *tile_offsets; | ||
138 | size_t n_frames; | ||
139 | } SpriteAnimation; | ||
140 | |||
141 | typedef enum {BTN_STATE_IDLE, BTN_STATE_PRESSED, BTN_STATE_RELEASED, BTN_STATE_HOLD} BtnState; | 139 | typedef enum {BTN_STATE_IDLE, BTN_STATE_PRESSED, BTN_STATE_RELEASED, BTN_STATE_HOLD} BtnState; |
142 | 140 | ||
143 | typedef struct AnimationEntry { | 141 | typedef struct AnimationEntry { |
@@ -262,3 +260,597 @@ AnimationEntry *btn_animation_startselect[] = { | |||
262 | {0, 0, 12, 1}, | 260 | {0, 0, 12, 1}, |
263 | }, | 261 | }, |
264 | }; | 262 | }; |
263 | |||
264 | typedef struct ButtonSprite { | ||
265 | ObjState *sprites; | ||
266 | AnimationEntry **animations; | ||
267 | size_t frame; | ||
268 | size_t n_obj; | ||
269 | size_t n_frames; | ||
270 | BtnState state; | ||
271 | } ButtonSprite; | ||
272 | |||
273 | void | ||
274 | init_button_sprite(ButtonSprite *btn) { | ||
275 | for (size_t i = 0; i < btn->n_obj; ++i) { | ||
276 | btn->sprites[i].id = load_packed_sprite_data( | ||
277 | btn->sprites[i].data, | ||
278 | btn->sprites[i].n_tiles, | ||
279 | btn->sprites[i].frames); | ||
280 | btn->sprites[i].base_tile = sprites[btn->sprites[i].id].tile_start; | ||
281 | } | ||
282 | } | ||
283 | |||
284 | void | ||
285 | button_tick(ButtonSprite *btn) { | ||
286 | // Nothing to do here. | ||
287 | if (btn->state == BTN_STATE_IDLE) { | ||
288 | return; | ||
289 | } | ||
290 | |||
291 | // Reset animation state. | ||
292 | if (btn->state == BTN_STATE_PRESSED && btn->frame != 0) { | ||
293 | btn->frame = 0; | ||
294 | } | ||
295 | |||
296 | // Continue the animation. | ||
297 | if (btn->state == BTN_STATE_HOLD || btn->state == BTN_STATE_PRESSED ) { | ||
298 | if(btn->frame < btn->n_frames - 1) { | ||
299 | btn->frame++; | ||
300 | } | ||
301 | } | ||
302 | |||
303 | // Finish the animation and return to idle. | ||
304 | if (btn->state == BTN_STATE_RELEASED) { | ||
305 | if (btn->frame > 0 && btn->frame < btn->n_frames - 1) { | ||
306 | btn->frame++; | ||
307 | } else { | ||
308 | btn->frame = 0; | ||
309 | btn->state = BTN_STATE_IDLE; | ||
310 | } | ||
311 | } | ||
312 | for (size_t i = 0; i < btn->n_obj; ++i) { | ||
313 | AnimationEntry anim_frame = btn->animations[i][btn->frame]; | ||
314 | int x = btn->sprites[i].x + anim_frame.x_offset; | ||
315 | int y = btn->sprites[i].y + anim_frame.y_offset; | ||
316 | int base_tile = btn->sprites[i].base_tile + anim_frame.tile_offset; | ||
317 | |||
318 | // Clear the previous x/y coordinate and base tiles. | ||
319 | btn->sprites[i].obj_attr_0 &= ~0xFF; | ||
320 | btn->sprites[i].obj_attr_1 &= ~0x1FF; | ||
321 | btn->sprites[i].obj_attr_2 &= ~0x3FF; | ||
322 | |||
323 | // Update x/y/tile and hidden state from the animations. | ||
324 | btn->sprites[i].obj_attr_0 |= OBJ_Y_COORD(y); | ||
325 | btn->sprites[i].obj_attr_1 |= OBJ_X_COORD(x); | ||
326 | btn->sprites[i].obj_attr_2 |= base_tile; | ||
327 | if (anim_frame.hidden) { | ||
328 | btn->sprites[i].obj_attr_0 |= OBJ_HIDDEN; | ||
329 | } else { | ||
330 | btn->sprites[i].obj_attr_0 &= ~OBJ_HIDDEN; | ||
331 | } | ||
332 | |||
333 | // Update OBJ attributes. | ||
334 | OBJ_ATTR_0(btn->sprites[i].id) = btn->sprites[i].obj_attr_0; | ||
335 | OBJ_ATTR_1(btn->sprites[i].id) = btn->sprites[i].obj_attr_1; | ||
336 | OBJ_ATTR_2(btn->sprites[i].id) = btn->sprites[i].obj_attr_2; | ||
337 | } | ||
338 | } | ||
339 | |||
340 | ButtonSprite buttons[] = { | ||
341 | // DOWN. | ||
342 | { | ||
343 | .frame = 0, | ||
344 | .n_obj = 3, | ||
345 | .n_frames = 8, | ||
346 | .state = BTN_STATE_RELEASED, | ||
347 | .animations = &btn_animation, | ||
348 | .sprites = &(ObjState[]){ | ||
349 | { | ||
350 | .id = 0, | ||
351 | .x = SCREEN_WIDTH / 2 - 64 - 16, | ||
352 | .y = SCREEN_HEIGHT / 2 + 29, | ||
353 | .data = &gba_btn_updown_data, | ||
354 | .n_tiles = 4, | ||
355 | .frames = 1, | ||
356 | .obj_attr_0 = 0, | ||
357 | .obj_attr_1 = OBJ_V_FLIP | OBJ_SIZE_MID, | ||
358 | .obj_attr_2 = 0 | ||
359 | }, | ||
360 | { | ||
361 | .id = 0, | ||
362 | .x = SCREEN_WIDTH / 2 - 64 - 16, | ||
363 | .y = SCREEN_HEIGHT / 2 + 29 + 11, | ||
364 | .data = &gba_btn_down_shadow_data, | ||
365 | .n_tiles = 2, | ||
366 | .frames = 1, | ||
367 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
368 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
369 | .obj_attr_2 = 0 | ||
370 | }, | ||
371 | { | ||
372 | .id = 0, | ||
373 | .x = SCREEN_WIDTH / 2 - 64 - 16 - 8, | ||
374 | .y = SCREEN_HEIGHT / 2 + 29 + 17, | ||
375 | .data = &gba_btn_fx_downup, | ||
376 | .n_tiles = 4, | ||
377 | .frames = 4, | ||
378 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
379 | .obj_attr_1 = OBJ_SIZE_MID, | ||
380 | .obj_attr_2 = 0 | ||
381 | }, | ||
382 | }, | ||
383 | }, | ||
384 | // UP. | ||
385 | { | ||
386 | .frame = 0, | ||
387 | .n_obj = 3, | ||
388 | .n_frames = 8, | ||
389 | .state = BTN_STATE_RELEASED, | ||
390 | .animations = &btn_animation, | ||
391 | .sprites = &(ObjState[]){ | ||
392 | { | ||
393 | .id = 0, | ||
394 | .x = SCREEN_WIDTH / 2 - 64 - 16, | ||
395 | .y = SCREEN_HEIGHT / 2 + 32 - 18, | ||
396 | .data = &gba_btn_updown_data, | ||
397 | .n_tiles = 4, | ||
398 | .frames = 1, | ||
399 | .obj_attr_0 = 0, | ||
400 | .obj_attr_1 = OBJ_SIZE_MID, | ||
401 | .obj_attr_2 = 0 | ||
402 | }, | ||
403 | { | ||
404 | .id = 0, | ||
405 | .x = SCREEN_WIDTH / 2 - 64 - 16, | ||
406 | .y = SCREEN_HEIGHT / 2 + 32 - 18 + 7, | ||
407 | .data = &gba_btn_up_shadow_data, | ||
408 | .n_tiles = 2, | ||
409 | .frames = 1, | ||
410 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
411 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
412 | .obj_attr_2 = 0 | ||
413 | }, | ||
414 | { | ||
415 | .id = 0, | ||
416 | .x = SCREEN_WIDTH / 2 - 64 - 16 - 8, | ||
417 | .y = SCREEN_HEIGHT / 2 + 32 - 18 - 7, | ||
418 | .data = &gba_btn_fx_downup, | ||
419 | .n_tiles = 4, | ||
420 | .frames = 4, | ||
421 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
422 | .obj_attr_1 = OBJ_V_FLIP | OBJ_SIZE_MID, | ||
423 | .obj_attr_2 = 0 | ||
424 | }, | ||
425 | }, | ||
426 | }, | ||
427 | // LEFT | ||
428 | { | ||
429 | .frame = 0, | ||
430 | .n_obj = 3, | ||
431 | .n_frames = 8, | ||
432 | .state = BTN_STATE_RELEASED, | ||
433 | .animations = &btn_animation, | ||
434 | .sprites = &(ObjState[]){ | ||
435 | { | ||
436 | .id = 0, | ||
437 | .x = SCREEN_WIDTH / 2 - 64 - 16 - 10, | ||
438 | .y = SCREEN_HEIGHT / 2 + 32 - 10, | ||
439 | .data = &gba_btn_leftright_data, | ||
440 | .n_tiles = 4, | ||
441 | .frames = 1, | ||
442 | .obj_attr_0 = 0, | ||
443 | .obj_attr_1 = OBJ_SIZE_MID, | ||
444 | .obj_attr_2 = 0 | ||
445 | }, | ||
446 | { | ||
447 | .id = 0, | ||
448 | .x = SCREEN_WIDTH / 2 - 64 - 16 - 10, | ||
449 | .y = SCREEN_HEIGHT / 2 + 32 - 10 + 6, | ||
450 | .data = &gba_btn_leftright_shadow_data, | ||
451 | .n_tiles = 2, | ||
452 | .frames = 1, | ||
453 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
454 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
455 | .obj_attr_2 = 0 | ||
456 | }, | ||
457 | { | ||
458 | .id = 0, | ||
459 | .x = SCREEN_WIDTH / 2 - 64 - 16 - 10 - 6, | ||
460 | .y = SCREEN_HEIGHT / 2 + 32 - 10 - 8, | ||
461 | .data = &gba_btn_fx_leftright, | ||
462 | .n_tiles = 4, | ||
463 | .frames = 4, | ||
464 | .obj_attr_0 = OBJ_SHAPE_TALL, | ||
465 | .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_MID, | ||
466 | .obj_attr_2 = 0 | ||
467 | }, | ||
468 | }, | ||
469 | }, | ||
470 | // RIGHT. | ||
471 | { | ||
472 | .frame = 0, | ||
473 | .n_obj = 3, | ||
474 | .n_frames = 8, | ||
475 | .state = BTN_STATE_RELEASED, | ||
476 | .animations = &btn_animation, | ||
477 | .sprites = &(ObjState[]){ | ||
478 | { | ||
479 | .id = 0, | ||
480 | .x = SCREEN_WIDTH / 2 - 64 - 16 + 11, | ||
481 | .y = SCREEN_HEIGHT / 2 + 32 - 10, | ||
482 | .data = &gba_btn_leftright_data, | ||
483 | .n_tiles = 4, | ||
484 | .frames = 1, | ||
485 | .obj_attr_0 = 0, | ||
486 | .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_MID, | ||
487 | .obj_attr_2 = 0 | ||
488 | }, | ||
489 | { | ||
490 | .id = 0, | ||
491 | .x = SCREEN_WIDTH / 2 - 64 - 16 + 11, | ||
492 | .y = SCREEN_HEIGHT / 2 + 32 - 10 + 6, | ||
493 | .data = &gba_btn_leftright_shadow_data, | ||
494 | .n_tiles = 2, | ||
495 | .frames = 1, | ||
496 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
497 | .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_SMALL, | ||
498 | .obj_attr_2 = 0 | ||
499 | }, | ||
500 | { | ||
501 | .id = 0, | ||
502 | .x = SCREEN_WIDTH / 2 - 64 - 16 + 11 + 14, | ||
503 | .y = SCREEN_HEIGHT / 2 + 32 - 10 - 8, | ||
504 | .data = &gba_btn_fx_leftright, | ||
505 | .n_tiles = 4, | ||
506 | .frames = 4, | ||
507 | .obj_attr_0 = OBJ_SHAPE_TALL, | ||
508 | .obj_attr_1 = OBJ_SIZE_MID, | ||
509 | .obj_attr_2 = 0 | ||
510 | }, | ||
511 | }, | ||
512 | }, | ||
513 | // L. | ||
514 | { | ||
515 | .frame = 0, | ||
516 | .n_obj = 3, | ||
517 | .n_frames = 8, | ||
518 | .state = BTN_STATE_RELEASED, | ||
519 | .animations = &btn_animation, | ||
520 | .sprites = &(ObjState[]){ | ||
521 | { | ||
522 | .id = 0, | ||
523 | .x = SCREEN_WIDTH / 2 - 64 - 28, | ||
524 | .y = SCREEN_HEIGHT / 2 - 32 - 20, | ||
525 | .data = &gba_btn_l_data, | ||
526 | .n_tiles = 2, | ||
527 | .frames = 1, | ||
528 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
529 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
530 | .obj_attr_2 = 0 | ||
531 | }, | ||
532 | { | ||
533 | .id = 0, | ||
534 | .x = SCREEN_WIDTH / 2 - 64 - 28, | ||
535 | .y = SCREEN_HEIGHT / 2 - 32 - 20 + 2, | ||
536 | .data = &gba_btn_lr_shadow_data, | ||
537 | .n_tiles = 2, | ||
538 | .frames = 1, | ||
539 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
540 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
541 | .obj_attr_2 = 0 | ||
542 | }, | ||
543 | { | ||
544 | .id = 0, | ||
545 | .x = SCREEN_WIDTH / 2 - 64 - 28 - 12, | ||
546 | .y = SCREEN_HEIGHT / 2 - 32 - 20 - 12, | ||
547 | .data = &gba_btn_fx_lr, | ||
548 | .n_tiles = 4, | ||
549 | .frames = 4, | ||
550 | .obj_attr_0 = OBJ_SHAPE_SQUARE, | ||
551 | .obj_attr_1 = OBJ_SIZE_MID, | ||
552 | .obj_attr_2 = 0 | ||
553 | }, | ||
554 | }, | ||
555 | }, | ||
556 | // R. | ||
557 | { | ||
558 | .frame = 0, | ||
559 | .n_obj = 3, | ||
560 | .n_frames = 8, | ||
561 | .state = BTN_STATE_RELEASED, | ||
562 | .animations = &btn_animation, | ||
563 | .sprites = &(ObjState[]){ | ||
564 | { | ||
565 | .id = 0, | ||
566 | .x = SCREEN_WIDTH / 2 + 32 + 20 + 24, | ||
567 | .y = SCREEN_HEIGHT / 2 - 32 - 20, | ||
568 | .data = &gba_btn_r_data, | ||
569 | .n_tiles = 2, | ||
570 | .frames = 1, | ||
571 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
572 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
573 | .obj_attr_2 = 0 | ||
574 | }, | ||
575 | { | ||
576 | .id = 0, | ||
577 | .x = SCREEN_WIDTH / 2 + 32 + 20 + 24, | ||
578 | .y = SCREEN_HEIGHT / 2 - 32 - 20 + 2, | ||
579 | .data = &gba_btn_lr_shadow_data, | ||
580 | .n_tiles = 2, | ||
581 | .frames = 1, | ||
582 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
583 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
584 | .obj_attr_2 = 0 | ||
585 | }, | ||
586 | { | ||
587 | .id = 0, | ||
588 | .x = SCREEN_WIDTH / 2 + 32 + 20 + 12 + 24, | ||
589 | .y = SCREEN_HEIGHT / 2 - 32 - 20 - 12, | ||
590 | .data = &gba_btn_fx_lr, | ||
591 | .n_tiles = 4, | ||
592 | .frames = 4, | ||
593 | .obj_attr_0 = OBJ_SHAPE_SQUARE, | ||
594 | .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_MID, | ||
595 | .obj_attr_2 = 0 | ||
596 | }, | ||
597 | }, | ||
598 | }, | ||
599 | // A. | ||
600 | { | ||
601 | .frame = 0, | ||
602 | .n_obj = 3, | ||
603 | .n_frames = 8, | ||
604 | .state = BTN_STATE_RELEASED, | ||
605 | .animations = &btn_animation_ab, | ||
606 | .sprites = &(ObjState[]){ | ||
607 | { | ||
608 | .id = 0, | ||
609 | .x = SCREEN_WIDTH / 2 + 32 + 20 + 20, | ||
610 | .y = SCREEN_HEIGHT / 2 + 32 - 16, | ||
611 | .data = &gba_btn_a_data, | ||
612 | .n_tiles = 4, | ||
613 | .frames = 1, | ||
614 | .obj_attr_0 = OBJ_SHAPE_SQUARE, | ||
615 | .obj_attr_1 = OBJ_SIZE_MID, | ||
616 | .obj_attr_2 = 0 | ||
617 | }, | ||
618 | { | ||
619 | .id = 0, | ||
620 | .x = SCREEN_WIDTH / 2 + 32 + 20 + 20, | ||
621 | .y = SCREEN_HEIGHT / 2 + 32 - 16 + 8, | ||
622 | .data = &gba_btn_ab_shadow_data, | ||
623 | .n_tiles = 2, | ||
624 | .frames = 1, | ||
625 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
626 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
627 | .obj_attr_2 = 0 | ||
628 | }, | ||
629 | { | ||
630 | .id = 0, | ||
631 | .x = SCREEN_WIDTH / 2 + 32 + 19 - 7 + 20, | ||
632 | .y = SCREEN_HEIGHT / 2 + 32 - 23, | ||
633 | .data = &gba_btn_fx_ab, | ||
634 | .n_tiles = 16, | ||
635 | .frames = 3, | ||
636 | .obj_attr_0 = OBJ_SHAPE_SQUARE, | ||
637 | .obj_attr_1 = OBJ_SIZE_BIG, | ||
638 | .obj_attr_2 = 0 | ||
639 | }, | ||
640 | }, | ||
641 | }, | ||
642 | // B. | ||
643 | { | ||
644 | .frame = 0, | ||
645 | .n_obj = 3, | ||
646 | .n_frames = 8, | ||
647 | .state = BTN_STATE_RELEASED, | ||
648 | .animations = &btn_animation_ab, | ||
649 | .sprites = &(ObjState[]){ | ||
650 | { | ||
651 | .id = 0, | ||
652 | .x = SCREEN_WIDTH / 2 + 32 + 20, | ||
653 | .y = SCREEN_HEIGHT / 2 + 32, | ||
654 | .data = &gba_btn_b_data, | ||
655 | .n_tiles = 4, | ||
656 | .frames = 1, | ||
657 | .obj_attr_0 = OBJ_SHAPE_SQUARE, | ||
658 | .obj_attr_1 = OBJ_SIZE_MID, | ||
659 | .obj_attr_2 = 0 | ||
660 | }, | ||
661 | { | ||
662 | .id = 0, | ||
663 | .x = SCREEN_WIDTH / 2 + 32 + 20, | ||
664 | .y = SCREEN_HEIGHT / 2 + 32 + 8, | ||
665 | .data = &gba_btn_ab_shadow_data, | ||
666 | .n_tiles = 2, | ||
667 | .frames = 1, | ||
668 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
669 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
670 | .obj_attr_2 = 0 | ||
671 | }, | ||
672 | { | ||
673 | .id = 0, | ||
674 | .x = SCREEN_WIDTH / 2 + 32 - 8 + 20, | ||
675 | .y = SCREEN_HEIGHT / 2 + 33 + 8 - 16, | ||
676 | .data = &gba_btn_fx_ab, | ||
677 | .n_tiles = 16, | ||
678 | .frames = 3, | ||
679 | .obj_attr_0 = OBJ_SHAPE_SQUARE, | ||
680 | .obj_attr_1 = OBJ_SIZE_BIG, | ||
681 | .obj_attr_2 = 0 | ||
682 | }, | ||
683 | }, | ||
684 | }, | ||
685 | // START. | ||
686 | { | ||
687 | .frame = 0, | ||
688 | .n_obj = 3, | ||
689 | .n_frames = 8, | ||
690 | .state = BTN_STATE_RELEASED, | ||
691 | .animations = &btn_animation_startselect, | ||
692 | .sprites = &(ObjState[]){ | ||
693 | { | ||
694 | .id = 0, | ||
695 | .x = SCREEN_WIDTH / 2 - 10 + 26 - 14, | ||
696 | .y = SCREEN_HEIGHT / 2 + 40 + 10, | ||
697 | .data = &gba_btn_startselect_data, | ||
698 | .n_tiles = 2, | ||
699 | .frames = 2, | ||
700 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
701 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
702 | .obj_attr_2 = 0 | ||
703 | }, | ||
704 | { | ||
705 | .id = 0, | ||
706 | .x = SCREEN_WIDTH / 2 - 18 + 26 - 14, | ||
707 | .y = SCREEN_HEIGHT / 2 + 46 + 10, | ||
708 | .data = &gba_btn_start_text, | ||
709 | .n_tiles = 4, | ||
710 | .frames = 1, | ||
711 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
712 | .obj_attr_1 = OBJ_SIZE_MID, | ||
713 | .obj_attr_2 = 0 | ||
714 | }, | ||
715 | { | ||
716 | .id = 0, | ||
717 | .x = SCREEN_WIDTH / 2 - 19 + 26 - 14, | ||
718 | .y = SCREEN_HEIGHT / 2 + 37 + 10, | ||
719 | .data = &gba_btn_fx_startselect, | ||
720 | .n_tiles = 4, | ||
721 | .frames = 4, | ||
722 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
723 | .obj_attr_1 = OBJ_SIZE_MID, | ||
724 | .obj_attr_2 = 0 | ||
725 | }, | ||
726 | }, | ||
727 | }, | ||
728 | // SELECT. | ||
729 | { | ||
730 | .frame = 0, | ||
731 | .n_obj = 3, | ||
732 | .n_frames = 8, | ||
733 | .state = BTN_STATE_RELEASED, | ||
734 | .animations = &btn_animation_startselect, | ||
735 | .sprites = &(ObjState[]){ | ||
736 | { | ||
737 | .id = 0, | ||
738 | .x = SCREEN_WIDTH / 2 - 10 - 14, | ||
739 | .y = SCREEN_HEIGHT / 2 + 40 + 10, | ||
740 | .data = &gba_btn_startselect_data, | ||
741 | .n_tiles = 2, | ||
742 | .frames = 2, | ||
743 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
744 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
745 | .obj_attr_2 = 0 | ||
746 | }, | ||
747 | { | ||
748 | .id = 0, | ||
749 | .x = SCREEN_WIDTH / 2 - 18 - 14, | ||
750 | .y = SCREEN_HEIGHT / 2 + 46 + 10, | ||
751 | .data = &gba_btn_select_text, | ||
752 | .n_tiles = 4, | ||
753 | .frames = 1, | ||
754 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
755 | .obj_attr_1 = OBJ_SIZE_MID, | ||
756 | .obj_attr_2 = 0 | ||
757 | }, | ||
758 | { | ||
759 | .id = 0, | ||
760 | .x = SCREEN_WIDTH / 2 - 19 - 14, | ||
761 | .y = SCREEN_HEIGHT / 2 + 37 + 10, | ||
762 | .data = &gba_btn_fx_startselect, | ||
763 | .n_tiles = 4, | ||
764 | .frames = 4, | ||
765 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
766 | .obj_attr_1 = OBJ_SIZE_MID, | ||
767 | .obj_attr_2 = 0 | ||
768 | }, | ||
769 | }, | ||
770 | }, | ||
771 | }; | ||
772 | |||
773 | static inline void | ||
774 | init_button_sprites(void) { | ||
775 | for (size_t i = 0; i < sizeof(buttons) / sizeof(ButtonSprite); ++i) { | ||
776 | init_button_sprite(&buttons[i]); | ||
777 | } | ||
778 | } | ||
779 | |||
780 | static inline void | ||
781 | update_button_sprites(void) { | ||
782 | if (key_pressed(KEY_DOWN)) { | ||
783 | buttons[0].state = BTN_STATE_PRESSED; | ||
784 | } else if (key_hold(KEY_DOWN)) { | ||
785 | buttons[0].state = BTN_STATE_HOLD; | ||
786 | } else { | ||
787 | buttons[0].state = BTN_STATE_RELEASED; | ||
788 | } | ||
789 | if (key_pressed(KEY_UP)) { | ||
790 | buttons[1].state = BTN_STATE_PRESSED; | ||
791 | } else if (key_hold(KEY_UP)) { | ||
792 | buttons[1].state = BTN_STATE_HOLD; | ||
793 | } else { | ||
794 | buttons[1].state = BTN_STATE_RELEASED; | ||
795 | } | ||
796 | if (key_pressed(KEY_LEFT)) { | ||
797 | buttons[2].state = BTN_STATE_PRESSED; | ||
798 | } else if (key_hold(KEY_LEFT)) { | ||
799 | buttons[2].state = BTN_STATE_HOLD; | ||
800 | } else { | ||
801 | buttons[2].state = BTN_STATE_RELEASED; | ||
802 | } | ||
803 | if (key_pressed(KEY_RIGHT)) { | ||
804 | buttons[3].state = BTN_STATE_PRESSED; | ||
805 | } else if (key_hold(KEY_RIGHT)) { | ||
806 | buttons[3].state = BTN_STATE_HOLD; | ||
807 | } else { | ||
808 | buttons[3].state = BTN_STATE_RELEASED; | ||
809 | } | ||
810 | if (key_pressed(KEY_L)) { | ||
811 | buttons[4].state = BTN_STATE_PRESSED; | ||
812 | } else if (key_hold(KEY_L)) { | ||
813 | buttons[4].state = BTN_STATE_HOLD; | ||
814 | } else { | ||
815 | buttons[4].state = BTN_STATE_RELEASED; | ||
816 | } | ||
817 | if (key_pressed(KEY_R)) { | ||
818 | buttons[5].state = BTN_STATE_PRESSED; | ||
819 | } else if (key_hold(KEY_R)) { | ||
820 | buttons[5].state = BTN_STATE_HOLD; | ||
821 | } else { | ||
822 | buttons[5].state = BTN_STATE_RELEASED; | ||
823 | } | ||
824 | if (key_pressed(KEY_A)) { | ||
825 | buttons[6].state = BTN_STATE_PRESSED; | ||
826 | } else if (key_hold(KEY_A)) { | ||
827 | buttons[6].state = BTN_STATE_HOLD; | ||
828 | } else { | ||
829 | buttons[6].state = BTN_STATE_RELEASED; | ||
830 | } | ||
831 | if (key_pressed(KEY_B)) { | ||
832 | buttons[7].state = BTN_STATE_PRESSED; | ||
833 | } else if (key_hold(KEY_B)) { | ||
834 | buttons[7].state = BTN_STATE_HOLD; | ||
835 | } else { | ||
836 | buttons[7].state = BTN_STATE_RELEASED; | ||
837 | } | ||
838 | if (key_pressed(KEY_START)) { | ||
839 | buttons[8].state = BTN_STATE_PRESSED; | ||
840 | } else if (key_hold(KEY_START)) { | ||
841 | buttons[8].state = BTN_STATE_HOLD; | ||
842 | } else { | ||
843 | buttons[8].state = BTN_STATE_RELEASED; | ||
844 | } | ||
845 | if (key_pressed(KEY_SELECT)) { | ||
846 | buttons[9].state = BTN_STATE_PRESSED; | ||
847 | } else if (key_hold(KEY_SELECT)) { | ||
848 | buttons[9].state = BTN_STATE_HOLD; | ||
849 | } else { | ||
850 | buttons[9].state = BTN_STATE_RELEASED; | ||
851 | } | ||
852 | |||
853 | for (size_t i = 0; i < sizeof(buttons) / sizeof(ButtonSprite); ++i) { | ||
854 | button_tick(&buttons[i]); | ||
855 | } | ||
856 | } | ||
@@ -1,677 +1,17 @@ | |||
1 | #include <string.h> | 1 | #include <string.h> |
2 | 2 | ||
3 | #include "shorthand.h" | 3 | #include "common.h" |
4 | #include "bd-font.c" | ||
5 | #include "gba-buttons.c" | 4 | #include "gba-buttons.c" |
6 | #include "background-tiles.c" | 5 | #include "background-tiles.c" |
7 | 6 | #include "sprites.h" | |
8 | // | ||
9 | // Memory sections. | ||
10 | // | ||
11 | |||
12 | // Defines for the different memory sections in the GBA. | ||
13 | #define MEM_SROM 0x00000000 | ||
14 | #define MEM_EW 0x02000000 | ||
15 | #define MEM_IW 0x03000000 | ||
16 | #define MEM_IO 0x04000000 | ||
17 | #define MEM_PAL 0x05000000 | ||
18 | #define MEM_VRAM 0x06000000 | ||
19 | #define MEM_OAM 0x07000000 | ||
20 | #define MEM_PAK 0x08000000 | ||
21 | #define MEM_CART 0x0E000000 | ||
22 | |||
23 | // | ||
24 | // Display modes. | ||
25 | // | ||
26 | |||
27 | // Display registers. | ||
28 | #define DISP_CTRL *((vu32*)(MEM_IO + 0x0000)) | ||
29 | #define DISP_STATUS *((vu16*)(MEM_IO + 0x0004)) | ||
30 | #define DISP_VCOUNT *((vu16*)(MEM_IO + 0x0006)) | ||
31 | |||
32 | // The first three bits in the DISP_CTRL are used to control the video mode. | ||
33 | #define DISP_MODE_0 0x0000 | ||
34 | #define DISP_MODE_1 0x0001 | ||
35 | #define DISP_MODE_2 0x0002 | ||
36 | #define DISP_MODE_3 0x0003 | ||
37 | #define DISP_MODE_4 0x0004 | ||
38 | #define DISP_MODE_5 0x0005 | ||
39 | #define DISP_GB (1 << 3) | ||
40 | #define DISP_PAGE (1 << 4) | ||
41 | #define DISP_OAM_HBLANK (1 << 5) | ||
42 | #define DISP_OBJ_1D (1 << 6) | ||
43 | #define DISP_BLANK (1 << 7) | ||
44 | #define DISP_BG_0 (1 << 8) | ||
45 | #define DISP_BG_1 (1 << 9) | ||
46 | #define DISP_BG_2 (1 << 10) | ||
47 | #define DISP_BG_3 (1 << 11) | ||
48 | #define DISP_OBJ (1 << 12) | ||
49 | #define DISP_ENABLE_SPRITES DISP_OBJ | DISP_OBJ_1D | ||
50 | |||
51 | // Registers to control of BG layers. | ||
52 | #define BG_CTRL_0 *((vu16*)(0x04000008 + 0x0002 * 0)) | ||
53 | #define BG_CTRL_1 *((vu16*)(0x04000008 + 0x0002 * 1)) | ||
54 | #define BG_CTRL_2 *((vu16*)(0x04000008 + 0x0002 * 2)) | ||
55 | #define BG_CTRL_3 *((vu16*)(0x04000008 + 0x0002 * 3)) | ||
56 | |||
57 | // Bits to control the background. | ||
58 | #define BG_PRIORITY_0 0x0 | ||
59 | #define BG_PRIORITY_1 0x1 | ||
60 | #define BG_PRIORITY_2 0x2 | ||
61 | #define BG_PRIORITY_3 0x3 | ||
62 | #define BG_CHARBLOCK(N) ((N) << 2) | ||
63 | #define BG_MOSAIC (1 << 6) | ||
64 | #define BG_HIGH_COLOR (1 << 7) | ||
65 | #define BG_SCREENBLOCK(N) ((N) << 8) | ||
66 | #define BG_AFFINE (1 << 0xD) | ||
67 | #define BG_SIZE(N) ((N) << 0xE) | ||
68 | |||
69 | // BG registers for horizontal displacement. | ||
70 | #define BG_H_SCROLL_0 *((vu16*)(0x04000010 + 0x0004 * 0)) | ||
71 | #define BG_H_SCROLL_1 *((vu16*)(0x04000010 + 0x0004 * 1)) | ||
72 | #define BG_H_SCROLL_2 *((vu16*)(0x04000010 + 0x0004 * 2)) | ||
73 | #define BG_H_SCROLL_3 *((vu16*)(0x04000010 + 0x0004 * 3)) | ||
74 | |||
75 | // BG registers for vertical displacement. | ||
76 | #define BG_V_SCROLL_0 *((vu16*)(0x04000012 + 0x0004 * 0)) | ||
77 | #define BG_V_SCROLL_1 *((vu16*)(0x04000012 + 0x0004 * 1)) | ||
78 | #define BG_V_SCROLL_2 *((vu16*)(0x04000012 + 0x0004 * 2)) | ||
79 | #define BG_V_SCROLL_3 *((vu16*)(0x04000012 + 0x0004 * 3)) | ||
80 | |||
81 | // Screen settings. | ||
82 | #define SCREEN_WIDTH 240 | ||
83 | #define SCREEN_HEIGHT 160 | ||
84 | |||
85 | // The GBA in mode 3 expects rbg15 colors in the VRAM, where each component | ||
86 | // (RGB) have a 0--31 range. For example, pure red would be rgb15(31, 0, 0). | ||
87 | typedef u16 Color; | ||
88 | |||
89 | // | ||
90 | // Tile memory access. | ||
91 | // | ||
92 | |||
93 | // NOTE: Only defining 4bpp tiles for now. | ||
94 | typedef struct Tile { | ||
95 | u32 data[8]; | ||
96 | } Tile; | ||
97 | |||
98 | typedef Tile TileBlock[512]; | ||
99 | #define TILE_MEM ((TileBlock*) MEM_VRAM) | ||
100 | |||
101 | typedef u16 ScreenBlock[1024]; | ||
102 | #define SCREENBLOCK_MEM ((ScreenBlock*)MEM_VRAM) | ||
103 | |||
104 | // We can treat the screen as a HxW matrix. With the following macro we can | ||
105 | // write a pixel to the screen at the (x, y) position using: | ||
106 | // | ||
107 | // FRAMEBUFFER[y][x] = color; | ||
108 | // | ||
109 | typedef Color Scanline[SCREEN_WIDTH]; | ||
110 | #define FRAMEBUFFER ((Scanline*)MEM_VRAM) | ||
111 | #define SCREEN_BUFFER ((u16*) MEM_VRAM) | ||
112 | #define PAL_BUFFER_BG ((u16*) MEM_PAL) | ||
113 | #define PAL_BUFFER_SPRITES ((u16*) 0x05000200) | ||
114 | |||
115 | // | ||
116 | // Colors. | ||
117 | // | ||
118 | |||
119 | static inline Color | ||
120 | rgb15(u32 red, u32 green, u32 blue ) { | ||
121 | return (blue << 10) | (green << 5) | red; | ||
122 | } | ||
123 | |||
124 | #define COLOR_RED rgb15(31, 0, 12) | ||
125 | #define COLOR_BLUE rgb15(2, 15, 30) | ||
126 | #define COLOR_CYAN rgb15(0, 30, 30) | ||
127 | #define COLOR_GREY rgb15(4, 4, 4) | ||
128 | #define COLOR_BLACK rgb15(0, 0, 0) | ||
129 | #define COLOR_WHITE rgb15(28, 28, 28) | ||
130 | |||
131 | // | ||
132 | // Sprites. | ||
133 | // | ||
134 | |||
135 | // Using macros instead of aligned structs for setting up OBJ attributes and | ||
136 | // affine parameters. | ||
137 | // TODO: Benchmark if this would be slower or the same that TONC's | ||
138 | // implementation. | ||
139 | #define OBJ_ATTR_0(N) *((vu16*)(MEM_OAM + 0 + 8 * (N))) | ||
140 | #define OBJ_ATTR_1(N) *((vu16*)(MEM_OAM + 2 + 8 * (N))) | ||
141 | #define OBJ_ATTR_2(N) *((vu16*)(MEM_OAM + 4 + 8 * (N))) | ||
142 | #define OBJ_AFFINE_PA(N) *((vs16*)(MEM_OAM + 6 + 8 * 0 + 8 * 4 * (N))) | ||
143 | #define OBJ_AFFINE_PB(N) *((vs16*)(MEM_OAM + 6 + 8 * 1 + 8 * 4 * (N))) | ||
144 | #define OBJ_AFFINE_PC(N) *((vs16*)(MEM_OAM + 6 + 8 * 2 + 8 * 4 * (N))) | ||
145 | #define OBJ_AFFINE_PD(N) *((vs16*)(MEM_OAM + 6 + 8 * 3 + 8 * 4 * (N))) | ||
146 | |||
147 | // OBJ_ATTR_0 parameters | ||
148 | #define OBJ_Y_COORD(N) ((N) & 0xFF) | ||
149 | #define OBJ_NORMAL (0x00 << 0x8) | ||
150 | #define OBJ_AFFINE (0x01 << 0x8) | ||
151 | #define OBJ_HIDDEN (0x02 << 0x8) | ||
152 | #define OBJ_AFFINE_2X (0x03 << 0x8) | ||
153 | #define OBJ_ALPHA_BLEND (0x01 << 0xA) | ||
154 | #define OBJ_WINDOW (0x02 << 0xA) | ||
155 | #define OBJ_SHAPE_SQUARE (0x00 << 0xE) | ||
156 | #define OBJ_SHAPE_WIDE (0x01 << 0xE) | ||
157 | #define OBJ_SHAPE_TALL (0x02 << 0xE) | ||
158 | |||
159 | // OBJ_ATTR_1 parameters | ||
160 | #define OBJ_X_COORD(N) ((N) & 0x1FF) | ||
161 | #define OBJ_AFFINE_IDX(N) ((N) << 0x9) | ||
162 | #define OBJ_H_FLIP (0x01 << 0xC) | ||
163 | #define OBJ_V_FLIP (0x01 << 0xD) | ||
164 | #define OBJ_SIZE_SMALL (0x00 << 0xE) | ||
165 | #define OBJ_SIZE_MID (0x01 << 0xE) | ||
166 | #define OBJ_SIZE_BIG (0x02 << 0xE) | ||
167 | #define OBJ_SIZE_HUGE (0x03 << 0xE) | ||
168 | |||
169 | // OBJ_ATTR_2 parameters | ||
170 | #define OBJ_TILE_INDEX(N) ((N) & 0x3FF) | ||
171 | #define OBJ_PRIORITY(N) ((N) << 0xA) | ||
172 | #define OBJ_PAL_BANK(N) ((N) << 0xC) | ||
173 | |||
174 | // Using bd-font, an 8x8 bitmap font. | ||
175 | static void | ||
176 | put_char(int x, int y, Color clr, u8 chr) { | ||
177 | for (size_t i = 0; i < 8; ++i) { | ||
178 | for (size_t j = 0; j < 8; ++j) { | ||
179 | if ((font[chr][i] >> (7 - j)) & 0x1) { | ||
180 | FRAMEBUFFER[y + i][x + j] = clr; | ||
181 | } | ||
182 | } | ||
183 | } | ||
184 | } | ||
185 | |||
186 | static void | ||
187 | put_text(int x, int y, Color clr, char *msg) { | ||
188 | int count = 0; | ||
189 | while (*msg) { | ||
190 | put_char(x + count, y, clr, *msg++); | ||
191 | count += 8; | ||
192 | } | ||
193 | } | ||
194 | |||
195 | // Draws a line with the given color between (x0,y0) and (x1,y1) using the | ||
196 | // Bresenham's line drawing algorithm using exclusively integer arithmetic. | ||
197 | static void | ||
198 | draw_line(int x0, int y0, int x1, int y1, Color clr) { | ||
199 | // Pointer to the initial position of the screen buffer where we will start | ||
200 | // writing our data. | ||
201 | vu16 *destination = (u16*)(SCREEN_BUFFER + y0 * SCREEN_WIDTH + x0); | ||
202 | |||
203 | // Adjust the step direction and calculate deltas. | ||
204 | int x_step; | ||
205 | int y_step; | ||
206 | int dx; | ||
207 | int dy; | ||
208 | if (x0 > x1) { | ||
209 | x_step = -1; | ||
210 | dx = x0 - x1; | ||
211 | } else { | ||
212 | x_step = 1; | ||
213 | dx = x1 - x0; | ||
214 | } | ||
215 | if (y0 > y1) { | ||
216 | y_step = -SCREEN_WIDTH; | ||
217 | dy = y0 - y1; | ||
218 | } else { | ||
219 | y_step = +SCREEN_WIDTH; | ||
220 | dy = y1 - y0; | ||
221 | } | ||
222 | |||
223 | if(dy == 0) { | ||
224 | // Horizontal line. | ||
225 | for(int i = 0; i <= dx; i++) { | ||
226 | destination[i * x_step] = clr; | ||
227 | } | ||
228 | } else if(dx == 0) { | ||
229 | // Vertical line. | ||
230 | for(int i = 0; i <= dy; i++) { | ||
231 | destination[i * y_step] = clr; | ||
232 | } | ||
233 | } else if (dx >= dy){ | ||
234 | // Positive slope. | ||
235 | int diff = 2 * dy - dx; | ||
236 | for (int i = 0; i <= dx; ++i) { | ||
237 | *destination = clr; | ||
238 | if (diff >= 0) { | ||
239 | destination += y_step; | ||
240 | diff -= 2 * dx; | ||
241 | } | ||
242 | destination += x_step; | ||
243 | diff += 2 * dy; | ||
244 | } | ||
245 | } else { | ||
246 | // Negative slope. | ||
247 | int diff = 2 * dx - dy; | ||
248 | for (int i = 0; i <= dy; ++i) { | ||
249 | *destination = clr; | ||
250 | if (diff >= 0) { | ||
251 | destination += x_step; | ||
252 | diff -= 2 * dy; | ||
253 | } | ||
254 | destination += y_step; | ||
255 | diff += 2 * dx; | ||
256 | } | ||
257 | } | ||
258 | } | ||
259 | |||
260 | static inline void | ||
261 | draw_rect(int x0, int y0, int x1, int y1, Color clr) { | ||
262 | if (x0 > x1) { | ||
263 | int tmp = x0; | ||
264 | x0 = x1; | ||
265 | x1 = tmp; | ||
266 | } | ||
267 | if (y0 > y1) { | ||
268 | int tmp = y0; | ||
269 | y0 = y1; | ||
270 | y1 = tmp; | ||
271 | } | ||
272 | int dx = x1 - x0; | ||
273 | int dy = y1 - y0; | ||
274 | for (int i = 0; i <= dx; ++i) { | ||
275 | int x = x0 + i; | ||
276 | FRAMEBUFFER[y0][x] = clr; | ||
277 | FRAMEBUFFER[y1][x] = clr; | ||
278 | } | ||
279 | for (int j = 0; j <= dy; ++j) { | ||
280 | int y = y0 + j; | ||
281 | FRAMEBUFFER[y][x0] = clr; | ||
282 | FRAMEBUFFER[y][x1] = clr; | ||
283 | } | ||
284 | } | ||
285 | |||
286 | static inline void | ||
287 | draw_fill_rect(int x0, int y0, int x1, int y1, Color clr) { | ||
288 | if (x0 > x1) { | ||
289 | int tmp = x0; | ||
290 | x0 = x1; | ||
291 | x1 = tmp; | ||
292 | } | ||
293 | if (y0 > y1) { | ||
294 | int tmp = y0; | ||
295 | y0 = y1; | ||
296 | y1 = tmp; | ||
297 | } | ||
298 | int dx = x1 - x0; | ||
299 | int dy = y1 - y0; | ||
300 | for (int i = 0; i <= dx; ++i) { | ||
301 | for (int j = 0; j <= dy; ++j) { | ||
302 | int x = x0 + i; | ||
303 | int y = y0 + j; | ||
304 | FRAMEBUFFER[y][x] = clr; | ||
305 | } | ||
306 | } | ||
307 | } | ||
308 | |||
309 | static inline void | ||
310 | wait_vsync(void) { | ||
311 | while(DISP_VCOUNT >= 160); | ||
312 | while(DISP_VCOUNT < 160); | ||
313 | } | ||
314 | 7 | ||
315 | // | 8 | // |
316 | // Main functions. | 9 | // Main functions. |
317 | // | 10 | // |
318 | 11 | ||
319 | // In Mode4 the buffer is of 8 bytes per pixel instead of 16. We can't write the | 12 | // TODO: Cleanup OBJ/OAM memory copying and access. |
320 | // color directly, instead the color is stored in the palette memory at | ||
321 | // `MEM_PAL`. Note that in this mode MEM_PAL[0] is the background color. This | ||
322 | // plotter takes an index to a color stored in MEM_PAL[col_index]. Because the | ||
323 | // GBA needs to meet memory alignment requirements, we can't write a u8 into | ||
324 | // memory, instead we need to read a u16 word, mask and or the corresponding | ||
325 | // bits and wave the updated u16. | ||
326 | static void | ||
327 | put_pixel_m4(int x, int y, u8 col_index, vu16 *buffer) { | ||
328 | int buffer_index = (y * SCREEN_WIDTH + x) / 2; | ||
329 | vu16 *destination = &buffer[buffer_index]; | ||
330 | // Odd pixels will go to the top 8 bits of the destination. Even pixels to | ||
331 | // the lower 8 bits. | ||
332 | int odd = x & 0x1; | ||
333 | if(odd) { | ||
334 | *destination= (*destination & 0xFF) | (col_index << 8); | ||
335 | } else { | ||
336 | *destination= (*destination & ~0xFF) | col_index; | ||
337 | } | ||
338 | } | ||
339 | |||
340 | static void | ||
341 | draw_fill_rect_m4(int x0, int y0, int x1, int y1, u8 col_index, vu16 *buffer) { | ||
342 | int ix, iy; | ||
343 | for(iy = y0; iy < y1; iy++) { | ||
344 | for(ix = x0; ix < x1; ix++) { | ||
345 | put_pixel_m4(ix, iy, col_index, buffer); | ||
346 | } | ||
347 | } | ||
348 | } | ||
349 | |||
350 | static inline void | ||
351 | flip_page(void) { | ||
352 | DISP_CTRL ^= DISP_PAGE; | ||
353 | } | ||
354 | |||
355 | #define SCREEN_PAGE_1 ((vu16*) MEM_VRAM) | ||
356 | #define SCREEN_PAGE_2 ((vu16*) (MEM_VRAM + 0xa000)) | ||
357 | |||
358 | // | ||
359 | // Profiling. | ||
360 | // | ||
361 | |||
362 | #define TIMER_DATA_0 *((vu16*) (0x04000100 + 0x04 * 0)) | ||
363 | #define TIMER_DATA_1 *((vu16*) (0x04000100 + 0x04 * 1)) | ||
364 | #define TIMER_DATA_2 *((vu16*) (0x04000100 + 0x04 * 2)) | ||
365 | #define TIMER_DATA_3 *((vu16*) (0x04000100 + 0x04 * 3)) | ||
366 | #define TIMER_CTRL_0 *((vu16*) (0x04000102 + 0x04 * 0)) | ||
367 | #define TIMER_CTRL_1 *((vu16*) (0x04000102 + 0x04 * 1)) | ||
368 | #define TIMER_CTRL_2 *((vu16*) (0x04000102 + 0x04 * 2)) | ||
369 | #define TIMER_CTRL_3 *((vu16*) (0x04000102 + 0x04 * 3)) | ||
370 | |||
371 | // Timer control bits. | ||
372 | #define TIMER_CTRL_FREQ_0 0 | ||
373 | #define TIMER_CTRL_FREQ_1 1 | ||
374 | #define TIMER_CTRL_FREQ_2 2 | ||
375 | #define TIMER_CTRL_FREQ_3 3 | ||
376 | #define TIMER_CTRL_CASCADE (1 << 2) | ||
377 | #define TIMER_CTRL_IRQ (1 << 6) | ||
378 | #define TIMER_CTRL_ENABLE (1 << 7) | ||
379 | |||
380 | // We use timers 2 and 3 to count the number of cycles since the profile_start | ||
381 | // functions is called. Don't use if the code we are trying to profile make use | ||
382 | // of these timers. | ||
383 | static inline | ||
384 | void profile_start(void) { | ||
385 | TIMER_DATA_2 = 0; | ||
386 | TIMER_DATA_3 = 0; | ||
387 | TIMER_CTRL_2 = 0; | ||
388 | TIMER_CTRL_3 = 0; | ||
389 | TIMER_CTRL_3 = TIMER_CTRL_ENABLE | TIMER_CTRL_CASCADE; | ||
390 | TIMER_CTRL_2 = TIMER_CTRL_ENABLE; | ||
391 | } | ||
392 | |||
393 | static inline | ||
394 | u32 profile_stop(void) { | ||
395 | TIMER_CTRL_2 = 0; | ||
396 | return (TIMER_DATA_3 << 16) | TIMER_DATA_2; | ||
397 | } | ||
398 | |||
399 | // | ||
400 | // Input handling. | ||
401 | // | ||
402 | |||
403 | // Memory address for key input register | ||
404 | #define KEY_INPUTS *((vu16*) 0x04000130) | ||
405 | |||
406 | // Alias for key pressing bits. | ||
407 | #define KEY_A (1 << 0) | ||
408 | #define KEY_B (1 << 1) | ||
409 | #define KEY_SELECT (1 << 2) | ||
410 | #define KEY_START (1 << 3) | ||
411 | #define KEY_RIGHT (1 << 4) | ||
412 | #define KEY_LEFT (1 << 5) | ||
413 | #define KEY_UP (1 << 6) | ||
414 | #define KEY_DOWN (1 << 7) | ||
415 | #define KEY_R (1 << 8) | ||
416 | #define KEY_L (1 << 9) | ||
417 | |||
418 | #define KEY_MASK 0x03FF | ||
419 | |||
420 | // Saving the previous and current key states as globals for now. | ||
421 | static u16 key_curr = 0; | ||
422 | static u16 key_prev = 0; | ||
423 | |||
424 | static inline void | ||
425 | poll_keys(void) { | ||
426 | key_prev = key_curr; | ||
427 | key_curr = ~KEY_INPUTS & KEY_MASK; | ||
428 | } | ||
429 | |||
430 | // Returns true if the given key has been pressed at time of calling and was not | ||
431 | // pressed since the previous call. For example, if a key is being held, this | ||
432 | // function will return `true` only on the frame where the key initially | ||
433 | // activated. | ||
434 | static inline u32 | ||
435 | key_pressed(u32 key) { | ||
436 | return (key_curr & key) & ~(key_prev & key); | ||
437 | } | ||
438 | |||
439 | // Check if the given key is pressed and has been since at least one frame. | ||
440 | static inline u32 | ||
441 | key_hold(u32 key) { | ||
442 | return (key_curr & key) & key_prev & key; | ||
443 | } | ||
444 | |||
445 | // Check if the given key/button is currently pressed. | ||
446 | #define KEY_PRESSED(key) (~(KEY_INPUTS) & key) | ||
447 | |||
448 | void | ||
449 | draw_logo(void) { | ||
450 | int side = 60; | ||
451 | int line = 35; | ||
452 | int height = side * 0.5; | ||
453 | int x = SCREEN_WIDTH / 2 - height / 2; | ||
454 | int y = SCREEN_HEIGHT / 2; | ||
455 | |||
456 | // Draw red triangle. | ||
457 | draw_line(x + height - 1, y - side / 2, x, y - 1, COLOR_RED); | ||
458 | draw_line(x + height - 1, y + side / 2, x, y + 1, COLOR_RED); | ||
459 | draw_line(x + height - 1, y - side / 2 + 1, x, y, COLOR_RED); | ||
460 | draw_line(x + height - 1, y + side / 2 - 1, x, y, COLOR_RED); | ||
461 | |||
462 | // Draw white triangle. | ||
463 | draw_line(x, y - side / 2, x, y + side / 2, COLOR_WHITE); | ||
464 | draw_line(x + 1, y - side / 2, x + height, y - 1, COLOR_WHITE); | ||
465 | draw_line(x + 1, y + side / 2, x + height, y + 1, COLOR_WHITE); | ||
466 | |||
467 | // Draw white line at triangle tip. | ||
468 | draw_line(x + height, y - side / 2, x + height, y + side / 2, COLOR_WHITE); | ||
469 | draw_line(x + height + 1, y - side / 2, x + height + 1, y + side / 2, COLOR_WHITE); | ||
470 | |||
471 | // Double triangle line. | ||
472 | draw_line(x - 1, y - side / 2, x - 1, y + side / 2, COLOR_WHITE); | ||
473 | draw_line(x + 1, y - side / 2 + 1, x + height, y, COLOR_WHITE); | ||
474 | draw_line(x + 1, y + side / 2 - 1, x + height, y, COLOR_WHITE); | ||
475 | |||
476 | // Draw white lines. | ||
477 | draw_line(x - line, y, x, y, COLOR_WHITE); | ||
478 | draw_line(x + height, y, x + height + line, y, COLOR_WHITE); | ||
479 | draw_line(x - line, y + 1, x, y + 1, COLOR_WHITE); | ||
480 | draw_line(x + height, y + 1, x + height + line, y + 1, COLOR_WHITE); | ||
481 | } | ||
482 | |||
483 | void | ||
484 | copy_font_to_tile_memory(Tile *tile) { | ||
485 | // Hex to bits translation table. | ||
486 | const u32 conversion_u32[16] = { | ||
487 | 0x00000000, 0x00001000, 0x00000100, 0x00001100, | ||
488 | 0x00000010, 0x00001010, 0x00000110, 0x00001110, | ||
489 | 0x00000001, 0x00001001, 0x00000101, 0x00001101, | ||
490 | 0x00000011, 0x00001011, 0x00000111, 0x00001111, | ||
491 | }; | ||
492 | for (size_t i = 0; i < 250; ++i) { | ||
493 | for (size_t j = 0; j < 8; ++j) { | ||
494 | u8 row = font[i][j]; | ||
495 | u32 tile_idx = 0x00000000; | ||
496 | tile_idx = conversion_u32[row & 0xF] << 16; | ||
497 | tile_idx |= conversion_u32[(row >> 4) & 0xF]; | ||
498 | (tile + i)->data[j] = tile_idx; | ||
499 | } | ||
500 | } | ||
501 | } | ||
502 | |||
503 | |||
504 | u32 | ||
505 | unpack_1bb(u8 hex) { | ||
506 | const u32 conversion_u32[16] = { | ||
507 | 0x00000000, 0x00000001, 0x00000010, 0x00000011, | ||
508 | 0x00000100, 0x00000101, 0x00000110, 0x00000111, | ||
509 | 0x00001000, 0x00001001, 0x00001010, 0x00001011, | ||
510 | 0x00001100, 0x00001101, 0x00001110, 0x00001111, | ||
511 | }; | ||
512 | u8 low = hex & 0xF; | ||
513 | u8 high = (hex >> 4) & 0xF; | ||
514 | return (conversion_u32[high] << 16) | conversion_u32[low]; | ||
515 | } | ||
516 | |||
517 | typedef struct Sprite { | ||
518 | // A unique sprite identifier. | ||
519 | size_t id; | ||
520 | // The number of tiles for a single sprite frame. | ||
521 | size_t n_tiles; | ||
522 | // The starting tile of this sprite. | ||
523 | size_t tile_start; | ||
524 | // The associated palette bank for this sprite. | ||
525 | size_t pal_bank; | ||
526 | } Sprite; | ||
527 | |||
528 | typedef struct ButtonSprite { | ||
529 | int id; | ||
530 | int x; | ||
531 | int y; | ||
532 | int frame; | ||
533 | BtnState state; | ||
534 | } ButtonSprite; | ||
535 | |||
536 | typedef struct MultiSprite { | ||
537 | ObjState *sprites; | ||
538 | AnimationEntry **animations; | ||
539 | size_t frame; | ||
540 | size_t n_obj; | ||
541 | size_t n_frames; | ||
542 | BtnState state; | ||
543 | } MultiSprite; | ||
544 | |||
545 | #define NUM_SPRITES 128 | ||
546 | |||
547 | Sprite sprites[NUM_SPRITES]; | ||
548 | |||
549 | // Keeping track of unique sprites and current sprite memory pointer using | ||
550 | // global singletons. | ||
551 | size_t sprite_counter = 0; | ||
552 | size_t sprite_tile_counter = 0; | ||
553 | u32 *sprite_memory = NULL; | ||
554 | |||
555 | // Loads the sprite data into video memory and initialize the Sprite structure. | ||
556 | size_t | ||
557 | load_sprite_data(u32 *sprite_data, size_t n_tiles, size_t n_frames) { | ||
558 | memcpy(sprite_memory, sprite_data, 8 * n_tiles * n_frames * sizeof(u32)); | ||
559 | sprite_memory += 8 * n_tiles * n_frames; | ||
560 | Sprite sprite = { | ||
561 | .id = sprite_counter, | ||
562 | .n_tiles = n_tiles, | ||
563 | .tile_start = sprite_tile_counter, | ||
564 | }; | ||
565 | sprite_tile_counter += n_tiles * n_frames; | ||
566 | sprites[sprite_counter] = sprite; | ||
567 | return sprite_counter++; | ||
568 | } | ||
569 | |||
570 | size_t | ||
571 | load_packed_sprite_data(u32 *sprite_data, size_t n_tiles, size_t n_frames) { | ||
572 | size_t counter = 0; | ||
573 | for (size_t i = 0; i < 8 * n_tiles * n_frames / 4; ++i) { | ||
574 | u32 hex = sprite_data[i]; | ||
575 | sprite_memory[counter++] = unpack_1bb((hex >> 24) & 0xFF); | ||
576 | sprite_memory[counter++] = unpack_1bb((hex >> 16) & 0xFF); | ||
577 | sprite_memory[counter++] = unpack_1bb((hex >> 8) & 0xFF); | ||
578 | sprite_memory[counter++] = unpack_1bb((hex) & 0xFF); | ||
579 | } | ||
580 | sprite_memory += 8 * n_tiles * n_frames; | ||
581 | Sprite sprite = { | ||
582 | .id = sprite_counter, | ||
583 | .n_tiles = n_tiles, | ||
584 | .tile_start = sprite_tile_counter, | ||
585 | }; | ||
586 | sprite_tile_counter += n_tiles * n_frames; | ||
587 | sprites[sprite_counter] = sprite; | ||
588 | return sprite_counter++; | ||
589 | } | ||
590 | |||
591 | void | ||
592 | init_button_sprite(MultiSprite *btn) { | ||
593 | for (size_t i = 0; i < btn->n_obj; ++i) { | ||
594 | btn->sprites[i].id = load_packed_sprite_data( | ||
595 | btn->sprites[i].data, | ||
596 | btn->sprites[i].n_tiles, | ||
597 | btn->sprites[i].frames); | ||
598 | btn->sprites[i].base_tile = sprites[btn->sprites[i].id].tile_start; | ||
599 | } | ||
600 | } | ||
601 | |||
602 | void | ||
603 | button_tick(MultiSprite *btn) { | ||
604 | // Nothing to do here. | ||
605 | if (btn->state == BTN_STATE_IDLE) { | ||
606 | return; | ||
607 | } | ||
608 | |||
609 | // Reset animation state. | ||
610 | if (btn->state == BTN_STATE_PRESSED && btn->frame != 0) { | ||
611 | btn->frame = 0; | ||
612 | } | ||
613 | |||
614 | // Continue the animation. | ||
615 | if (btn->state == BTN_STATE_HOLD || btn->state == BTN_STATE_PRESSED ) { | ||
616 | if(btn->frame < btn->n_frames - 1) { | ||
617 | btn->frame++; | ||
618 | } | ||
619 | } | ||
620 | |||
621 | // Finish the animation and return to idle. | ||
622 | if (btn->state == BTN_STATE_RELEASED) { | ||
623 | if (btn->frame > 0 && btn->frame < btn->n_frames - 1) { | ||
624 | btn->frame++; | ||
625 | } else { | ||
626 | btn->frame = 0; | ||
627 | btn->state = BTN_STATE_IDLE; | ||
628 | } | ||
629 | } | ||
630 | for (size_t i = 0; i < btn->n_obj; ++i) { | ||
631 | AnimationEntry anim_frame = btn->animations[i][btn->frame]; | ||
632 | int x = btn->sprites[i].x + anim_frame.x_offset; | ||
633 | int y = btn->sprites[i].y + anim_frame.y_offset; | ||
634 | int base_tile = btn->sprites[i].base_tile + anim_frame.tile_offset; | ||
635 | |||
636 | // Clear the previous x/y coordinate and base tiles. | ||
637 | btn->sprites[i].obj_attr_0 &= ~0xFF; | ||
638 | btn->sprites[i].obj_attr_1 &= ~0x1FF; | ||
639 | btn->sprites[i].obj_attr_2 &= ~0x3FF; | ||
640 | |||
641 | // Update x/y/tile and hidden state from the animations. | ||
642 | btn->sprites[i].obj_attr_0 |= OBJ_Y_COORD(y); | ||
643 | btn->sprites[i].obj_attr_1 |= OBJ_X_COORD(x); | ||
644 | btn->sprites[i].obj_attr_2 |= base_tile; | ||
645 | if (anim_frame.hidden) { | ||
646 | btn->sprites[i].obj_attr_0 |= OBJ_HIDDEN; | ||
647 | } else { | ||
648 | btn->sprites[i].obj_attr_0 &= ~OBJ_HIDDEN; | ||
649 | } | ||
650 | |||
651 | // Update OBJ attributes. | ||
652 | OBJ_ATTR_0(btn->sprites[i].id) = btn->sprites[i].obj_attr_0; | ||
653 | OBJ_ATTR_1(btn->sprites[i].id) = btn->sprites[i].obj_attr_1; | ||
654 | OBJ_ATTR_2(btn->sprites[i].id) = btn->sprites[i].obj_attr_2; | ||
655 | } | ||
656 | } | ||
657 | 13 | ||
658 | int main(void) { | 14 | int main(void) { |
659 | |||
660 | // Add colors to the sprite color palette. Tiles with color number 0 are | ||
661 | // treated as transparent. | ||
662 | for (size_t i = 0; i < 16; ++i) { | ||
663 | PAL_BUFFER_SPRITES[i] = COLOR_WHITE; | ||
664 | } | ||
665 | |||
666 | // Initialize all attributes by disabling rendering. If we don't do this, | ||
667 | // glitches may appear. | ||
668 | for (size_t i = 0; i < 128; ++i) { | ||
669 | OBJ_ATTR_0(i) = (1 << 9); | ||
670 | } | ||
671 | |||
672 | sprite_tile_counter = 0; | ||
673 | sprite_memory = &TILE_MEM[4][sprite_tile_counter]; | ||
674 | |||
675 | // Load background palette. | 15 | // Load background palette. |
676 | memcpy(&PAL_BUFFER_BG[0], &bg_palette, 512); | 16 | memcpy(&PAL_BUFFER_BG[0], &bg_palette, 512); |
677 | memcpy(&TILE_MEM[0][0], bg_data, 3168); | 17 | memcpy(&TILE_MEM[0][0], bg_data, 3168); |
@@ -687,445 +27,10 @@ int main(void) { | |||
687 | // sequential. | 27 | // sequential. |
688 | DISP_CTRL = DISP_ENABLE_SPRITES | DISP_MODE_0 | DISP_BG_0; | 28 | DISP_CTRL = DISP_ENABLE_SPRITES | DISP_MODE_0 | DISP_BG_0; |
689 | 29 | ||
690 | // Initialize the A/B button sprites. | 30 | // Initialize sprite button overlay. |
691 | int buttons_x = SCREEN_WIDTH / 2; | 31 | init_sprite_pal(0, COLOR_WHITE); |
692 | int buttons_y = SCREEN_HEIGHT / 2; | 32 | init_sprites(0); |
693 | 33 | init_button_sprites(); | |
694 | MultiSprite buttons[] = { | ||
695 | // DOWN. | ||
696 | { | ||
697 | .frame = 0, | ||
698 | .n_obj = 3, | ||
699 | .n_frames = 8, | ||
700 | .state = BTN_STATE_RELEASED, | ||
701 | .animations = &btn_animation, | ||
702 | .sprites = &(ObjState[]){ | ||
703 | { | ||
704 | .id = 0, | ||
705 | .x = buttons_x - 64 - 16, | ||
706 | .y = buttons_y + 29, | ||
707 | .data = &gba_btn_updown_data, | ||
708 | .n_tiles = 4, | ||
709 | .frames = 1, | ||
710 | .obj_attr_0 = 0, | ||
711 | .obj_attr_1 = OBJ_V_FLIP | OBJ_SIZE_MID, | ||
712 | .obj_attr_2 = 0 | ||
713 | }, | ||
714 | { | ||
715 | .id = 0, | ||
716 | .x = buttons_x - 64 - 16, | ||
717 | .y = buttons_y + 29 + 11, | ||
718 | .data = &gba_btn_down_shadow_data, | ||
719 | .n_tiles = 2, | ||
720 | .frames = 1, | ||
721 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
722 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
723 | .obj_attr_2 = 0 | ||
724 | }, | ||
725 | { | ||
726 | .id = 0, | ||
727 | .x = buttons_x - 64 - 16 - 8, | ||
728 | .y = buttons_y + 29 + 17, | ||
729 | .data = &gba_btn_fx_downup, | ||
730 | .n_tiles = 4, | ||
731 | .frames = 4, | ||
732 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
733 | .obj_attr_1 = OBJ_SIZE_MID, | ||
734 | .obj_attr_2 = 0 | ||
735 | }, | ||
736 | }, | ||
737 | }, | ||
738 | // UP. | ||
739 | { | ||
740 | .frame = 0, | ||
741 | .n_obj = 3, | ||
742 | .n_frames = 8, | ||
743 | .state = BTN_STATE_RELEASED, | ||
744 | .animations = &btn_animation, | ||
745 | .sprites = &(ObjState[]){ | ||
746 | { | ||
747 | .id = 0, | ||
748 | .x = buttons_x - 64 - 16, | ||
749 | .y = buttons_y + 32 - 18, | ||
750 | .data = &gba_btn_updown_data, | ||
751 | .n_tiles = 4, | ||
752 | .frames = 1, | ||
753 | .obj_attr_0 = 0, | ||
754 | .obj_attr_1 = OBJ_SIZE_MID, | ||
755 | .obj_attr_2 = 0 | ||
756 | }, | ||
757 | { | ||
758 | .id = 0, | ||
759 | .x = buttons_x - 64 - 16, | ||
760 | .y = buttons_y + 32 - 18 + 7, | ||
761 | .data = &gba_btn_up_shadow_data, | ||
762 | .n_tiles = 2, | ||
763 | .frames = 1, | ||
764 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
765 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
766 | .obj_attr_2 = 0 | ||
767 | }, | ||
768 | { | ||
769 | .id = 0, | ||
770 | .x = buttons_x - 64 - 16 - 8, | ||
771 | .y = buttons_y + 32 - 18 - 7, | ||
772 | .data = &gba_btn_fx_downup, | ||
773 | .n_tiles = 4, | ||
774 | .frames = 4, | ||
775 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
776 | .obj_attr_1 = OBJ_V_FLIP | OBJ_SIZE_MID, | ||
777 | .obj_attr_2 = 0 | ||
778 | }, | ||
779 | }, | ||
780 | }, | ||
781 | // LEFT | ||
782 | { | ||
783 | .frame = 0, | ||
784 | .n_obj = 3, | ||
785 | .n_frames = 8, | ||
786 | .state = BTN_STATE_RELEASED, | ||
787 | .animations = &btn_animation, | ||
788 | .sprites = &(ObjState[]){ | ||
789 | { | ||
790 | .id = 0, | ||
791 | .x = buttons_x - 64 - 16 - 10, | ||
792 | .y = buttons_y + 32 - 10, | ||
793 | .data = &gba_btn_leftright_data, | ||
794 | .n_tiles = 4, | ||
795 | .frames = 1, | ||
796 | .obj_attr_0 = 0, | ||
797 | .obj_attr_1 = OBJ_SIZE_MID, | ||
798 | .obj_attr_2 = 0 | ||
799 | }, | ||
800 | { | ||
801 | .id = 0, | ||
802 | .x = buttons_x - 64 - 16 - 10, | ||
803 | .y = buttons_y + 32 - 10 + 6, | ||
804 | .data = &gba_btn_leftright_shadow_data, | ||
805 | .n_tiles = 2, | ||
806 | .frames = 1, | ||
807 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
808 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
809 | .obj_attr_2 = 0 | ||
810 | }, | ||
811 | { | ||
812 | .id = 0, | ||
813 | .x = buttons_x - 64 - 16 - 10 - 6, | ||
814 | .y = buttons_y + 32 - 10 - 8, | ||
815 | .data = &gba_btn_fx_leftright, | ||
816 | .n_tiles = 4, | ||
817 | .frames = 4, | ||
818 | .obj_attr_0 = OBJ_SHAPE_TALL, | ||
819 | .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_MID, | ||
820 | .obj_attr_2 = 0 | ||
821 | }, | ||
822 | }, | ||
823 | }, | ||
824 | // RIGHT. | ||
825 | { | ||
826 | .frame = 0, | ||
827 | .n_obj = 3, | ||
828 | .n_frames = 8, | ||
829 | .state = BTN_STATE_RELEASED, | ||
830 | .animations = &btn_animation, | ||
831 | .sprites = &(ObjState[]){ | ||
832 | { | ||
833 | .id = 0, | ||
834 | .x = buttons_x - 64 - 16 + 11, | ||
835 | .y = buttons_y + 32 - 10, | ||
836 | .data = &gba_btn_leftright_data, | ||
837 | .n_tiles = 4, | ||
838 | .frames = 1, | ||
839 | .obj_attr_0 = 0, | ||
840 | .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_MID, | ||
841 | .obj_attr_2 = 0 | ||
842 | }, | ||
843 | { | ||
844 | .id = 0, | ||
845 | .x = buttons_x - 64 - 16 + 11, | ||
846 | .y = buttons_y + 32 - 10 + 6, | ||
847 | .data = &gba_btn_leftright_shadow_data, | ||
848 | .n_tiles = 2, | ||
849 | .frames = 1, | ||
850 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
851 | .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_SMALL, | ||
852 | .obj_attr_2 = 0 | ||
853 | }, | ||
854 | { | ||
855 | .id = 0, | ||
856 | .x = buttons_x - 64 - 16 + 11 + 14, | ||
857 | .y = buttons_y + 32 - 10 - 8, | ||
858 | .data = &gba_btn_fx_leftright, | ||
859 | .n_tiles = 4, | ||
860 | .frames = 4, | ||
861 | .obj_attr_0 = OBJ_SHAPE_TALL, | ||
862 | .obj_attr_1 = OBJ_SIZE_MID, | ||
863 | .obj_attr_2 = 0 | ||
864 | }, | ||
865 | }, | ||
866 | }, | ||
867 | // L. | ||
868 | { | ||
869 | .frame = 0, | ||
870 | .n_obj = 3, | ||
871 | .n_frames = 8, | ||
872 | .state = BTN_STATE_RELEASED, | ||
873 | .animations = &btn_animation, | ||
874 | .sprites = &(ObjState[]){ | ||
875 | { | ||
876 | .id = 0, | ||
877 | .x = buttons_x - 64 - 28, | ||
878 | .y = buttons_y - 32 - 20, | ||
879 | .data = &gba_btn_l_data, | ||
880 | .n_tiles = 2, | ||
881 | .frames = 1, | ||
882 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
883 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
884 | .obj_attr_2 = 0 | ||
885 | }, | ||
886 | { | ||
887 | .id = 0, | ||
888 | .x = buttons_x - 64 - 28, | ||
889 | .y = buttons_y - 32 - 20 + 2, | ||
890 | .data = &gba_btn_lr_shadow_data, | ||
891 | .n_tiles = 2, | ||
892 | .frames = 1, | ||
893 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
894 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
895 | .obj_attr_2 = 0 | ||
896 | }, | ||
897 | { | ||
898 | .id = 0, | ||
899 | .x = buttons_x - 64 - 28 - 12, | ||
900 | .y = buttons_y - 32 - 20 - 12, | ||
901 | .data = &gba_btn_fx_lr, | ||
902 | .n_tiles = 4, | ||
903 | .frames = 4, | ||
904 | .obj_attr_0 = OBJ_SHAPE_SQUARE, | ||
905 | .obj_attr_1 = OBJ_SIZE_MID, | ||
906 | .obj_attr_2 = 0 | ||
907 | }, | ||
908 | }, | ||
909 | }, | ||
910 | // R. | ||
911 | { | ||
912 | .frame = 0, | ||
913 | .n_obj = 3, | ||
914 | .n_frames = 8, | ||
915 | .state = BTN_STATE_RELEASED, | ||
916 | .animations = &btn_animation, | ||
917 | .sprites = &(ObjState[]){ | ||
918 | { | ||
919 | .id = 0, | ||
920 | .x = buttons_x + 32 + 20 + 24, | ||
921 | .y = buttons_y - 32 - 20, | ||
922 | .data = &gba_btn_r_data, | ||
923 | .n_tiles = 2, | ||
924 | .frames = 1, | ||
925 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
926 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
927 | .obj_attr_2 = 0 | ||
928 | }, | ||
929 | { | ||
930 | .id = 0, | ||
931 | .x = buttons_x + 32 + 20 + 24, | ||
932 | .y = buttons_y - 32 - 20 + 2, | ||
933 | .data = &gba_btn_lr_shadow_data, | ||
934 | .n_tiles = 2, | ||
935 | .frames = 1, | ||
936 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
937 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
938 | .obj_attr_2 = 0 | ||
939 | }, | ||
940 | { | ||
941 | .id = 0, | ||
942 | .x = buttons_x + 32 + 20 + 12 + 24, | ||
943 | .y = buttons_y - 32 - 20 - 12, | ||
944 | .data = &gba_btn_fx_lr, | ||
945 | .n_tiles = 4, | ||
946 | .frames = 4, | ||
947 | .obj_attr_0 = OBJ_SHAPE_SQUARE, | ||
948 | .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_MID, | ||
949 | .obj_attr_2 = 0 | ||
950 | }, | ||
951 | }, | ||
952 | }, | ||
953 | // A. | ||
954 | { | ||
955 | .frame = 0, | ||
956 | .n_obj = 3, | ||
957 | .n_frames = 8, | ||
958 | .state = BTN_STATE_RELEASED, | ||
959 | .animations = &btn_animation_ab, | ||
960 | .sprites = &(ObjState[]){ | ||
961 | { | ||
962 | .id = 0, | ||
963 | .x = buttons_x + 32 + 20 + 20, | ||
964 | .y = buttons_y + 32 - 16, | ||
965 | .data = &gba_btn_a_data, | ||
966 | .n_tiles = 4, | ||
967 | .frames = 1, | ||
968 | .obj_attr_0 = OBJ_SHAPE_SQUARE, | ||
969 | .obj_attr_1 = OBJ_SIZE_MID, | ||
970 | .obj_attr_2 = 0 | ||
971 | }, | ||
972 | { | ||
973 | .id = 0, | ||
974 | .x = buttons_x + 32 + 20 + 20, | ||
975 | .y = buttons_y + 32 - 16 + 8, | ||
976 | .data = &gba_btn_ab_shadow_data, | ||
977 | .n_tiles = 2, | ||
978 | .frames = 1, | ||
979 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
980 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
981 | .obj_attr_2 = 0 | ||
982 | }, | ||
983 | { | ||
984 | .id = 0, | ||
985 | .x = buttons_x + 32 + 19 - 7 + 20, | ||
986 | .y = buttons_y + 32 - 23, | ||
987 | .data = &gba_btn_fx_ab, | ||
988 | .n_tiles = 16, | ||
989 | .frames = 3, | ||
990 | .obj_attr_0 = OBJ_SHAPE_SQUARE, | ||
991 | .obj_attr_1 = OBJ_SIZE_BIG, | ||
992 | .obj_attr_2 = 0 | ||
993 | }, | ||
994 | }, | ||
995 | }, | ||
996 | // B. | ||
997 | { | ||
998 | .frame = 0, | ||
999 | .n_obj = 3, | ||
1000 | .n_frames = 8, | ||
1001 | .state = BTN_STATE_RELEASED, | ||
1002 | .animations = &btn_animation_ab, | ||
1003 | .sprites = &(ObjState[]){ | ||
1004 | { | ||
1005 | .id = 0, | ||
1006 | .x = buttons_x + 32 + 20, | ||
1007 | .y = buttons_y + 32, | ||
1008 | .data = &gba_btn_b_data, | ||
1009 | .n_tiles = 4, | ||
1010 | .frames = 1, | ||
1011 | .obj_attr_0 = OBJ_SHAPE_SQUARE, | ||
1012 | .obj_attr_1 = OBJ_SIZE_MID, | ||
1013 | .obj_attr_2 = 0 | ||
1014 | }, | ||
1015 | { | ||
1016 | .id = 0, | ||
1017 | .x = buttons_x + 32 + 20, | ||
1018 | .y = buttons_y + 32 + 8, | ||
1019 | .data = &gba_btn_ab_shadow_data, | ||
1020 | .n_tiles = 2, | ||
1021 | .frames = 1, | ||
1022 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
1023 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
1024 | .obj_attr_2 = 0 | ||
1025 | }, | ||
1026 | { | ||
1027 | .id = 0, | ||
1028 | .x = buttons_x + 32 - 8 + 20, | ||
1029 | .y = buttons_y + 33 + 8 - 16, | ||
1030 | .data = &gba_btn_fx_ab, | ||
1031 | .n_tiles = 16, | ||
1032 | .frames = 3, | ||
1033 | .obj_attr_0 = OBJ_SHAPE_SQUARE, | ||
1034 | .obj_attr_1 = OBJ_SIZE_BIG, | ||
1035 | .obj_attr_2 = 0 | ||
1036 | }, | ||
1037 | }, | ||
1038 | }, | ||
1039 | // START. | ||
1040 | { | ||
1041 | .frame = 0, | ||
1042 | .n_obj = 3, | ||
1043 | .n_frames = 8, | ||
1044 | .state = BTN_STATE_RELEASED, | ||
1045 | .animations = &btn_animation_startselect, | ||
1046 | .sprites = &(ObjState[]){ | ||
1047 | { | ||
1048 | .id = 0, | ||
1049 | .x = buttons_x - 10 - 14, | ||
1050 | .y = buttons_y + 40 + 10, | ||
1051 | .data = &gba_btn_startselect_data, | ||
1052 | .n_tiles = 2, | ||
1053 | .frames = 2, | ||
1054 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
1055 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
1056 | .obj_attr_2 = 0 | ||
1057 | }, | ||
1058 | { | ||
1059 | .id = 0, | ||
1060 | .x = buttons_x - 18 - 14, | ||
1061 | .y = buttons_y + 46 + 10, | ||
1062 | .data = &gba_btn_start_text, | ||
1063 | .n_tiles = 4, | ||
1064 | .frames = 1, | ||
1065 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
1066 | .obj_attr_1 = OBJ_SIZE_MID, | ||
1067 | .obj_attr_2 = 0 | ||
1068 | }, | ||
1069 | { | ||
1070 | .id = 0, | ||
1071 | .x = buttons_x - 19 - 14, | ||
1072 | .y = buttons_y + 37 + 10, | ||
1073 | .data = &gba_btn_fx_startselect, | ||
1074 | .n_tiles = 4, | ||
1075 | .frames = 4, | ||
1076 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
1077 | .obj_attr_1 = OBJ_SIZE_MID, | ||
1078 | .obj_attr_2 = 0 | ||
1079 | }, | ||
1080 | }, | ||
1081 | }, | ||
1082 | // SELECT. | ||
1083 | { | ||
1084 | .frame = 0, | ||
1085 | .n_obj = 3, | ||
1086 | .n_frames = 8, | ||
1087 | .state = BTN_STATE_RELEASED, | ||
1088 | .animations = &btn_animation_startselect, | ||
1089 | .sprites = &(ObjState[]){ | ||
1090 | { | ||
1091 | .id = 0, | ||
1092 | .x = buttons_x - 10 + 26 - 14, | ||
1093 | .y = buttons_y + 40 + 10, | ||
1094 | .data = &gba_btn_startselect_data, | ||
1095 | .n_tiles = 2, | ||
1096 | .frames = 2, | ||
1097 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
1098 | .obj_attr_1 = OBJ_SIZE_SMALL, | ||
1099 | .obj_attr_2 = 0 | ||
1100 | }, | ||
1101 | { | ||
1102 | .id = 0, | ||
1103 | .x = buttons_x - 18 + 26 - 14, | ||
1104 | .y = buttons_y + 46 + 10, | ||
1105 | .data = &gba_btn_select_text, | ||
1106 | .n_tiles = 4, | ||
1107 | .frames = 1, | ||
1108 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
1109 | .obj_attr_1 = OBJ_SIZE_MID, | ||
1110 | .obj_attr_2 = 0 | ||
1111 | }, | ||
1112 | { | ||
1113 | .id = 0, | ||
1114 | .x = buttons_x - 19 + 26 - 14, | ||
1115 | .y = buttons_y + 37 + 10, | ||
1116 | .data = &gba_btn_fx_startselect, | ||
1117 | .n_tiles = 4, | ||
1118 | .frames = 4, | ||
1119 | .obj_attr_0 = OBJ_SHAPE_WIDE, | ||
1120 | .obj_attr_1 = OBJ_SIZE_MID, | ||
1121 | .obj_attr_2 = 0 | ||
1122 | }, | ||
1123 | }, | ||
1124 | }, | ||
1125 | }; | ||
1126 | for (size_t i = 0; i < sizeof(buttons) / sizeof(MultiSprite); ++i) { | ||
1127 | init_button_sprite(&buttons[i]); | ||
1128 | } | ||
1129 | 34 | ||
1130 | int frame_counter = 0; | 35 | int frame_counter = 0; |
1131 | int x = 0; | 36 | int x = 0; |
@@ -1134,77 +39,6 @@ int main(void) { | |||
1134 | wait_vsync(); | 39 | wait_vsync(); |
1135 | poll_keys(); | 40 | poll_keys(); |
1136 | 41 | ||
1137 | if (key_pressed(KEY_DOWN)) { | ||
1138 | buttons[0].state = BTN_STATE_PRESSED; | ||
1139 | } else if (key_hold(KEY_DOWN)) { | ||
1140 | buttons[0].state = BTN_STATE_HOLD; | ||
1141 | } else { | ||
1142 | buttons[0].state = BTN_STATE_RELEASED; | ||
1143 | } | ||
1144 | if (key_pressed(KEY_UP)) { | ||
1145 | buttons[1].state = BTN_STATE_PRESSED; | ||
1146 | } else if (key_hold(KEY_UP)) { | ||
1147 | buttons[1].state = BTN_STATE_HOLD; | ||
1148 | } else { | ||
1149 | buttons[1].state = BTN_STATE_RELEASED; | ||
1150 | } | ||
1151 | if (key_pressed(KEY_LEFT)) { | ||
1152 | buttons[2].state = BTN_STATE_PRESSED; | ||
1153 | } else if (key_hold(KEY_LEFT)) { | ||
1154 | buttons[2].state = BTN_STATE_HOLD; | ||
1155 | } else { | ||
1156 | buttons[2].state = BTN_STATE_RELEASED; | ||
1157 | } | ||
1158 | if (key_pressed(KEY_RIGHT)) { | ||
1159 | buttons[3].state = BTN_STATE_PRESSED; | ||
1160 | } else if (key_hold(KEY_RIGHT)) { | ||
1161 | buttons[3].state = BTN_STATE_HOLD; | ||
1162 | } else { | ||
1163 | buttons[3].state = BTN_STATE_RELEASED; | ||
1164 | } | ||
1165 | if (key_pressed(KEY_L)) { | ||
1166 | buttons[4].state = BTN_STATE_PRESSED; | ||
1167 | } else if (key_hold(KEY_L)) { | ||
1168 | buttons[4].state = BTN_STATE_HOLD; | ||
1169 | } else { | ||
1170 | buttons[4].state = BTN_STATE_RELEASED; | ||
1171 | } | ||
1172 | if (key_pressed(KEY_R)) { | ||
1173 | buttons[5].state = BTN_STATE_PRESSED; | ||
1174 | } else if (key_hold(KEY_R)) { | ||
1175 | buttons[5].state = BTN_STATE_HOLD; | ||
1176 | } else { | ||
1177 | buttons[5].state = BTN_STATE_RELEASED; | ||
1178 | } | ||
1179 | if (key_pressed(KEY_A)) { | ||
1180 | buttons[6].state = BTN_STATE_PRESSED; | ||
1181 | } else if (key_hold(KEY_A)) { | ||
1182 | buttons[6].state = BTN_STATE_HOLD; | ||
1183 | } else { | ||
1184 | buttons[6].state = BTN_STATE_RELEASED; | ||
1185 | } | ||
1186 | if (key_pressed(KEY_B)) { | ||
1187 | buttons[7].state = BTN_STATE_PRESSED; | ||
1188 | } else if (key_hold(KEY_B)) { | ||
1189 | buttons[7].state = BTN_STATE_HOLD; | ||
1190 | } else { | ||
1191 | buttons[7].state = BTN_STATE_RELEASED; | ||
1192 | } | ||
1193 | if (key_pressed(KEY_START)) { | ||
1194 | buttons[8].state = BTN_STATE_PRESSED; | ||
1195 | } else if (key_hold(KEY_START)) { | ||
1196 | buttons[8].state = BTN_STATE_HOLD; | ||
1197 | } else { | ||
1198 | buttons[8].state = BTN_STATE_RELEASED; | ||
1199 | } | ||
1200 | if (key_pressed(KEY_SELECT)) { | ||
1201 | buttons[9].state = BTN_STATE_PRESSED; | ||
1202 | } else if (key_hold(KEY_SELECT)) { | ||
1203 | buttons[9].state = BTN_STATE_HOLD; | ||
1204 | } else { | ||
1205 | buttons[9].state = BTN_STATE_RELEASED; | ||
1206 | } | ||
1207 | |||
1208 | if (key_hold(KEY_DOWN)) { | 42 | if (key_hold(KEY_DOWN)) { |
1209 | y += 3; | 43 | y += 3; |
1210 | } | 44 | } |
@@ -1222,9 +56,7 @@ int main(void) { | |||
1222 | BG_H_SCROLL_0 = x; | 56 | BG_H_SCROLL_0 = x; |
1223 | BG_V_SCROLL_0 = y; | 57 | BG_V_SCROLL_0 = y; |
1224 | 58 | ||
1225 | for (size_t i = 0; i < sizeof(buttons) / sizeof(MultiSprite); ++i) { | 59 | update_button_sprites(); |
1226 | button_tick(&buttons[i]); | ||
1227 | } | ||
1228 | }; | 60 | }; |
1229 | 61 | ||
1230 | return 0; | 62 | return 0; |
diff --git a/src/sprites.h b/src/sprites.h new file mode 100644 index 0000000..e7439f1 --- /dev/null +++ b/src/sprites.h | |||
@@ -0,0 +1,98 @@ | |||
1 | #ifndef GBAEXP_SPRITES_H | ||
2 | #define GBAEXP_SPRITES_H | ||
3 | |||
4 | #include "common.h" | ||
5 | |||
6 | u32 | ||
7 | unpack_1bb(u8 hex) { | ||
8 | const u32 conversion_u32[16] = { | ||
9 | 0x00000000, 0x00000001, 0x00000010, 0x00000011, | ||
10 | 0x00000100, 0x00000101, 0x00000110, 0x00000111, | ||
11 | 0x00001000, 0x00001001, 0x00001010, 0x00001011, | ||
12 | 0x00001100, 0x00001101, 0x00001110, 0x00001111, | ||
13 | }; | ||
14 | u8 low = hex & 0xF; | ||
15 | u8 high = (hex >> 4) & 0xF; | ||
16 | return (conversion_u32[high] << 16) | conversion_u32[low]; | ||
17 | } | ||
18 | |||
19 | typedef struct Sprite { | ||
20 | // A unique sprite identifier. | ||
21 | size_t id; | ||
22 | // The number of tiles for a single sprite frame. | ||
23 | size_t n_tiles; | ||
24 | // The starting tile of this sprite. | ||
25 | size_t tile_start; | ||
26 | // The associated palette bank for this sprite. | ||
27 | size_t pal_bank; | ||
28 | } Sprite; | ||
29 | |||
30 | |||
31 | #define NUM_SPRITES 128 | ||
32 | Sprite sprites[NUM_SPRITES]; | ||
33 | |||
34 | // Keeping track of unique sprites and current sprite memory pointer using | ||
35 | // global singletons. | ||
36 | size_t sprite_counter = 0; | ||
37 | size_t sprite_tile_counter = 0; | ||
38 | u32 *sprite_memory = NULL; | ||
39 | |||
40 | // Loads the sprite data into video memory and initialize the Sprite structure. | ||
41 | size_t | ||
42 | load_sprite_data(u32 *sprite_data, size_t n_tiles, size_t n_frames) { | ||
43 | memcpy(sprite_memory, sprite_data, 8 * n_tiles * n_frames * sizeof(u32)); | ||
44 | sprite_memory += 8 * n_tiles * n_frames; | ||
45 | Sprite sprite = { | ||
46 | .id = sprite_counter, | ||
47 | .n_tiles = n_tiles, | ||
48 | .tile_start = sprite_tile_counter, | ||
49 | }; | ||
50 | sprite_tile_counter += n_tiles * n_frames; | ||
51 | sprites[sprite_counter] = sprite; | ||
52 | return sprite_counter++; | ||
53 | } | ||
54 | |||
55 | size_t | ||
56 | load_packed_sprite_data(u32 *sprite_data, size_t n_tiles, size_t n_frames) { | ||
57 | size_t counter = 0; | ||
58 | for (size_t i = 0; i < 8 * n_tiles * n_frames / 4; ++i) { | ||
59 | u32 hex = sprite_data[i]; | ||
60 | sprite_memory[counter++] = unpack_1bb((hex >> 24) & 0xFF); | ||
61 | sprite_memory[counter++] = unpack_1bb((hex >> 16) & 0xFF); | ||
62 | sprite_memory[counter++] = unpack_1bb((hex >> 8) & 0xFF); | ||
63 | sprite_memory[counter++] = unpack_1bb((hex) & 0xFF); | ||
64 | } | ||
65 | sprite_memory += 8 * n_tiles * n_frames; | ||
66 | Sprite sprite = { | ||
67 | .id = sprite_counter, | ||
68 | .n_tiles = n_tiles, | ||
69 | .tile_start = sprite_tile_counter, | ||
70 | }; | ||
71 | sprite_tile_counter += n_tiles * n_frames; | ||
72 | sprites[sprite_counter] = sprite; | ||
73 | return sprite_counter++; | ||
74 | } | ||
75 | |||
76 | void | ||
77 | init_sprites(size_t starting_tile) { | ||
78 | // Initialize all attributes by disabling rendering. If we don't do this, | ||
79 | // glitches may appear. | ||
80 | for (size_t i = 0; i < 128; ++i) { | ||
81 | OBJ_ATTR_0(i) = (1 << 9); | ||
82 | } | ||
83 | |||
84 | sprite_counter = 0; | ||
85 | // Prepare global sprite_memory address. | ||
86 | sprite_memory = &TILE_MEM[4][starting_tile]; | ||
87 | } | ||
88 | |||
89 | void | ||
90 | init_sprite_pal(size_t starting_index, Color col) { | ||
91 | // Add colors to the sprite color palette. Tiles with color number 0 are | ||
92 | // treated as transparent. | ||
93 | for (size_t i = 0; i < 16; ++i) { | ||
94 | PAL_BUFFER_SPRITES[i + starting_index] = col; | ||
95 | } | ||
96 | |||
97 | } | ||
98 | #endif // GBAEXP_SPRITES_H | ||