Agradecimientos
Antes de entrar en materia, quiero agradecer la inspiración del artículo a la publicación de Eric Boez sobre lo que él llamó: "Un reto de domingo". Se propuso crear una versión rápida del conocido juego de Atari Pong de 1972 con FUSION-C V1.3. Podéis encontrarlo en su GitHub [1] y en su canal de YouTube [2].
Objetivo
Ver cómo con pocas líneas de código, paso a paso, somos capaces de crear mediante FUSION-C V1.2 una versión sencilla del juego Pong.
Igual que en el ejemplo de "Moviendo un Sprite", trabajaremos con Screen 5. Aquí, sin embargo, introduciremos un nuevo concepto: las colisiones entre Sprites, para que la pelota rebote en las paletas de los jugadores.
Hemos decidido dividir el artículo en varias entregas, para introducir de una a una las piezas del juego en cada nueva entrega. Empezaremos con la pelota y luego continuaremos con la paleta del jugador que controlamos. Después de las dos primeras entregas haremos un frontón para finalizar en la última agregando la paleta controlada por la CPU. Por lo tanto, quedaría de la siguiente manera:
Primera entrega: Pong (la pelota)
Segunda entrega: Pong (la paleta del jugador)
Tercera entrega: Front Pong
Cuarta entrega: Pong (la paleta de la CPU)
Introducción
Comenzamos la serie de artículos centrándonos primero en crear la pelota del juego y hacer que esta se mueva por sí sola en el espacio, rebotando en las cuatro paredes.
Vamos a construir el programa paso a paso, introduciendo el código poco a poco. Nos enfocaremos en las partes más relevantes del código, podréis encontrar el código completo al final del artículo, así como una versión en línea funcional.
Abre el editor de texto que prefieras, en mi caso Sublime Text 3, y nombra y guarda el archivo como "ball.c". Es con este con lo que trabajaremos durante este artículo. Si no has leído el artículo sobre "Automatizando la compilación con Sublime Text 3", ahora es un buen momento, ya que puede ser de gran ayuda.
En este primer artículo explicaremos las diferentes secciones de las que se compone un programa en C, al mismo tiempo que iremos introduciendo el código del "Pong (la pelota)". Comenzamos sin más dilación.
Sección de documentación
Todo archivo de C debería ir precedido por una breve descripción de lo que contiene, el nombre del programa, su fecha de creación, el autor o autores, entre otra información que creas conveniente incluir.
//
// ball.c
// Fusion-C Pong game example articles
// Here we will learn the movements of the ball
// MoltSXalats 2023
//
Sección del preprocesador
Aquí incluiremos las cabeceras de las bibliotecas que se utilizarán en el programa para que el sistema las enlace. En nuestro caso, utilizaremos: la biblioteca principal de fusion-c, la de gestión de Sprites de fusion-c y la de gestión de gráficos de fusion-c para el estándar msx2.
#include "fusion-c/header/msx_fusion.h"
#include "fusion-c/header/vdp_sprites.h"
#include "fusion-c/header/vdp_graph2.h"
Sección de definición de macros y constantes simbólicas
En esta sección se incluyen las macros y constantes simbólicas. A modo de ejemplo, crearemos dos constantes TRUE y FALSE asociadas a los enteros 1 y 0 respectivamente.
Sección de definición de tipos
En esta sección crearemos tipos distintos a los preestablecidos en el lenguaje C. Para nuestro ejemplo, hemos considerado oportuno crear un tipo Sprite.
typedef struct {
char spr;
char y;
char x;
char pat;
} Sprite;
El tipo Sprite es una estructura que consta de 4 variables relacionadas con los datos de un Sprite. Estas son el número del Sprite dentro de los 32 posibles, la posición inicial del Sprite en pantalla (x, y) y el patrón de la tabla de patrones donde se guardan las definiciones de los Sprites.
La pelota del juego, que introduciremos en breve, será del tipo Sprite.
Si deseas profundizar sobre las estructuras en C, puedes consultar [3].
Sección de definición de los prototipos de las funciones
En esta sección se definen los prototipos de las funciones que se utilizarán dentro de la función main(). La declaración de las funciones le proporciona información al compilador sobre una función que será utilizada pero aún no ha sido implementada. En particular, le dice al compilador los tipos de datos que deben pasarse y qué valores devuelve cada función.
void GameStart(void);
void DrawSprite(void);
void BallCal(void);
void AxisCal(void);
void FT_Wait(int);
Sección de definición de las variables globales
En esta sección declararemos las variables que utilizaremos dentro de la función main() y que serán globales a todo el módulo.
signed char DirX;
signed char DirY;
char PlyScore,CpuScore;
Sprite TheBall;
const char PatternBall[]={
0b11100000,
0b11100000,
0b11100000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000
};
A continuación, explicamos las variables más destacadas.
DirX y DirY las utilizaremos para mantener la dirección de la pelota mientras la movemos por las coordenadas (x, y). Estas variables toman valores de 1 o -1 en función de si nos movemos hacia la derecha o hacia la izquierda, o hacia abajo o hacia arriba.
En la figura de más arriba podemos ver en pantalla los ejes de coordenadas. Si nos movemos hacia la derecha, las X son positivas, y si nos movemos hacia la izquierda, son negativas. Para las Y, si nos movemos hacia abajo son positivas, mientras que si nos movemos hacia arriba, son negativas.
Hemos definido la pelota del Pong como un Sprite de 3x3 y creamos un patrón para la tabla de patrones a través de la variable PatternBall. El concepto de Sprite será capturado por la variable TheBall de tipo Sprite que hemos creado nosotros.
const char PatternBall[]={
0b11100000,
0b11100000,
0b11100000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000
};
Sección de la función main y el bucle de juego
En esta sección implementaremos la función main y dentro de ella el bucle de juego. Este bucle está compuesto por el while que podemos encontrar dentro de esta función.
Antes de entrar al bucle del juego se prepara todo el entorno: el color de la pantalla, el modo de la pantalla, el tipo de Sprites que se utilizarán, posición inicial y dirección de la pelota, etc. También se pinta la pantalla inicial del Pong con la función GameStart() para entrar en la ejecución de un bucle infinito que se repetirá hasta que se presione la tecla ESC, que corresponde al código ASCII 27 en el MSX.
El bucle de juego consta de cuatro funciones, la función que calcula la posición de la pelota y controla que esta esté dentro del escenario de juego BallCall(), la función que pinta la pelota en pantalla con las coordenadas obtenidas de la función anterior DrawSprite(), una función que hemos creado como ejemplo AxisCal() y que nos muestra en pantalla las coordenadas de la pelota en cada iteración, y la función FT_Wait() que impone una pausa al movimiento de la pelota para que el pintado fluya a una velocidad controlada, ni muy rápido ni muy lento.
void main (void)
{
SetColors(15,0,0);
Screen(5);
Sprite8();
SpriteDouble();
KeySound(0);
SetSpritePattern(0, PatternBall,8);
// Defining Variables
// Player Pad Sprite
PlyScore=0;
// Cpu Pad Sprite
CpuScore=0;
// Ball Sprite
TheBall.x=128;
TheBall.y=100;
TheBall.spr=0;
TheBall.pat=0;
DirX=1;
DirY=1;
GameStart();
while (Inkey()!=27) // Main loop, ESC to Exit
{
BallCal();
DrawSprite();
AxisCal();
FT_Wait(170);
}
// Ending program, and return to DOS
Screen(0);
KeySound(1);
Exit(0);
}
Implementación de las funciones definidas por el usuario
Es en esta sección de un programa en C donde se implementan las funciones que usaremos dentro de la función main() y cuyas cabeceras hemos definido anteriormente.
// Print the initial game screen
void GameStart(void)
{
char Temp[5];
PutText((256-20*8)/2,4,"Press SPACE to START",0);
// Initial Positions
TheBall.x=128;
TheBall.y=100;
DirX*=-1;
DrawSprite();
Itoa(PlyScore,Temp,10);
PutText(10,4,Temp,0);
Itoa(CpuScore,Temp,10);
PutText(235,4,Temp,0);
while (!IsSpace(WaitKey()))
{}
PutText((256-20*8)/2,4," ",0);
}
// Put all sprite on screen
void DrawSprite(void)
{
PutSprite(TheBall.spr,TheBall.pat,TheBall.x,TheBall.y,15);
}
// Calculates Ball Position
void BallCal(void)
{
TheBall.x+=DirX;
TheBall.y+=DirY;
if (TheBall.y>=204 || TheBall.y<=10)
DirY*=-1;
if (TheBall.x>=250 || TheBall.x<=6)
DirX*=-1;
}
// Prints Ball Coordinates
void AxisCal(void)
{
char Temp[5];
Itoa(TheBall.x,Temp,10);
if(StrLen(Temp)==1)
{
if(TheBall.x==9)
PutText(100,4," ",0);
PutText(100,4,Temp,0);
}
if(StrLen(Temp)==2) {
if(TheBall.x==99)
PutText(100,4," ",0);
PutText(100,4,Temp,0);
}
if(StrLen(Temp)==3)
{
PutText(100,4,Temp,0);
}
Itoa(TheBall.y,Temp,10);
if(StrLen(Temp)==1)
{
if(TheBall.y==9)
PutText(140,4," ",0);
PutText(140,4,Temp,0);
}
if(StrLen(Temp)==2) {
if(TheBall.y==99)
PutText(140,4," ",0);
PutText(140,4,Temp,0);
}
if(StrLen(Temp)==3)
{
PutText(140,4,Temp,0);
}
}
// Wait Routine
void FT_Wait(int cicles) {
unsigned int i;
for(i=0;i<cicles;i++)
{
while(IsVsync()==1)
// With FUSION 1.3 use...
//while(Vsynch()==0)
{}
}
}
A continuación, os dejamos el código completo y al final un enlace donde podréis ejecutar en línea el ejemplo en el que hemos trabajado.
//Documentation section
//
// ball.c
// Fusion-C Pong game example articles
// Here we will learn the movements of the ball
// MoltSXalats 2023
//
// Preprocessor section
#include "fusion-c/header/msx_fusion.h"
#include "fusion-c/header/vdp_sprites.h"
#include "fusion-c/header/vdp_graph2.h"
// Definition section of macros and symbolic constants
#define TRUE 1
#define FALSE 0
// Type definition section
typedef struct {
char spr; // Sprite ID
char y; // Y destination of the Sprite
char x; // X destination of the Sprite
char pat; // Pattern number to use
} Sprite;
// Definition section of function prototypes
void GameStart(void);
void DrawSprite(void);
void BallCal(void);
void AxisCal(void);
void FT_Wait(int);
// Definition section of global variables
signed char DirX; // Direction of the ball
signed char DirY;
char PlyScore,CpuScore;
Sprite TheBall; // Definining the ball Sprite Structure
const char PatternBall[]={
0b11100000,
0b11100000,
0b11100000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000
};
// Section of the main function and the game loop
void main (void)
{
SetColors(15,0,0);
Screen(5); // 256 x 212 pixel
Sprite8();
SpriteDouble(); // Magnified to 16 x 16 pixels
KeySound(0);
SetSpritePattern(0, PatternBall,8);
// Defining Variables
// Player Pad Sprite
PlyScore=0;
// Cpu Pad Sprite
CpuScore=0;
// Ball Sprite
TheBall.x=128;
TheBall.y=100;
TheBall.spr=0;
TheBall.pat=0;
DirX=1;
DirY=1;
GameStart();
while (Inkey()!=27) // Main loop, ESC to Exit
{
BallCal();
DrawSprite();
AxisCal();
FT_Wait(170);
}
// Ending program, and return to DOS
Screen(0);
KeySound(1);
Exit(0);
}
// User defined functions section
// Print the initial game screen
void GameStart(void)
{
char Temp[5];
// El texto es de 5 x 7
PutText((256-20*8)/2,4,"Press SPACE to START",0);
// Initial Positions
TheBall.x=128;
TheBall.y=100;
DirX*=-1;
DrawSprite();
Itoa(PlyScore,Temp,10);
PutText(10,4,Temp,0);
Itoa(CpuScore,Temp,10);
PutText(235,4,Temp,0);
while (!IsSpace(WaitKey()))
{}
PutText((256-20*8)/2,4," ",0);
}
// Put all sprites on screen
void DrawSprite(void)
{
PutSprite(TheBall.spr,TheBall.pat,TheBall.x,TheBall.y,15);
}
// Calculates Ball Position
void BallCal(void)
{
TheBall.x+=DirX;
TheBall.y+=DirY;
if (TheBall.y>=204 || TheBall.y<=10) // Douncing the Ball on the Top and Bottom border
DirY*=-1;
if (TheBall.x>=250 || TheBall.x<=6) // Douncing the Ball on the Left and Right border
DirX*=-1;
}
// Prints Ball Coordinates
void AxisCal(void)
{
char Temp[5];
Itoa(TheBall.x,Temp,10);
if(StrLen(Temp)==1)
{
if(TheBall.x==9)
PutText(100,4," ",0);
PutText(100,4,Temp,0);
}
if(StrLen(Temp)==2) {
if(TheBall.x==99)
PutText(100,4," ",0);
PutText(100,4,Temp,0);
}
if(StrLen(Temp)==3)
{
PutText(100,4,Temp,0);
}
Itoa(TheBall.y,Temp,10);
if(StrLen(Temp)==1)
{
if(TheBall.y==9)
PutText(140,4," ",0);
PutText(140,4,Temp,0);
}
if(StrLen(Temp)==2) {
if(TheBall.y==99)
PutText(140,4," ",0);
PutText(140,4,Temp,0);
}
if(StrLen(Temp)==3)
{
PutText(140,4,Temp,0);
}
}
// Wait Routine
void FT_Wait(int cicles) {
unsigned int i;
for(i=0;i<cicles;i++)
{
while(IsVsync()==1)
// With FUSION 1.3 use...
//while(Vsynch()==0)
{}
}
}
Haz clic aquí para ver el ejemplo en funcionamiento.
Comments