Muchas personas cuando empiezan a entrar en el mundo de la programación tienen la iniciativa o inquietud de ver como se implementa un juego, pues bueno para todas esas personas en este post les vengo a compartir un código muy básico, el cual es el fundamento de casi cualquier juego que involucre el movimiento de algún elemento por parte del Usuario, como por ejemplo: Mario, Pac Man, Bomber Man, Snake, Space Invaders, entre muchos otros.
Bueno el ejemplo es un código que nos permite mover un objeto (en este caso un muñeco) mediante las flechas (Arriba, Abajo, Derecha, Izquierda) por una área definida en la pantalla. Acá podemos ver una imagen alusiva al ejemplo.
Para la implementación de este código y cualquier otro a futuro que utilice el mismo paradigma primero debemos conocer sobre la programación orientada a objetos (POO), dado a que prácticamente cada componente del juego lo manejaremos como un objeto. Pero que es un objeto o que es POO?, bueno básicamente es la abstracción de un objeto del mundo real a un mundo virtual o programado. Cada objeto tiene sus propiedades (atributos) y funciones en un mundo real, tomaremos las funciones y propiedades que necesitemos según la implementación y lo encapsularemos en una clase, luego de esta podremos crear distintos objetos. Es importante tomarse el tiempo para pensar cómo se estructuraran las clases y cuales necesitaremos, dado a que es la estructura principal del juego.
El segundo aspecto importante son las coordenadas, estas se representan mediante dos valores, "x" que representa la anchura en una pantalla y "y" la altura. Mediante estos dos valores podemos posicionar un objeto en algún lugar de la pantalla. Es importante que cada objeto dentro de sus propiedades tengan las coordenadas, para que el mismo conozca su ubicación y basado en ella pueda tomar decisiones. Otro aspecto importante a recalcar es que entre más alto sea el valor x este estará más hacia la derecha nuestra y entre menor sea el valor de y más hacia abajo de la pantalla.
El tercer aspecto que debemos conocer es el ciclo de vida del juego, todo juego tiene un ciclo de vida desde donde se inicia hasta que ocurra un evento de GameOver el cual es la regla que hace que el juego termine como por ejemplo que se hayan terminado las vidas de personaje del juego o alguna otra regla según la lógica del juego. Para esto se hace la implementación de un daemon, que básicamente es un ciclo dentro del cual deben llamarse todas las acciones que ocurren en el juego, y la condición de parada de dicho ciclo seria el evento GameOver. Para este ejemplo como no es una implementación completa de un juego no existe una regla en la cual termine, sino que esto ocurre cuando el usuario presione la tecla "Esc".
El tercer aspecto que debemos conocer es el ciclo de vida del juego, todo juego tiene un ciclo de vida desde donde se inicia hasta que ocurra un evento de GameOver el cual es la regla que hace que el juego termine como por ejemplo que se hayan terminado las vidas de personaje del juego o alguna otra regla según la lógica del juego. Para esto se hace la implementación de un daemon, que básicamente es un ciclo dentro del cual deben llamarse todas las acciones que ocurren en el juego, y la condición de parada de dicho ciclo seria el evento GameOver. Para este ejemplo como no es una implementación completa de un juego no existe una regla en la cual termine, sino que esto ocurre cuando el usuario presione la tecla "Esc".
Con estos tres aspectos claros podemos realizar cualquier implementación de un juego como los ejemplos dados al inicio, solo se debe jugar un poco con los objetos y principalmente con las coordenadas de los objetos que es el alma del juego. Ya explicado estos principios, continuare con la estructura y posteriormente el código fuente del ejemplo del post.
Antes que nada el ejemplo hace uso de un encabezado (archivo.h) el cual tiene algunas funciones básicas como cambiar el color de la consola, posicionar el cursor de impresión de consola en una coordenada específica, ocultar el cursor de la consola, entre otras. Estas funciones son necesarias en el resto de la implementación del ejemplo, y la idea es que este mismo encabezado pueda utilizarse en ejemplos futuros sin necesidad de cambiarse volver a programar el código. Este es el código de dicho encabezado:
Antes que nada el ejemplo hace uso de un encabezado (archivo.h) el cual tiene algunas funciones básicas como cambiar el color de la consola, posicionar el cursor de impresión de consola en una coordenada específica, ocultar el cursor de la consola, entre otras. Estas funciones son necesarias en el resto de la implementación del ejemplo, y la idea es que este mismo encabezado pueda utilizarse en ejemplos futuros sin necesidad de cambiarse volver a programar el código. Este es el código de dicho encabezado:
//////////////////////////////////////////// // Definición Bibliotecas y definiciones // q' utilizaremos en el código /////////////////////////////////////////// #include <windows.h> #include <conio.h> //Definiciones de teclas #define ARR 72//Valor ASCII para tecla flecha arriba. #define ABJ 80//Valor ASCII para tecla flecha abajo. #define DER 77//Valor ASCII para tecla flecha Derecha. #define IZQ 75//Valor ASCII para tecla flecha Izquierda. #define ENT 13//Valor ASCII para tecla Enter. #define ESC 27//Valor ASCII para tecla Esc. //Definiciones de configuración pantalla Salir #define LIN_HOR_SALIR 205//Lineas Horizontales #define LIN_VER_SALIR 186//Lineas Vericales #define ESQ_SUP_DER_SALIR 201//Esquina superior Derecha #define ESQ_SUP_IZQ_SALIR 200//Esquina superior Izquierda #define ESQ_INF_DER_SALIR 187//Esquina inferior Derecha #define ESQ_INF_IZQ_SALIR 188//Esquina inferior Izquierda #define COLOR_CAJA_SALIR 0x47//Color de la caja de la pantalla salir. #define OPC_SELEC_SALIR 0x4E//Color de la opción seleccionada en la pantalla salir. //////////////////////////////////////////// // Clase UtilidadesInterfaz //Donde se implementara la lógica //necesaria para crear una interfaz con flechas. /////////////////////////////////////////// class UtilidadesInterfaz{ public: //Funcion para capturar la tacla digitada omitiendo el el Enter. static int GetKey() { int Tecla=getch(); if(Tecla==0xE0 || Tecla==0) Tecla=getch(); return Tecla; } //Método para Establecer los Colores recibe un numero de 2 digitos //el primer digito estabece el color del background de la consola. //el segundo numero establece el color de la letra de la consola. static void setColor(unsigned short color){ //Funcion para cambiar el color de la consola SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),color); } //Método que me posiciona el foco de cursor de la consola dadas 2 coordenadas. static void gotoxy(int x,int y){ //Variable Coordenada que guarda las coordenadas donde voy a imprimir. COORD conCord; conCord.X=x; conCord.Y=y; //Método posiciona el foco de cursor de la consola según coordenadas especificadas. SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),conCord); } //Método para cambiar propiedades del cursor. static void ocultaCursor(){ //Variable para controlar atributos del cursor; CONSOLE_CURSOR_INFO myCursor; myCursor.dwSize = 25; myCursor.bVisible = FALSE; //Funcion para cambiar propiedades del cursor de la consola. SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),&myCursor); } //Función que confirma la desición del Usuario de salir del programa. static bool Salir(){ system("CLS"); system("color 0F"); setColor(COLOR_CAJA_SALIR); //Dibujo las lineas horizontales de la caja. for(int i=19;i<59;i++){ gotoxy(i,15);printf("%c",LIN_HOR_SALIR); gotoxy(i,5);printf("%c",LIN_HOR_SALIR); } //Dibujo las lineas verticales de la caja. for(int i=6;i<15;i++){ gotoxy(18,i);printf("%c",LIN_VER_SALIR); gotoxy(59,i);printf("%c",LIN_VER_SALIR); } //Dibujo las esquinas de la caja gotoxy(18,5);printf("%c",ESQ_SUP_DER_SALIR); gotoxy(18,15);printf("%c",ESQ_SUP_IZQ_SALIR); gotoxy(59,5);printf("%c",ESQ_INF_DER_SALIR); gotoxy(59,15);printf("%c",ESQ_INF_IZQ_SALIR); //Dibujo el titulo de la caja. gotoxy(19,6); printf(" \n"); gotoxy(19,7); printf(" Desea salir por completo del programa? \n"); gotoxy(19,8); printf(" \n"); //Dibujo las opciones a esocger en la caja. int tecla = 0;//Tecla presionada, inicializada en 0 para primer caso. bool Opcion =true;//Variable que almacena el número de opción en el q se encuentra el Usuario. while(true){ gotoxy(32,12); //Dibujamos las opciones a seleccionar setColor(COLOR_CAJA_SALIR); if(Opcion) setColor(OPC_SELEC_SALIR); printf("SI"); gotoxy(42,12); setColor(COLOR_CAJA_SALIR); if(!Opcion) setColor(OPC_SELEC_SALIR); printf("NO"); tecla=GetKey();//Leemos la tecla digitada. //En caso que la tecla digitada sea alguna de las aceptadas como acciones en el menú if(tecla==DER||tecla==IZQ||tecla==ENT){ switch(tecla){ case DER:Opcion=false;break; case IZQ:Opcion=true;break; case ENT:system("CLS");system("color 0F");return Opcion; } } Sleep(100);//Dormimos el proceso por 100 milisegundos.(Recomendable para liberar procesador) } } //Método para dibujar la portada inicial del Programa. static void Portada(){ //Dibujo las lineas horizontales de la caja para la portada for(int i=2;i<78;i++){ if(i>9 && i<69){ setColor(0x0A); gotoxy(i,2);printf("%c",31); gotoxy(i,21);printf("%c",30); }else{ setColor(0x0D); gotoxy(i,2);printf("%c",205); gotoxy(i,21);printf("%c",205); } } //Dibujo las lineas verticales de la caja para la portada for(int i=3;i<22;i++){ if(i>4&&i<18){ setColor(0x0A); gotoxy(2,i);printf("%c",16); gotoxy(77,i);printf("%c",17); } else{ setColor(0x0D); gotoxy(2,i);printf("%c",186); gotoxy(77,i);printf("%c",186); } } //Dibujo las esquinas de la caja para la portada gotoxy(2,2);printf("%c",201); gotoxy(2,21);printf("%c",200); gotoxy(77,2);printf("%c",187); gotoxy(77,21);printf("%c",188); //Dibujo creditos de la Portada. setColor(0x0E); gotoxy(27,8);printf("Mover Objetos con Flechas C++"); gotoxy(25,12);printf("Programador: Greivin Chaves R."); gotoxy(25,18);printf("Derechos Reservados. 26/11/2013"); setColor(0x0F); printf("\n"); gotoxy(20,23);system("PAUSE"); } };
Básicamente este ejemplo cuenta con dos clases Muñeco y Área (como lógica principal). La primera (Muñeco), tiene como propiedades las coordenadas del muñeco, y como funciones moverse, ValidarMovimiento, Dibujar y Borrar, y la función de clase lógicamente es todo relacionado con el objeto, si queremos agregar más funcionalidad al muñeco, únicamente se agregan las funciones o propiedades que el mismo necesite para dicha función en esta clase, y se llaman desde la interfaz en el método principal (más adelante se encuentra esta parte). Este es el código de la clase Muñeco:
#include "stdafx.h" #include <iostream> #include "UtilidadesInterfaz.h" #define ut UtilidadesInterfaz //Definición de espacio nombre utilizado para Utilidades Interfaz. //Definiciones de skins ASCII utilizadas en el muñeco. #define Cabeza 1 //Skin utilizado para la cabeza #define ManoIzquierda 42//Skin utilizado para la mano Izquierda #define ManoDerecha 42//Skin utilizado para la mano Derecha #define Cuerpo 176//Skin utilizado para el Tronco o Cuerpo #define Piernas 19//Skin utilizado para las piernas //Definiciones Colores muñeco. #define ColPiel 0x0D //Color de la Piel del muñeco. Aplica para cabeza y manos #define ColCuerpo 0x0A//Color del cuerpo del mueñeco. #define ColPies 0x0E//Color de los pies del mueñeco. //////////////////////////////////////////////////////////////////////////////////////////////// // Clase Muneco // Crea un objeto con forma de muñeco que es el que se moveremos con las flechas. /////////////////////////////////////////////////////////////////////////////////////////////// class Muneco{ private: //Coordenadas donde se encuentra el Objeto. int x,y; public: //Constructor sin parametros del Objeto. Lo posiciona en las coordenadas 00. Muneco(){ x=0; y=0; } //Contructor con parametros del Objeto. Lo posiciona en las coordenadas XY. Muneco(int _x,int _y){ x=_x; y=_y; } //Get de la Coordenada X int CordX(){return x;} //Get de la Coordenada Y int CordY(){return y;} //Método para borrar e objeto de la Consola. Es el mismo que Mostrar pero ponemos caracteres vacíos en esa posición. void Borrar(){ //Borramos la cabeza del mueñeco ut::gotoxy(x+2,y); printf(" "); //Borramos las manos del muñeco, primero la Izq luego la derecha. ut::gotoxy(x,y+1); printf(" "); ut::gotoxy(x+4,y+1); printf(" "); //Borramos el tronco del muñeco. ut::gotoxy(x+1,y+1); printf(" "); ut::gotoxy(x+2,y+2); printf(" "); //Borramos los pies del mueñeco ut::gotoxy(x+2,y+3); printf(" "); } //Método que imprime el muñeco en las coordenadas que se encuentra. void Mostrar(){ //Imprimimos la cabeza del muñeco. ut::setColor(ColPiel); ut::gotoxy(x+2,y); printf("%c",Cabeza); //Imprimimos las manos del muñeco, primero la Izq luego la derecha. ut::gotoxy(x,y+1); printf("%c",ManoIzquierda); ut::gotoxy(x+4,y+1); printf("%c",ManoDerecha); //Imprimimos el tronco del muñeco. ut::setColor(ColCuerpo); ut::gotoxy(x+1,y+1); printf("%c%c%c",Cuerpo,Cuerpo,Cuerpo); ut::gotoxy(x+2,y+2); printf("%c",Cuerpo); //Imprimimos los pies del mueñeco ut::gotoxy(x+2,y+3); ut::setColor(ColPies); printf("%c",Piernas); } //Método para mover el objeto dentro de las coordenadas. void Mover(int px, int py){ Borrar();//Borro el objeto anterior //Muevo las coordenadas a las nuevas. x=px; y=py; //Redibujo el Objeto en las nuevas coordenadas. Mostrar(); } //Función que valida el movimiento del objeto y verifica si el nuevo movimiento a realizar no sobre pasa los limites definidos. /*TENER EN CUENTA ESTOS DOS ASPECTOS CUANDO SE VALIDAN LOS MOVIMIENTOS*/ //La coordenada X tiene el punto de referencia en la mano Izquierda del muñeco, para validar si no sobrepaso el Limite Derecho debe sumársele 4, dado a que hacia de la derecha de la mano Izquierda hay 4 caracteres más. //La coordenada Y tiene el punto de referencia en la Cabeza del muñeco, para validar si no sobrepaso el Limite Inferior debe sumársele 3, dado a que para abajo de la cabeza hay 3 caracteres más. bool MovimientoValido(int px,int py,int limIzq,int limDer,int limSup,int limInf){ //En caso que sobrepase el Limite Superior if(py<=limSup)return false; //En caso que sobrepase el Limte Inferior if(py+3>=limInf)return false; //En caso que sobrepase el Limite Izquierda if(px<=limIzq)return false; //En caso que sobre pase el Limite Derecha if(px+4>=limDer)return false; } };
La segunda clase (Área), dibuja el Área en la que puede movilizar el muñeco, define los limites de la misma. En este caso las propiedades serian los 4 limites y como funciones tenemos dibujarArea únicamente. Este es el código de la clase:
#include "stdafx.h" #include <iostream> #include "Muneco.cpp" //Definiciones de Skins Ascii utilizados definir el área donde se mueve el Objeto. #define LIM_HOR 205 //Caracter Limite Horizontal #define LIM_VER 186 //Caracter Limite Vertical #define ESQ_SUP_IZQ 201 //Caracter Esquina Superior Izquierda #define ESQ_SUP_DER 187 //Caracter Esquina Superior Derecha #define ESQ_INF_IZQ 200 //Caracter Esquina Inferior Izquierda #define ESQ_INF_DER 188 //Caracter Esquina Inferior Derecha //////////////////////////////////////////////////////////////////////////////////////////////// // Clase Área Juego // Crea un objeto área la cual define el área donde puede moverse el Objeto (Muñeco) /////////////////////////////////////////////////////////////////////////////////////////////// class AreaJuego{ private: //Variables que almacena las coordenadas limites hasta donde puede moverse el objeto. int LIM_SUP; //Coordenada Limite Inferior int LIM_INF; //Coordenada Limite Superior int LIM_IZQ; //Coordenada Limite Izquierda int LIM_DER; //Coordenada Limite Derecha public: //Contructor por defecto del Área, en este caso crea un área cuadrada AreaJuego(){ LIM_SUP=2; LIM_INF=20; LIM_IZQ=2; LIM_DER=20; } //Constructor con parametros del área, para poder crear un área segun las coordenadas deseadas //Debe tenerse cuidado que las coordenadas no sean mayores al alto o ancho de la cosola, sino puede que no funcione correctamente. AreaJuego(int limSup,int limInf,int limIzq, int limDer){ LIM_SUP=limSup; LIM_INF=limInf; LIM_IZQ=limIzq; LIM_DER=limDer; } //métodos GETS Y SETS para las distintas coordenadas, en caso de cambiar una coordenada debe redibujarse el Área //que el cambio pueda visualizarse. int getLimSup(){return LIM_SUP;} int getLimInf(){return LIM_INF;} int getLimIzq(){return LIM_IZQ;} int getLimDer(){return LIM_DER;} void setLimSup(int limSup){LIM_SUP=limSup;} void setLimInf(int limInf){LIM_INF=limInf;} void setLimIzq(int limIzq){LIM_IZQ=limIzq;} void setLimDer(int limDer){LIM_DER=limDer;} //Método para dibujar el Área donde se mueve el Objeto, se dibuja segun las Coordenadas del Área. void limites(){ ut::setColor(0x02); //Dibuja los limites Horizontales for(int i=LIM_IZQ;i<LIM_DER;i++){ ut::gotoxy(i,LIM_SUP);printf("%c",LIM_HOR); ut::gotoxy(i,LIM_INF);printf("%c",LIM_HOR); } //Dibuja los limites Verticales for(int i=LIM_SUP;i<LIM_INF;i++){ ut::gotoxy(LIM_IZQ,i);printf("%c",LIM_VER); ut::gotoxy(LIM_DER,i);printf("%c",LIM_VER); } //Dibuja las Esquinas del Área. ut::gotoxy(LIM_IZQ,LIM_SUP);printf("%c",ESQ_SUP_IZQ); ut::gotoxy(LIM_IZQ,LIM_INF);printf("%c",ESQ_INF_IZQ); ut::gotoxy(LIM_DER,LIM_SUP);printf("%c",ESQ_SUP_DER); ut::gotoxy(LIM_DER,LIM_INF);printf("%c",ESQ_INF_DER); ut::setColor(0x0F); } };
Además de esto como clase no primordial de la lógica del ejemplo, pero necesaria para su funcionamiento es la clase Interfaz en esta unimos la clase Área y Muñeco, y realizamos el bucle principal de vida del juego, y los procesos de solicitar e informar al usuario lo que sucede en el juego. Este es el código de la clase:
#include "stdafx.h" #include "AreaJuego.cpp" using namespace std; //////////////////////////////////////////////////////////////////////////////////////////////// // Clase Interfaz Objeto Flechas // Crea un objeto con forma de muñeco que es el que se moveremos con las flechas. /////////////////////////////////////////////////////////////////////////////////////////////// class InterfazObjetoFlechas{ private: Muneco * obj; AreaJuego *area; //Método que inicia los Elementos (Área,Objeto,Info Esc) void IniciaElementos(){ area->limites();//Dibujamos los limites del Área donde se mueve el Objeto. obj->Mostrar();//Mostramos el Objeto ut::gotoxy(2,22); cout<<"Esc/Salir"; } public: //Constructor por defecto de la clase InterfazObjetoFlechas (){ //Creamos una Área de Juego donde se va a jugar. area = new AreaJuego(2,20,2,77); //Creamos un Objeto Nuevo con las coordenadas Iniciales en el Centro del Área de la Plataforma. obj = new Muneco((area->getLimDer()-area->getLimIzq())/2,(area->getLimInf()-area->getLimSup())/2); } //Destructor de la Clase ~InterfazObjetoFlechas (){ delete obj;//Destruyo el Objeto } //Método principal donde se mueve el Objeto dentro del Área. void metodoPrincipal(){ ut::Portada();//Imprimimos la portada de la Aplicación ut::ocultaCursor();//Ocultamos el cursor system("CLS"); IniciaElementos(); bool Salir = false; //Bandera que me indica si la aplicación debe cerrarse. int tecla =0; //Variable para almacenar la Tecla digitada. //Inicializamos los valores de las Coordenadas digitadas por el Usuario. int x=obj->CordX(); int y=obj->CordY(); while(!Salir){ //Detectamos si alguna tecla es precionada mediante funcion kbhit de Conio.h if(kbhit()){ //Guardamos la tecla presionada a la variable tecla. tecla=getch(); if(tecla==IZQ || tecla==DER || tecla == ARR ||tecla== ABJ){ //Casos a evaluar con la tecla presionada para mover el objeto. //En caso que el objeto tenga mas funciones que moverse deben agregarse los case respectivos switch(tecla){ case IZQ:x--;break; case DER:x++;break; case ARR:y--;break; case ABJ:y++;break; } //Validamos que que no se sobrepase los limites. if(obj->MovimientoValido(x,y,area->getLimIzq(),area->getLimDer(),area->getLimSup(),area->getLimInf())){ //En caso que no haya pasado los limites movemos el objeto. obj->Mover(x,y); }else{ //En caso que el movimiento no sea valido pongo el X y Y en las pociones anteriores antes del movimiento. x=obj->CordX(); y=obj->CordY(); } }else{//En caso que la tecla presionada sea distinta a alguna de las Flechas. if(tecla==ESC){ //Llamamos al metodo de Salir Salir=ut::Salir(); if(!Salir){//En caso que no desee Salir de la Aplicación redibujamos los limites y el Objeto. IniciaElementos(); } } } } //Damos un tiempo de espera para volver a ejecutar el ciclo.Recomendado para liberar procesador. Sleep(30); } } }; int _tmain(int argc, _TCHAR* argv[]) { InterfazObjetoFlechas * nueva = new InterfazObjetoFlechas();//Nuevo Objeto de Interfaz nueva->metodoPrincipal();//Llamamos al metodo principal. delete nueva;//Destruimos el objeto Interfaz. return 0; }
Ahora lo único que necesitamos es un poquito de creatividad y dedicación para crear un juego simple como el de este ejemplo, Pronto adjuntare ejemplos de juegos un poco más elaborados. Acá dejo el código Fuente del ejemplo completo: Descargar
pasa el archivo
ResponderEliminar