I implemented the following Tron cycle game so that it can be run on GameCube / Wii devices (quarantine...). Indeed, there exist many tools (I rely on devkitPPC) and I thought a Tron game would be an easy starting point for this kind of development.
I have no experience in C, neither in enforcing "classes" in C, or in game development (the naming might be horrible), therefore, any help would be welcomed!
The directory structure is as follows:
├── Makefile
├── source
│ └── tron.c
└── textures
├── ballsprites.png
└── textures.scf
And the different files are presented below (the only ones I wrote are tron.c and the sprites, all the credit for the rest goes to the team behind the devkit!).
Wall walls[NUM_PLAYERS][NUM_WALLS];
is a global variable, and not an attribute of the VersusEngineManager
class, because the emulator kept firing weird memory errors. Any help on this point would be great!
textures.scf
<filepath="ballsprites.png" id="ballsprites" colfmt=6 />
tron.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <math.h>
#include <gccore.h>
#include <ogc/tpl.h>
#include "textures_tpl.h"
#include "textures.h"
#define DEFAULT_FIFO_SIZE (256*1024)
static void *frameBuffer[2] = { NULL, NULL};
static GXRModeObj *rmode;
#define NUM_PLAYERS 2
#define NUM_WALLS 20000
#define SPEED 0x200
#define SPRITE_SIZE 4
#define WIDTH 640
#define HEIGHT 480
#define UPPER_MARGIN 20
#define TV_MARGIN 50
typedef struct
{
int x, y;
} Point;
bool Point_CheckInRectangle(Point *self, int xmin, int xmax, int ymin, int ymax);
bool Point_CheckCollision(Point *self, Point *other);
typedef struct
{
int x, y;
int dx, dy;
int image;
} Player;
int Player_init(Player *self, int x, int y, int dx, int dy, int image);
void Player_UpdatePosition(Player *self);
typedef struct
{
int x, y;
int image;
} Wall;
int playerLossesCount[NUM_PLAYERS];
Wall walls[NUM_PLAYERS][NUM_WALLS];
typedef struct
{
int speed;
int nPlayers;
int wallIndex;
Player players[NUM_PLAYERS];
} VersusEngineManager;
int VersusEngineManager_init(VersusEngineManager *self, int speed, int nPlayers);
bool VersusEngineManager_CheckPlayersCollision(VersusEngineManager *self);
bool VersusEngineManager_CheckWallCollision(VersusEngineManager *self, int playerId);
int VersusEngineManager_UpdateGameState(VersusEngineManager *self);
void VersusEngineManager_UpdatePlayerDirectionFromPAD(VersusEngineManager *self, int pad, int playerId);
void VersusEngineManager_UpdatePlayersDirectionFromPADs(VersusEngineManager *self);
void VersusEngineManager_UpdatePlayersDirectionFromPADs(VersusEngineManager *self)
{
VersusEngineManager_UpdatePlayerDirectionFromPAD(self, 0, 0);
VersusEngineManager_UpdatePlayerDirectionFromPAD(self, 1, 1);
}
typedef struct
{
int spriteSize;
} SpriteDrawer;
int SpriteDrawer_init(SpriteDrawer *self, int spriteSize);
void SpriteDrawer_Rectangle(SpriteDrawer *self, int x, int y, int width, int height, int image);
void SpriteDrawer_SpriteTex(SpriteDrawer *self, int x, int y, int width, int height, int image);
void SpriteDrawer_AllSprites(SpriteDrawer *self);
void SpriteDrawer_Sprites(SpriteDrawer *self, VersusEngineManager *versusEngineManager);
void SpriteDrawer_Scores(SpriteDrawer *self, VersusEngineManager *versusEngineManager);
void SpriteDrawer_ArenaSprites(SpriteDrawer *self);
GXTexObj texObj;
int main( int argc, char **argv ){
u32 fb; // initial framebuffer index
u32 first_frame;
f32 yscale;
u32 xfbHeight;
Mtx44 perspective;
Mtx GXmodelView2D;
void *gp_fifo = NULL;
GXColor background = {0, 0, 0, 0xff};
VIDEO_Init();
rmode = VIDEO_GetPreferredMode(NULL);
fb = 0;
first_frame = 1;
// allocate 2 framebuffers for double buffering
frameBuffer[0] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
frameBuffer[1] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
VIDEO_Configure(rmode);
VIDEO_SetNextFramebuffer(frameBuffer[fb]);
VIDEO_SetBlack(FALSE);
VIDEO_Flush();
VIDEO_WaitVSync();
if(rmode->viTVMode&VI_NON_INTERLACE) VIDEO_WaitVSync();
fb ^= 1;
// setup the fifo and then init the flipper
gp_fifo = memalign(32,DEFAULT_FIFO_SIZE);
memset(gp_fifo,0,DEFAULT_FIFO_SIZE);
GX_Init(gp_fifo,DEFAULT_FIFO_SIZE);
// clears the bg to color and clears the z buffer
GX_SetCopyClear(background, 0x00ffffff);
// other gx setup
GX_SetViewport(0,0,rmode->fbWidth,rmode->efbHeight,0,1);
yscale = GX_GetYScaleFactor(rmode->efbHeight,rmode->xfbHeight);
xfbHeight = GX_SetDispCopyYScale(yscale);
GX_SetScissor(0,0,rmode->fbWidth,rmode->efbHeight);
GX_SetDispCopySrc(0,0,rmode->fbWidth,rmode->efbHeight);
GX_SetDispCopyDst(rmode->fbWidth,xfbHeight);
GX_SetCopyFilter(rmode->aa,rmode->sample_pattern,GX_TRUE,rmode->vfilter);
GX_SetFieldMode(rmode->field_rendering,((rmode->viHeight==2*rmode->xfbHeight)?GX_ENABLE:GX_DISABLE));
if (rmode->aa)
GX_SetPixelFmt(GX_PF_RGB565_Z16, GX_ZC_LINEAR);
else
GX_SetPixelFmt(GX_PF_RGB8_Z24, GX_ZC_LINEAR);
GX_SetCullMode(GX_CULL_NONE);
GX_CopyDisp(frameBuffer[fb],GX_TRUE);
GX_SetDispCopyGamma(GX_GM_1_0);
// setup the vertex descriptor
// tells the flipper to expect direct data
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_F32, 0);
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
GX_SetNumChans(1);
GX_SetNumTexGens(1);
GX_SetTevOp(GX_TEVSTAGE0, GX_REPLACE);
GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
GX_InvalidateTexAll();
TPLFile spriteTPL;
TPL_OpenTPLFromMemory(&spriteTPL, (void *)textures_tpl,textures_tpl_size);
TPL_GetTexture(&spriteTPL,ballsprites,&texObj);
GX_LoadTexObj(&texObj, GX_TEXMAP0);
guOrtho(perspective,0,479,0,639,0,300);
GX_LoadProjectionMtx(perspective, GX_ORTHOGRAPHIC);
PAD_Init();
srand(time(NULL));
GX_SetViewport(0,0,rmode->fbWidth,rmode->efbHeight,0,1);
guMtxIdentity(GXmodelView2D);
guMtxTransApply (GXmodelView2D, GXmodelView2D, 0.0F, 0.0F, -5.0F);
GX_LoadPosMtxImm(GXmodelView2D,GX_PNMTX0);
GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR);
GX_SetAlphaUpdate(GX_TRUE);
GX_SetColorUpdate(GX_TRUE);
VersusEngineManager versusEngineManager;
VersusEngineManager_init(&versusEngineManager, SPEED, NUM_PLAYERS);
SpriteDrawer spriteDrawer;
SpriteDrawer_init(&spriteDrawer, SPRITE_SIZE);
while(1) {
GX_InvVtxCache();
GX_InvalidateTexAll();
GX_ClearVtxDesc();
GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);
VersusEngineManager_UpdatePlayersDirectionFromPADs(&versusEngineManager);
int gameState = VersusEngineManager_UpdateGameState(&versusEngineManager);
if(gameState==1) { VersusEngineManager_init(&versusEngineManager, SPEED, NUM_PLAYERS); }
SpriteDrawer_Sprites(&spriteDrawer, &versusEngineManager);
GX_DrawDone();
GX_CopyDisp(frameBuffer[fb],GX_TRUE);
VIDEO_SetNextFramebuffer(frameBuffer[fb]);
if(first_frame) {
VIDEO_SetBlack(FALSE);
first_frame = 0;
}
VIDEO_Flush();
VIDEO_WaitVSync();
fb ^= 1; // flip framebuffer
}
return 0;
}
float texCoords[] = {
0.0 ,0.0 , 0.5, 0.0, 0.5, 0.5, 0.0, 0.5,
0.5 ,0.0 , 1.0, 0.0, 1.0, 0.5, 0.5, 0.5,
0.0 ,0.5 , 0.5, 0.5, 0.5, 1.0, 0.0, 1.0,
0.5 ,0.5 , 1.0, 0.5, 1.0, 1.0, 0.5, 1.0
};
int SpriteDrawer_init(SpriteDrawer *self, int spriteSize)
{
self->spriteSize = spriteSize;
return 0;
}
void SpriteDrawer_Sprites(SpriteDrawer *self, VersusEngineManager *versusEngineManager) {
int spriteSize = self->spriteSize;
for(int playerId=0;playerId<NUM_PLAYERS;playerId++){
SpriteDrawer_SpriteTex(self,
versusEngineManager->players[playerId].x >> 8,
versusEngineManager->players[playerId].y >> 8,
spriteSize,
spriteSize,
versusEngineManager->players[playerId].image);
for(int wallId=0; wallId < NUM_WALLS; wallId++) {
SpriteDrawer_SpriteTex(self,
walls[playerId][wallId].x >> 8,
walls[playerId][wallId].y >> 8,
spriteSize,
spriteSize,
walls[playerId][wallId].image);
}
}
SpriteDrawer_ArenaSprites(self);
SpriteDrawer_Scores(self, versusEngineManager);
};
void SpriteDrawer_ArenaSprites(SpriteDrawer *self) {
int spriteSize = self->spriteSize;
SpriteDrawer_Rectangle(self, TV_MARGIN,UPPER_MARGIN+TV_MARGIN, WIDTH-2*TV_MARGIN-spriteSize, HEIGHT - 2*TV_MARGIN - spriteSize,3);
};
void SpriteDrawer_Rectangle(SpriteDrawer *self, int x,int y,int width,int height,int sprite) {
int spriteSize = self->spriteSize;
for(int i=x; i < x+width;i+=spriteSize){
SpriteDrawer_SpriteTex(self, i,y,spriteSize, spriteSize, sprite);
}
for(int i=x; i < x+width+spriteSize;i+=spriteSize){
SpriteDrawer_SpriteTex(self, i,y+height,spriteSize, spriteSize, sprite);
}
for(int i=y; i < y+height;i+=spriteSize){
SpriteDrawer_SpriteTex(self, x,i,spriteSize, spriteSize, sprite);
}
for(int i=y; i < y+height;i+=spriteSize){
SpriteDrawer_SpriteTex(self, x+width,i,spriteSize, spriteSize, sprite);
}
};
void SpriteDrawer_Scores(SpriteDrawer *self, VersusEngineManager *versusEngineManager) {
int spriteSize = self->spriteSize;
for(int playerId=0;playerId<NUM_PLAYERS;playerId++){
for(int score=0;score < playerLossesCount[playerId]; score++){
SpriteDrawer_SpriteTex(self, TV_MARGIN + ( (score * SPEED) >> 8), TV_MARGIN + 2* SPEED * (1 + playerId) >> 8, spriteSize, spriteSize, versusEngineManager->players[playerId].image);
}
}
};
void SpriteDrawer_SpriteTex(SpriteDrawer *self, int x, int y, int width, int height, int image ) {
int texIndex = image * 8;
GX_Begin(GX_QUADS, GX_VTXFMT0, 4); // Draw A Quad
GX_Position2f32(x, y); // Top Left
GX_TexCoord2f32(texCoords[texIndex],texCoords[texIndex+1]);
texIndex+=2;
GX_Position2f32(x+width-1, y); // Top Right
GX_TexCoord2f32(texCoords[texIndex],texCoords[texIndex+1]);
texIndex+=2;
GX_Position2f32(x+width-1,y+height-1); // Bottom Right
GX_TexCoord2f32(texCoords[texIndex],texCoords[texIndex+1]);
texIndex+=2;
GX_Position2f32(x,y+height-1); // Bottom Left
GX_TexCoord2f32(texCoords[texIndex],texCoords[texIndex+1]);
GX_End(); // Done Drawing The Quad
}
int Player_init(Player *self, int x, int y, int dx, int dy, int image)
{
self->x = x;
self->y = y;
self->dx = dx;
self->dy = dy;
self->image = image;
return 0;
}
void Player_UpdatePosition(Player *self)
{
self->x += self->dx;
self->y += self->dy;
}
bool Point_CheckInRectangle(Point *self, int xmin, int xmax, int ymin, int ymax)
{
return self->x < xmin || self->x > xmax || self->y < ymin || self->y > ymax;
}
bool Point_CheckCollision(Point *self, Point *other)
{
return self->x == other->x && self->y==other->y;
}
int VersusEngineManager_init(VersusEngineManager *self, int speed, int nPlayers)
{
self->speed = speed;
self->nPlayers = nPlayers;
self->wallIndex = 0;
Player player0, player1;
Player_init(&player0, ((WIDTH - SPRITE_SIZE ) / 2 ) << 8,100 << 8,0, SPEED, 0);
Player_init(&player1, ((WIDTH - SPRITE_SIZE ) / 2 ) << 8,400 << 8,0, -SPEED, 1);
self->players[0] = player0;
self->players[1] = player1;
for(int playerId=0;playerId<self->nPlayers;playerId++){
for(int wallId=0; wallId < NUM_WALLS; wallId++) {
walls[playerId][wallId].x = 0;
walls[playerId][wallId].y = 0;
walls[playerId][wallId].image = 2;
}
}
return 0;
}
bool VersusEngineManager_CheckPlayersCollision(VersusEngineManager *self)
{
for(int i=0; i < self->nPlayers; i++)
{
for(int j=i+1; j < self->nPlayers; j++)
{
if(Point_CheckCollision(&self->players[i], &self->players[j]) ) {
return true; }
}
}
return false;
};
bool VersusEngineManager_CheckWallCollision(VersusEngineManager *self, int playerId)
{
if(Point_CheckInRectangle(&self->players[playerId],
TV_MARGIN<<8,
WIDTH-SPRITE_SIZE-TV_MARGIN << 8,
(UPPER_MARGIN + TV_MARGIN) <<8 ,
(HEIGHT+UPPER_MARGIN-SPRITE_SIZE-TV_MARGIN) << 8))
{return true;}
for(int i=0;i<NUM_PLAYERS;i++){
for(int wallId=0; wallId < self->wallIndex; wallId++) {
if(self->players[playerId].x == walls[i][wallId].x && self->players[playerId].y == walls[i][wallId].y) {
return true;
}
}
}
return false;
}
int VersusEngineManager_UpdateGameState(VersusEngineManager *self)
{
for(int playerId=0;playerId<self->nPlayers;playerId++){
Player_UpdatePosition(&self->players[playerId]);
bool collision = false;
if (VersusEngineManager_CheckPlayersCollision(self)) {
collision = true;
}
else if(VersusEngineManager_CheckWallCollision(self, playerId)) {
playerLossesCount[playerId] += 1;
collision = true;
}
walls[playerId][self->wallIndex].x = self->players[playerId].x;
walls[playerId][self->wallIndex].y = self->players[playerId].y;
walls[playerId][self->wallIndex].image = playerId;
if(collision) {return 1;} ;
}
self->wallIndex += 1;
return 0;
};
void VersusEngineManager_UpdatePlayerDirectionFromPAD(VersusEngineManager *self, int pad, int playerId)
{
PAD_ScanPads();
if (PAD_ButtonsDown(pad) & PAD_BUTTON_LEFT) {
if (self->players[playerId].dx ==0) {
self->players[playerId].dx = -self->speed;
self->players[playerId].dy = 0;
return;
}
}
if (PAD_ButtonsDown(pad) & PAD_BUTTON_RIGHT) {
if (self->players[playerId].dx ==0) {
self->players[playerId].dx = self->speed;
self->players[playerId].dy = 0;
return;
}
}
if (PAD_ButtonsDown(pad) & PAD_BUTTON_UP) {
if (self->players[playerId].dy ==0) {
self->players[playerId].dx = 0;
self->players[playerId].dy = -self->speed;
return;
}
}
if (PAD_ButtonsDown(pad) & PAD_BUTTON_DOWN) {
if (self->players[playerId].dy ==0) {
self->players[playerId].dx = 0;
self->players[playerId].dy = self->speed;
return;
}
}
}
Makefile
#---------------------------------------------------------------------------------
# Clear the implicit built in rules
#---------------------------------------------------------------------------------
.SUFFIXES:
.SECONDARY:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITPPC)),)
$(error "Please set DEVKITPPC in your environment. export DEVKITPPC=<path to>devkitPPC")
endif
include $(DEVKITPPC)/wii_rules
#---------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# INCLUDES is a list of directories containing extra header files
#---------------------------------------------------------------------------------
TARGET := $(notdir $(CURDIR))
BUILD := build
SOURCES := source
DATA :=
TEXTURES := textures
INCLUDES :=
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
CFLAGS = -g -O2 -Wall $(MACHDEP) $(INCLUDE)
CXXFLAGS = $(CFLAGS)
LDFLAGS = -g $(MACHDEP) -Wl,-Map,$(notdir $@).map
#---------------------------------------------------------------------------------
# any extra libraries we wish to link with the project
#---------------------------------------------------------------------------------
LIBS := -logc -lm
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS :=
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir)) \
$(foreach dir,$(TEXTURES),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
#---------------------------------------------------------------------------------
# automatically build a list of object files for our project
#---------------------------------------------------------------------------------
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
sFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.S)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
SCFFILES := $(foreach dir,$(TEXTURES),$(notdir $(wildcard $(dir)/*.scf)))
TPLFILES := $(SCFFILES:.scf=.tpl)
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
export LD := $(CC)
else
export LD := $(CXX)
endif
export OFILES_BIN := $(addsuffix .o,$(BINFILES)) $(addsuffix .o,$(TPLFILES))
export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(sFILES:.s=.o) $(SFILES:.S=.o)
export OFILES := $(OFILES_BIN) $(OFILES_SOURCES)
export HFILES := $(addsuffix .h,$(subst .,_,$(BINFILES))) $(addsuffix .h,$(subst .,_,$(TPLFILES)))
#---------------------------------------------------------------------------------
# build a list of include paths
#---------------------------------------------------------------------------------
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD) \
-I$(LIBOGC_INC)
#---------------------------------------------------------------------------------
# build a list of library paths
#---------------------------------------------------------------------------------
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) \
-L$(LIBOGC_LIB)
export OUTPUT := $(CURDIR)/$(TARGET)
.PHONY: $(BUILD) clean
#---------------------------------------------------------------------------------
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(OUTPUT).elf $(OUTPUT).dol
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
$(OUTPUT).dol: $(OUTPUT).elf
$(OUTPUT).elf: $(OFILES)
$(OFILES_SOURCES) : $(HFILES)
#---------------------------------------------------------------------------------
# This rule links in binary data with the .bin extension
#---------------------------------------------------------------------------------
%.bin.o %_bin.h : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
#---------------------------------------------------------------------------------
%.tpl.o %_tpl.h : %.tpl
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPSDIR)/*.d
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
ballsprites.png
textures.scf
smells like XML but doesn't have an enclosing XML tag. Is this expected? \$\endgroup\$