4 This is a csfml (C binding for SFML) implementation of the game front end.
5 This is another alternative to the SDL for the PC. This front end is maybe a
6 little simpler than the SDL, so it's better as a learning resource.
8 by Miloslav Ciz (drummyfish), 2020
10 Released under CC0 1.0 (https://creativecommons.org/publicdomain/zero/1.0/)
11 plus a waiver of all other intellectual property. The goal of this work is
12 be and remain completely in the public domain forever, available for any use
16 #include <SFML/Audio.h>
17 #include <SFML/Graphics.h>
18 #include <SFML/System.h>
22 #include <SFML/Audio/Types.h>
24 #define SFG_SCREEN_RESOLUTION_X 640
25 #define SFG_SCREEN_RESOLUTION_Y 480
27 #define SFG_DITHERED_SHADOW 1
28 #define SFG_DIMINISH_SPRITES 1
29 #define SFG_RESOLUTION_SCALEDOWN 1
30 #define SFG_BACKGROUND_BLUR 1
32 #define SFG_LOG(s) printf("game: %s\n",s);
34 #define MUSIC_VOLUME 16
36 #define WINDOW_SIZE (SFG_SCREEN_RESOLUTION_X * SFG_SCREEN_RESOLUTION_Y)
41 uint32_t windowPixels
[WINDOW_SIZE
];
43 uint32_t paletteRGB32
[256]; // SFML can't do 565, so precompute RGB here
46 sfRenderWindow
* window
;
49 int8_t mouseWheelState
= 0;
51 int8_t SFG_keyPressed(uint8_t key
)
53 #define k(x) sfKeyboard_isKeyPressed(sfKey ## x)
57 case SFG_KEY_UP
: return k(W
) || k(Up
) || k(Num8
); break;
58 case SFG_KEY_RIGHT
: return k(E
) || k(Right
) || k(Num6
);
61 return k(S
) || k(Down
) || k(Num5
) || k (Num2
); break;
62 case SFG_KEY_LEFT
: return k(Q
) || k(Left
) || k(Num4
); break;
64 return k(J
) || k(Return
) || k(LControl
) || k(RControl
) ||
65 sfMouse_isButtonPressed(sfMouseLeft
); break;
66 case SFG_KEY_B
: return k(K
) || k(LShift
); break;
67 case SFG_KEY_C
: return k(L
); break;
68 case SFG_KEY_JUMP
: return k(Space
); break;
69 case SFG_KEY_STRAFE_LEFT
: return k(A
) || k(Num7
); break;
70 case SFG_KEY_STRAFE_RIGHT
: return k(D
) || k(Num9
); break;
71 case SFG_KEY_MAP
: return k(Tab
); break;
72 case SFG_KEY_CYCLE_WEAPON
: return k(F
);
73 case SFG_KEY_TOGGLE_FREELOOK
:
74 return sfMouse_isButtonPressed(sfMouseRight
);
77 case SFG_KEY_NEXT_WEAPON
:
81 #define checkMouse(cmp)\
82 if (mouseWheelState cmp 0) { mouseWheelState = 0; return 1; }
89 case SFG_KEY_PREVIOUS_WEAPON
:
90 if (k(O
) || k(Y
) || k(Z
))
100 case SFG_KEY_MENU
: return sfKeyboard_isKeyPressed(sfKeyEscape
); break;
101 default: return 0; break;
109 void SFG_getMouseOffset(int16_t *x
, int16_t *y
)
111 sfVector2u s
= sfWindow_getSize((const sfWindow
*) window
);
112 sfVector2i p
= sfMouse_getPosition((const sfWindow
*) window
);
123 sfMouse_setPosition(p
,(const sfWindow
*) window
);
126 uint32_t SFG_getTimeMs()
128 return sfClock_getElapsedTime(clock
).microseconds
/ 1000 ;
131 void SFG_sleepMs(uint16_t timeMs
)
134 t
.microseconds
= timeMs
* 1000;
138 void SFG_setPixel(uint16_t x
, uint16_t y
, uint8_t colorIndex
)
140 windowPixels
[y
* SFG_SCREEN_RESOLUTION_X
+ x
] = paletteRGB32
[colorIndex
];
143 void SFG_setMusic(uint8_t value
)
147 case SFG_MUSIC_TURN_ON
: musicOn
= 1; break;
148 case SFG_MUSIC_TURN_OFF
: musicOn
= 0; break;
149 case SFG_MUSIC_NEXT
: SFG_nextMusicTrack(); break;
154 void SFG_processEvent(uint8_t event
, uint8_t data
)
158 void SFG_save(uint8_t data
[SFG_SAVE_SIZE
])
160 FILE *f
= fopen(SFG_SAVE_FILE_PATH
,"wb");
165 fwrite(data
,1,SFG_SAVE_SIZE
,f
);
170 uint8_t SFG_load(uint8_t data
[SFG_SAVE_SIZE
])
172 FILE *f
= fopen(SFG_SAVE_FILE_PATH
,"rb");
176 fread(data
,1,SFG_SAVE_SIZE
,f
);
183 /* Because of the csfml sound API we won't use circular audio buffer, but a
184 linear buffer that is at each audio update shifted to the left. */
186 sfSoundStream
*sound
;
188 #define AUDIO_BUFFER_SIZE (SFG_SFX_SAMPLE_COUNT * 2) // total size of the buffer
189 #define AUDIO_BUFFER_OFFSET 400 /* size of the beginning portion of the buffer
190 that's being played, while the other part
191 is being filled with audio */
193 #if AUDIO_BUFFER_OFFSET * 2 > AUDIO_BUFFER_SIZE
194 #error "AUDIO_BUFFER_OFFSET must be at most half of AUDIO_BUFFER_SIZE"
197 int16_t audioBuffer
[AUDIO_BUFFER_SIZE
];
198 uint32_t audioUpdateFrame
= 0; // game frame at which audio buffer fill happened
200 static inline int16_t mixSamples(int16_t sample1
, int16_t sample2
)
202 return sample1
+ sample2
;
205 void SFG_playSound(uint8_t soundIndex
, uint8_t volume
)
207 uint16_t volumeScale
= 1 << (volume
/ 37);
209 uint32_t pos
= AUDIO_BUFFER_OFFSET
+
210 ((SFG_game
.frame
- audioUpdateFrame
) * SFG_MS_PER_FRAME
* 8);
212 for (int i
= 0; i
< SFG_SFX_SAMPLE_COUNT
; ++i
)
214 if (pos
>= AUDIO_BUFFER_SIZE
)
217 audioBuffer
[pos
] = mixSamples(audioBuffer
[pos
],
218 (128 - SFG_GET_SFX_SAMPLE(soundIndex
,i
)) * volumeScale
);
224 sfBool
soundFill(sfSoundStreamChunk
*data
, void *userdata
)
226 for (uint32_t i
= 0; i
< AUDIO_BUFFER_SIZE
- AUDIO_BUFFER_OFFSET
; ++i
)
227 audioBuffer
[i
] = audioBuffer
[i
+ AUDIO_BUFFER_OFFSET
];
229 for (uint32_t i
= AUDIO_BUFFER_SIZE
- AUDIO_BUFFER_OFFSET
;
230 i
< AUDIO_BUFFER_SIZE
; ++i
)
234 for (uint32_t i
= 0; i
< AUDIO_BUFFER_OFFSET
; ++i
) // mix in the music
236 audioBuffer
[i
] = mixSamples((SFG_getNextMusicSample() -
237 SFG_musicTrackAverages
[SFG_MusicState
.track
]) * MUSIC_VOLUME
,
241 data
->samples
= audioBuffer
;
242 data
->sampleCount
= AUDIO_BUFFER_OFFSET
;
244 audioUpdateFrame
= SFG_game
.frame
;
249 void soundSeek(sfTime t
, void *userData
)
253 uint32_t screenshotNumber
= 0;
256 Saves a screenshot using the simple uncompressed PPM file format.
262 sprintf(fileName
,"screenshot_%05d.ppm",screenshotNumber
);
264 FILE *f
= fopen(fileName
,"w");
268 puts("error: could not take screenshot");
272 fprintf(f
,"P6 %d %d 255\n",SFG_SCREEN_RESOLUTION_X
,SFG_SCREEN_RESOLUTION_Y
);
274 for (uint32_t i
= 0; i
< WINDOW_SIZE
; ++i
)
275 fwrite(&windowPixels
[i
],1,3,f
);
277 puts("screenshot taken");
284 int main(int argc
, char *argv
[])
286 uint8_t fullScreen
= 1;
288 for (uint8_t i
= 1; i
< argc
; ++i
)
290 if (argv
[i
][0] == '-' && argv
[i
][1] == 'h' && argv
[i
][2] == 0)
292 puts("Anarch (CSFML), version " SFG_VERSION_STRING
"\n");
293 puts("Anarch is a unique suckless FPS game. Collect weapons and items and destroy");
294 puts("robot enemies in your way in order to get to the level finish. Some door are");
295 puts("locked and require access cards. Good luck!\n");
296 puts("created by Miloslav \"drummyfish\" Ciz, 2020, released under CC0 1.0 (public domain)\n");
298 puts("- arrows, numpad, [W] [S] [A] [D] [Q] [E]: movement");
299 puts("- mouse: rotation, [LMB] shoot, [RMB] toggle free look");
300 puts("- [SPACE]: jump");
301 puts("- [J] [RETURN] [CTRL] [LMB]: game A button (shoot, confirm)");
302 puts("- [K] [SHIFT]: game B button (cancel, strafe)");
303 puts("- [L]: game C button (+ down = menu, + up = jump, ...)");
304 puts("- [F]: cycle next/previous weapon");
305 puts("- [O] [P] [X] [Y] [Z] [mouse wheel] [mouse middle]: change weapons");
306 puts("- [TAB]: map");
307 puts("- [F12]: screenshot");
308 puts("- [ESCAPE]: menu");
311 else if (argv
[i
][0] == '-' && argv
[i
][1] == 'w' && argv
[i
][2] == 0)
313 else if (argv
[i
][0] == '-' && argv
[i
][1] == 'f' && argv
[i
][2] == 0)
316 puts("SDL: unknown argument");
321 sfVideoMode mode
= {SFG_SCREEN_RESOLUTION_X
, SFG_SCREEN_RESOLUTION_Y
, 32};
323 clock
= sfClock_create();
324 sfClock_restart(clock
);
326 puts("initializing");
328 for (int i
= 0; i
< AUDIO_BUFFER_SIZE
; ++i
)
331 sound
= sfSoundStream_create(soundFill
,soundSeek
,1,8000,0);
333 for (int i
= 0; i
< 256; ++i
) // precompute RGB palette
335 uint16_t col565
= paletteRGB565
[i
];
337 paletteRGB32
[i
] = 0xff000000 | ((col565
<< 19) & 0xf80000) |
338 ((col565
<< 5) & 0xfc00) | ((col565
>> 8) & 0xf8);
341 sfTexture
* windowTexture
=
342 sfTexture_create(SFG_SCREEN_RESOLUTION_X
,SFG_SCREEN_RESOLUTION_Y
);
344 sfTexture_setSmooth(windowTexture
,sfTrue
);
346 sfSprite
* windowSprite
= sfSprite_create();
348 window
= sfRenderWindow_create(mode
, "Anarch",
349 fullScreen
? sfFullscreen
: (sfResize
| sfClose
), NULL
);
351 sfSprite_setTexture(windowSprite
, windowTexture
, sfTrue
);
353 sfWindow_setMouseCursorVisible((sfWindow
*) window
,sfFalse
);
354 sfWindow_setVerticalSyncEnabled((sfWindow
*) window
,sfFalse
);
356 sfSoundStream_play(sound
);
360 while (sfRenderWindow_isOpen(window
))
362 while (sfRenderWindow_pollEvent(window
,&event
))
363 if (event
.type
== sfEvtClosed
)
364 sfRenderWindow_close(window
);
365 else if (event
.type
== sfEvtMouseWheelMoved
)
366 mouseWheelState
= event
.mouseWheel
.delta
;
367 else if (event
.type
== sfEvtKeyPressed
&& event
.key
.code
== sfKeyF12
)
370 if (!SFG_mainLoopBody())
373 sfTexture_updateFromPixels(windowTexture
,(const sfUint8
*) windowPixels
,
374 SFG_SCREEN_RESOLUTION_X
,SFG_SCREEN_RESOLUTION_Y
,0,0);
375 sfRenderWindow_clear(window
, sfBlack
);
376 sfRenderWindow_drawSprite(window
,windowSprite
,NULL
);
377 sfRenderWindow_display(window
);
382 sfSoundStream_stop(sound
);
383 sfSoundStream_destroy(sound
);
384 sfSprite_destroy(windowSprite
);
385 sfTexture_destroy(windowTexture
);
386 sfRenderWindow_destroy(window
);
387 sfClock_destroy(clock
);