Nu1: Adding input and output to the N64 program

In this page we’ll be having a look at the next sample program on the list of NuSystem N64 homebrew samples, Nu1. This one goes into a bit more depth compared to the previous chapter since it not only displays a square, but it rotates it and allows the player to control the rotation speed and the location of the centre of the square. On top of that, it outputs text showing the current coordinates of the square.

Note: If you haven’t seen it yet, please go through the page on Nu0 to find out about the basics of how a N64 program is booted to display a simple square.

The main meat of the differences in this program is in the main.c and stage00.c files. main.c now calls to more functions meant to update the the screen, while stage00.c defines them and does the work.

What Nu1 does

  • Everything in nu0
  • Makes the square spin
  • Reads controller input
  • Changes the square’s spin and location based on controller input
  • Displays the x and y coordinates in the output

Nu1 Structure

nu1-structure

Program output

Files included

The program is very similar to the previous one. In fact, most of the code is identical to that for nu0.

In places where I label a file as ‘mostly identical’, it means that it’s pretty much the same file except that it might have an extra variable declaration here or there.

  • main.c – Changes: Added controller variables and
  • main.h – NEW: A few variables are defined.
  • stage00.c – Changes: Added function to take controller input and make the square spin.
  • gfxinit.c – Identical.
  • spec – Identical.
  • graphic.c – Mostly identical.
  • graphic.h – Mostly identical.
  • Makefile/.dos/.irix – Mostly identical.
  • readme.txt – Mostly identical.

What each of the files do

I won’t be going through all the bits that are similar to that in Nu0, only the parts of the code that are new or different for Nu1.

main.c

Again, this is the main container for the game.

Code

#include 
#include "main.h"
void stage00(int);
void initStage00(void);
void makeDL00(void);
void updateGame00(void);
NUContData contdata[1];
u8 contPattern;
void mainproc(void)
{
  nuGfxInit();
  contPattern = nuContInit();
  initStage00();
  nuGfxFuncSet((NUGfxFunc)stage00);
  nuGfxDisplayOn();
  while(1)
    ;
}
void stage00(int pendingGfx)
{
  if(pendingGfx < 3)
    makeDL00();		
  updateGame00(); 
}

Line by line

#include "main.h" is a new header file – look this up in a later section about the file.
void initStage00(void); void updateGame00(void); are two new functions that for now are just prototypes but will be properly defined later in stage00.c.
NUContData contdata[1]; u8 contPattern; set the variables for the controller data to be read.
contPattern = nuContInit(); initializes the Controller, Controller Pak, and Rumble Pak so that they can be used. It sets contPattern to be the bit pattern of what the controller reads. In other words, contPattern is the controller input.
initStage00(); initialises the variables that control the rotation and position of the square (defined later in stage00.c).
Finally, the callback function at the end adds updateGame00(); which is a new function (defined later in stage00.c).

main.h

This is a new file for Nu1. Pretty simple, all it does is define some constants if they haven’t been defined already.

Code

#ifndef MAIN_H
#define MAIN_H
#ifdef _LANGUAGE_C
extern NUContData contdata[1];
extern u8 contPattern;
#endif
#endif

The only line

NUContData contdata[1]; u8 contPattern; set the variables for the controller data to be read.

stage00.c

Again, this is where the most meat of the program happens.

Code

#include 
#include 
#include "main.h"
#include "graphic.h"
static float theta;
static float triPos_x;
static float triPos_y;
void shadetri(Dynamic* dynamicp);
void initStage00(void) {
  triPos_x = 0.0;
  triPos_y = 0.0;
  theta = 0.0;
}
void makeDL00(void) {
  Dynamic* dynamicp;
  char conbuf[20]; 
  dynamicp = &gfx_dynamic[gfx_gtask_no];
  glistp = &gfx_glist[gfx_gtask_no][0];
  gfxRCPInit();
  gfxClearCfb();
  guOrtho(&dynamicp->projection,
	  -(float)SCREEN_WD/2.0F, (float)SCREEN_WD/2.0F,
	  -(float)SCREEN_HT/2.0F, (float)SCREEN_HT/2.0F,
	  1.0F, 10.0F, 1.0F);
  guRotate(&dynamicp->modeling, theta, 0.0F, 0.0F, 1.0F);
  guTranslate(&dynamicp->translate, triPos_x, triPos_y, 0.0F);
  shadetri(dynamicp);
  gDPFullSync(glistp++);
  gSPEndDisplayList(glistp++);
  assert((glistp - gfx_glist[gfx_gtask_no]) < GFX_GLIST_LEN); nuGfxTaskStart(&gfx_glist[gfx_gtask_no][0], (s32)(glistp - gfx_glist[gfx_gtask_no]) * sizeof (Gfx), NU_GFX_UCODE_F3DEX , NU_SC_NOSWAPBUFFER);
  if(contPattern & 0x1)
    {
      nuDebConTextPos(0,12,23);
      sprintf(conbuf,"triPos_x=%5.1f",triPos_x);
      nuDebConCPuts(0, conbuf);

      nuDebConTextPos(0,12,24);
      sprintf(conbuf,"triPos_y=%5.1f",triPos_y);
      nuDebConCPuts(0, conbuf);
    }
  else
    {
      nuDebConTextPos(0,9,24);
      nuDebConCPuts(0, "Controller1 not connect");
    }
  nuDebConDisp(NU_SC_SWAPBUFFER);
  gfx_gtask_no ^= 1;
}
void updateGame00(void) {  
  static float vel = 1.0;
  nuContDataGetEx(contdata,0);
  triPos_x = contdata->stick_x;
  triPos_y = contdata->stick_y;
  if(contdata[0].trigger & A_BUTTON) {
      vel = -vel;
      osSyncPrintf("A button Push\n");
  }
  if(contdata[0].button & B_BUTTON)
    theta += vel * 3.0;
  else
    theta += vel;
  if(theta>360.0)
      theta-=360.0;
  else if (theta<0.0) theta+=360.0; } static Vtx shade_vtx[] = { { -64, 64, -5, 0, 0, 0, 0, 0xff, 0, 0xff }, { 64, 64, -5, 0, 0, 0, 0, 0, 0, 0xff }, { 64, -64, -5, 0, 0, 0, 0, 0, 0xff, 0xff }, { -64, -64, -5, 0, 0, 0, 0xff, 0, 0, 0xff }, }; void shadetri(Dynamic* dynamicp) { gSPMatrix(glistp++,OS_K0_TO_PHYSICAL(&(dynamicp->projection)),
		G_MTX_PROJECTION|G_MTX_LOAD|G_MTX_NOPUSH);
  gSPMatrix(glistp++,OS_K0_TO_PHYSICAL(&(dynamicp->translate)),
		G_MTX_MODELVIEW|G_MTX_LOAD|G_MTX_NOPUSH);
  gSPMatrix(glistp++,OS_K0_TO_PHYSICAL(&(dynamicp->modeling)),
		G_MTX_MODELVIEW|G_MTX_MUL|G_MTX_NOPUSH);

  gSPVertex(glistp++,&(shade_vtx[0]),4, 0);

  gDPPipeSync(glistp++);
  gDPSetCycleType(glistp++,G_CYC_1CYCLE);
  gDPSetRenderMode(glistp++,G_RM_AA_OPA_SURF, G_RM_AA_OPA_SURF2);
  gSPClearGeometryMode(glistp++,0xFFFFFFFF);
  gSPSetGeometryMode(glistp++,G_SHADE| G_SHADING_SMOOTH);

  gSP2Triangles(glistp++,0,1,2,0,0,2,3,0);
}

Line by line

static float theta;
static float triPos_x;
static float triPos_y;
void shadetri(Dynamic* dynamicp);
void initStage00(void) {
  triPos_x = 0.0;
  triPos_y = 0.0;
  theta = 0.0;
}

This part just declares and sets the variables for the coordinates and rotation of the square.

  dynamicp = &gfx_dynamic[gfx_gtask_no];
  glistp = &gfx_glist[gfx_gtask_no][0];

These are two pointers that contain the address of the display list buffer. Note that in nu0, &gfx_dynamic is used, while in nu1, dynamicp becomes the variable used, but they are both used for the same purpose. A rose by any other name.

  guRotate(&dynamicp->modeling, theta, 0.0F, 0.0F, 1.0F);
  guTranslate(&dynamicp->translate, triPos_x, triPos_y, 0.0F);

The part with guRotate is now changed so that the square rotates about the z (depth) axis and uses theta as the angle of rotation. guTranslate does the same thing, but moves the square along the screen according to triPos_x and triPos_y.

  nuGfxTaskStart(&gfx_glist[gfx_gtask_no][0],
		 (s32)(glistp - gfx_glist[gfx_gtask_no]) * sizeof (Gfx),
		 NU_GFX_UCODE_F3DEX , NU_SC_NOSWAPBUFFER)

This is mostly the same, except that NU_SC_NOSWAPBUFFER is used instead of switching buffers. This is because the buffers will be switched later on in the program.

  if(contPattern & 0x1)    {
      nuDebConTextPos(0,12,23);
      sprintf(conbuf,"triPos_x=%5.1f",triPos_x);
      nuDebConCPuts(0, conbuf);
      nuDebConTextPos(0,12,24);
      sprintf(conbuf,"triPos_y=%5.1f",triPos_y);
      nuDebConCPuts(0, conbuf);
    } else {
      nuDebConTextPos(0,9,24);
      nuDebConCPuts(0, "Controller1 not connect");
    }
  nuDebConDisp(NU_SC_SWAPBUFFER);

This should be pretty clear. The if statement checks to see if there is a controller connected to the console. The rest of the code just prints out values. nuDebConTextPos determines the location of the text, sprintf fills the conbuf variable with the string and nuDebConCPuts outputs the text to the screen. nuDebConDisp(NU_SC_SWAPBUFFER); then proceeds to display the text and swap the frame buffer.

gfx_gtask_no ^= 1;

This line takes the last bit from gfx_gtask_no and flips it to switch display list buffers.


So for now we’ve been looking at the makeDL00 function, we’ll now move on to the next one over at updateGame00. This is an entirely new function, so we’ll go through it line by line.

static float vel = 1.0;

This line sets a variable for the speed of rotation. Simple.

nuContDataGetEx(contdata,0);

Here the data from controller 1 (here it’s 0 because computers count that way) and stores it into contdata.

  triPos_x = contdata->stick_x;
  triPos_y = contdata->stick_y;

Pretty simple. It takes the x and y tilt values for the control stick and saves them as the triPos variables.

  if(contdata[0].trigger & A_BUTTON)
    {
      vel = -vel;
      osSyncPrintf("A button Push\n");
    }

This is pretty simple, it checks whether the trigger data (latest button press change) is equal to A, and if so it multiplies the speed by -1, effectively reversing its direction.

osSyncPrintf("A button Push\n"); then outputs a message to the debug window. We’re not using one, so you can ignore this.

  if(contdata[0].button & B_BUTTON)
    theta += vel * 3.0;
  else
    theta += vel;

  if(theta>360.0)
      theta-=360.0;
  else if (theta<0.0)
      theta+=360.0;

theta (θ) is the number used to determine the rotation angle of our square, and this bit of code is the one that manipulates it. The first four lines determine whether the B button is pressed; if it isn’t then the square spins normally, and if it is then it spins three times as fast (respecting the direction of rotation either way).

The last four lines force theta to stay in the 0-360 range by increasing or decreasing its value by 360 if it oversteps its bounds. Pretty simple really.


The shadetri function is pretty much the same, except that these lines are added:

  gSPMatrix(glistp++,OS_K0_TO_PHYSICAL(&(dynamicp->translate)),
		G_MTX_MODELVIEW|G_MTX_LOAD|G_MTX_NOPUSH);

Which adds a translate to the display list.

Conclusion on Nu1

It’s pretty clear – Nu1 is the exact same thing as the previous installment, but with a few things added to take controller input and change what is displayed on the screen.

In the first two chapters, we’ve been focusing only on stage00 to take care of all our action. In the next chapter, we’ll be looking at switching stages by adding stage01.


Search

Subscribe to the mailing list

Follow N64 Squid

  • RSS Feed
  • YouTube

Random featured posts