Skip to content
MegaMech edited this page Jul 12, 2022 · 12 revisions

Threading

The game begins by setting up its four threads; idle, video, audio, and the game loop. The idle thread allows the cpu to sleep. Without it, if at any time execution of all threads were paused, the cpu would never be able to continue.

As such, the idle thread runs the following code: while(TRUE); (it runs in a perpetual loop of nothing; sleep). N64 threads are ran based on priority running whichever thread holds the most of it. Threads can also pause and wait for events. Note that the N64 is not multi-threaded by modern standards as the other threads contain specific purposes.

Overall Control Flow

init_threads:
  idle, video, audio, game loop
game loop:
  audio?
  jumpTo a specific menu or race based on a gameState flag.
  profiler
  config_gfx_pool
  read_controllers
  game_state_controller
  endDL/vsync
game_state_controller:
  switch(loc)
     menus -> switch(menu) { // do menu stuff }
     race_logic_loop -> spaghetti
     ending_sequence (trophy scene)
     credits

If mk64 is in a menu state it will branch off to the menu code, running relevant bits of code based on more flags such as which particular menu the user is in. This will loop until the state changes to a different one such as race mode. If mk64 is in a race state, then race related code is ran and it spaghetti's off into a wide series of branches.

This relatively primitive design could be defined as a state machine from an abstract point of view. This would differ from an OOP design that uses objects and hierarchy. You will become very familiar with this design principle as you explore the code-base. During any step of the game loop, a switch can be setup to check a flag then run code relevant to the situation. For instance, a flag can check whether a race is in-progress or complete. If in-progress set the player to human controlled. If complete, set player to AI controlled.

Actors

The above is also true for actors. Do you desire course specific functionality? Just use a switch that evaluates gCurrentCourseId with the course specific code in the cases.

If you are familiar with oot or sm64 prepare to be very disappointed. Both games split actors into separate files. In an unorderly fashion, mk64 appears to place all actors in the same file save for the odd exception.

Actor setup:

for actorListSize {
  actor = gActorList[i]
  switch(actor->type) {
    case TYPE: actor_name(args, actor);
  }
}

// Camera/Mat4 are optional
void actor_name(Camera, Mat4, actor) {
  actor->pos[x] += 10; // Increase the actors X position by ten every frame or game loop.
  actor->rot[y] -= 1; // Decrease the actors Y rotation by one every frame or game loop.

  // Increase the actors velocity until it reaches fifteen.
  if (actor->velocity[z] < 15) {
    actor->velocity[z] += 5 // Increase the actors Z velocity by five every frame.
  }
}

Check actor_types.h for a full list of options. The actor struct is customizable so long as the size of the struct stays identical to the spec. Generally, the types in the struct may be modified so long as type and flags stay the same.

See update_obj_railroad_crossing for an example of how a timer may be used.

Audio may be activated in the following method: func_800C98B8(actor->pos, actor->velocity, s32_audio_flag);

Clone this wiki locally