00:00
00:00
CrankyRabbit
Would-be DJ/Producer. Plays too many video games. Drew a webcomic once. Old fart.

Age 53, Male

Coordinator

New Zealand

Joined on 5/7/04

Level:
19
Exp Points:
3,637 / 4,010
Exp Rank:
15,324
Vote Power:
6.05 votes
Rank:
Safety Patrol
Global Rank:
30,531
Blams:
94
Saves:
224
B/P Bonus:
6%
Whistle:
Normal
Medals:
2,416
Supporter:
2y 1m 7d

Adventures in MEG-4: Setting some Framework

Posted by CrankyRabbit - November 12th, 2024


One of the goals I want to achieve is writing some games and sharing them on Newgrounds. Most likely I will set up a separate account for that. One should keep vices separate.


The weapon of choice is the MEG-4 fantasy console. This thing is a bit more flexible than the PICO-8, sports about 512KB of memory, and a classic resolution of 320x200 pixels. Basically, about the same resolution as the Commodore 64. Except that you can code your programs in a flavour of ANSI C, and you don't have to faff about in screen resolution modes that severely limit you choice of colours and make all your pixels wider.


This means some heavy limitations. No object oriented programming, no structs (I know from experimenting.) Everything is ANSI flavoured numbers and chars, in arrays and out.


Naturally I’ve tried making games before. What happened was that I ended up with a too-grand vision, got bogged down in details, couldn’t understand the workflow, and gave up for the easy rewards of playing someone else’s games. Not this time.


The games I intend making are basic copies of arcade classics: Breakout, Bezerk, Space Invaders, Joust, that sort of mid-1980s stuff.


But first I need to code up a framework.


States of Play

One feature of the framework I need is to be able to identify what state the game is in. For instance, there is an initial default state where the game sets itself up and sorts itself out. There may be a splash screen state which lasts until you press the start button. Then there is the game loop state proper, and finally a game over state. Each state has unique logic, and it makes sense to keep it all separate.


The easiest way is to set up a global variable, and change its value whenever we need a state change.


enum { _FALSE = 0, _TRUE = 1 };
 enum { S_ERROR = -1, S_INIT = 0, S_TITLE = 1, S_GAME = 2, S_GAMEOVER = 3 };
int32_t _STATE = S_INIT; // obviously if(_STATE < 0) do error stuff

This makes the loop() function a basic switch..case statement.

switch(_STATE) {
    case S_INIT:
        // set up everything for showtime
        if (setup() != _TRUE) {
            _STATE = S_ERROR;
        } else {
            _STATE = S_TITLE;
        }
    break;
    case S_TITLE:
        // splashy splashy insert coin
    break;
    // and so on until
    default:
        // do error stuff
 }

But it may be more complex than that. Theoretically we shouldn’t need thousands of distinct running states, but that is academic. If I understand correctly, enumerated values are 32-bit signed integers, so it’s best we keep compatibility.

You’ll also notice another pair of global constants, _TRUE and _FALSE. These can be used for answering the question, “did this function do what it’s supposed to?” or, as we’ll code later, “are these two objects colliding?” Having constants makes the code easier to read.


However, we don’t really need to compare like that. After all, states are defined as numbers! We can streamline loop() into this:

switch(_STATE) {
    case S_INIT:
        // set up everything for showtime
        _STATE = setup();
    break;
    case S_TITLE:
        _STATE = title();
    break;
    // and so on until
    default:
        // do error stuff
 }

This requires less processing overhead, and is simpler than constant comparisons; the functions just have to return the state for the next cycle. If everything got set up properly, we switch straight to the title state; a lot easier. Especially if we have multiple game over states (e.g. good and bad), and then need to circle back to the title screen.


Of course, every function returns an int32_t, so we declare them as int32_t setup() {}. If there are any child functions for these, we’ll need a naming system that helps identify what’s what.


There are two conventions: One is to capitaliseFirstLetters and prepend the scope of the function. So, a function that sets up, say, a prefab array of baddies could be called setupBaddiesArray. The capitals make it easy to spot the individual words. Another is to use underscores, which would make the function call setup_baddies_array. It’s basically a matter of taste and legibility. (Word doesn’t like either of them.)


Anyway! We have a state management skeleton designed. But... what am I going to use it on?


First Game: Bat and Ball, redone

One of the tutorial demos in the game is a simple keepy-uppy game with a ball and a bat. If the ball hits an obstacle, it changes direction appropriately. If it drops off the bottom of the screen, game over.


Naturally I want to complicate this demo and turn it into the base for a Breakout clone. I want variable velocities, more generalised collision code, scoring etc. BUT! Before I can start that, I need to get it a) into the state framework, and b) sort out collision coding.


Variable Velocities

In its vanilla state, the ball moves in diagonal lines. While it has velocities dx and dy, these just change sign when it hits the bat or edges of the screen. Whether you crawl the bat, or just fling it from side to side with gay abandon, it doesn’t matter a damn. I want it to matter.


Generalised collision code

The collision detection for the two objects is hard coded into the loop. I want a method that can be generalised to an arbitrary number of objects. For instance, adding blocks, or bumpers, or pickups. This is going to be tricky. I’ll discuss implementation in another blog post.


But first, let’s see about getting bat and ball into a state-based frame of mind:

#!c
 
 int x, y, dx, dy, px;
 str_t msg = "GAME OVER!";
 
 void setup()
 {
  /* Things to do on startup */
  x = 156;
  y = 96;
  dx = dy = 1;
 }
 
 void loop()
 {
  /* Things to run for every frame, at 60 FPS */
  cls(0);
  spr(x, y, 0, 1, 1, 1, 0);
  px = inw(0x16);
  if(px > 296) px = 296;
  spr(px, 191, 1, 3, 1, 1, 0);
  x = x + dx;
  y = y + dy;
  if(x == 0 || x == 311) dx = -dx;
  if(y == 0 || (y == 183 && x >= px && x <= px + 24)) dy = -dy;
  if(y > 199) {
    dx = dy = 0;
    text(184, (320 - width(2, msg)) / 2, 90, 2, 112, 128, msg);
    if(getclk(BTN_L)) setup();
  }
 }

So currently it exists in one state. The ball launches as soon as the game starts. We need to fix that:

#!c
 
 enum {S_ERROR = -1, S_INIT = 0, S_TITLE = 1, S_GAME = 2, S_GAMEOVER = 3};
 int32_t _STATE = S_INIT;
 
 int x, y, dx, dy, px;
 str_t msg = "GAME OVER!";
 
 void setup() {
 }
 
 void loop() {
  switch (_STATE) {
    S_INIT:
      _STATE = initSetup();
    break;
    S_TITLE:
      // placeholder
    break;
    S_GAME:
      _STATE = gameLoop();
    break;
    S_GAMEOVER:
      // placeholder
    break;
    default:
      // an error occurred, which with a game this simple shouldn’t happen
    break;
  }
 }
 
 int32_t initSetup () {
  /* Things to do on startup */
  x = 156;
  y = 96;
  dx = dy = 1;
  return S_GAME;
 }
 
 int32_t gameLoop()
 {
  /* Things to run for every frame, at 60 FPS */
  cls(0);
  spr(x, y, 0, 1, 1, 1, 0);
  px = inw(0x16);
  if(px > 296) px = 296;
  spr(px, 191, 1, 3, 1, 1, 0);
  x = x + dx;
  y = y + dy;
  if(x == 0 || x == 311) dx = -dx;
  if(y == 0 || (y == 183 && x >= px && x <= px + 24)) dy = -dy;
  if(y > 199) {
    dx = dy = 0;
    text(184, (320 - width(2, msg)) / 2, 90, 2, 112, 128, msg);
    if(getclk(BTN_L)) { return S_INIT} else {return S_GAME};
  }
 }

So what has happened here is that we have made some function prototypes, and pushed both the setup and game logic into state-specific functions. This should run. So, what do we want to do now?


Well, first off, we want a start screen, preferably one with a “click to start” message and bat plus ball showing. So what’ll that look like?

int32_t titleLoop()
 {
  str_t startMsg = "Click to Start";
  /* Things to run for every frame, at 60 FPS */
  cls(0);
  spr(x, y, 0, 1, 1, 1, 0);
  spr(px, 191, 1, 3, 1, 1, 0);
  text(184, (320 - width(2, startMsg)) / 2, 90, 2, 112, 128, startMsg);
    if(getclk(BTN_L)) {return S_GAME} else {return S_TITLE};
  }
 }

Now notice that this is just a stripped down copy of gameLoop(), since the only thing we need to know is whether or not the player clicked to start. Once they do, the _STATE changes and the message will poof. It’s keepy-uppy time!


(Also, there are some semicolons missing, as well as a default return call! Oops.)


But wait! We now need a game-over state!

int32_t gameLoop()
 {
  /* Things to run for every frame, at 60 FPS */
  cls(0);
  spr(x, y, 0, 1, 1, 1, 0);
  px = inw(0x16);
  if(px > 296) px = 296;
  spr(px, 191, 1, 3, 1, 1, 0);
  x = x + dx;
  y = y + dy;
  if(x == 0 || x == 311) dx = -dx;
  if(y == 0 || (y == 183 && x >= px && x <= px + 24)) dy = -dy;
  if(y > 199) {
    return S_GAMEOVER;
  }
  return S_GAME;
 }
 
 int32_t gameOver() {
  dx = dy = 0;
  text(184, (320 - width(2, msg)) / 2, 90, 2, 112, 128, msg);
  if(getclk(BTN_L)) { return S_INIT;} else {return S_GAME;};
 }

So putting all these parts into the code, this should all run. Then I can start working on the next essential step: How to deal with all the objects?


The outcome(s)

Upon testing, the answer is a resounding no. For some reason, the switch call keeps breaking with a totally unhelpful 'syntax error' message. This could be due to a flawed implementation, or perhaps from me being a dill and writing this code while brainstorming in Word. However once replaced with if .. else if .. else statements, the code works properly. While annoying, I can work around this.


Also, I forgot to draw sprites, a lack quickly corrected.


Nevertheless, the state implementation works as expected, now all I need is to add a title state, so that you have to click the mouse to start the ball... not rolling, that's the wrong descriptor...


Tags:

Comments

Comments ain't a thing here.