D esarrollo de juegos

Aula Macedonia



Sistemas de control e interacción


Artículo realizado por
Juan Antonio Medina "mICRO"





Como programar multiples controles y respuestas para varios jugadores simultaneos

Muchas veces en nuestros juegos deberemos de controlar simultáneamente el control de varios jugadores que pueden a la vez usar varios medios, por ejemplo un jugador podría usar teclas + joystick y otro solo teclas, en algunos casos esto se puede volver demasiado complicado, para clarificar este tema este articulo usara el método del CONTROL y la RESPUESTA que nos permitirá fácilmente tener el control de varios jugadores y los medios que usen además también nos permitirá un fácil control de lo que quiere hacer en jugador sin importar que lo haga con teclado o con Joystick. Este articulo es a nivel básico y solo quiere ser una aclaración en el tema, se recomienda al lector del mismo que implemente su propio sistema de control y respuesta mas adecuado para su juego ya que este puede que sea un poco básico.

CONTROL

En control es donde definiremos como va el jugador a controlar la acción en el juego, indicaremos si va a usar joystick o teclado o las teclas que usara entre otras cosas, esta información será necesaria para luego generar una respuesta acorde con el control dado.

Para ello introduciremos la información en un struct de c:

// PRINCIPIO DE CODIGO EN C
// Estructura para alojar la definicion del control de un Jugador
typedef struct {

unsigned short int joystick; // El Numero del Joystick, 0 si no usa
unsigned short int teclado; // Distinto de 0 si usa Teclado
unsigned short int ar,ab,iz,de; // Codigos de las teclas para la direccion
unsigned short int f1,f2; // Codigos de las teclas para el boton 1 y 2

} ___control;

//FIN DE CODIGO EN C

Un ejemplo podría ser este en el que se asignan los valores para dos controles:

//PRINCIPIO DE CODIGO EN C

___control control1;
___control control2;

control1.joystick = 1; // Primer Joystick que este conectado en el sistema
control1.teclado = 1; // Tambien usaremos el teclado
control1.ar = VK_UP; // Tecla de arriba (WIN 32 VK_KEYCODES)
control1.ab = VK_DOWN; // Tecla de abajo (WIN 32 VK_KEYCODES)
control1.iz = VK_LEFT; // Tecla de izquierda (WIN 32 VK_KEYCODES)
control1.de = VK_RIGHT; // Tecla de derecha (WIN 32 VK_KEYCODES)
control1.f1 = VK_ALT; // Tecla para el Boton 1 (WIN 32 VK_KEYCODES)
control1.f2 = VK_RSHIFT; // Tecla para el Boton 2 (WIN 32 VK_KEYCODES)

control2.joystick = 0; // No usaremos Joystick
control2.teclado = 1; // Usaremos el teclado
control2.ar = VK_W; // Tecla de arriba (WIN 32 VK_KEYCODES)
control2.ab = VK_S; // Tecla de abajo (WIN 32 VK_KEYCODES)
control2.iz = VK_A; // Tecla de izquierda (WIN 32 VK_KEYCODES)
control2.de = VK_D; // Tecla de derecha (WIN 32 VK_KEYCODES)
control2.f1 = VK_F; // Tecla para el Boton 1 (WIN 32 VK_KEYCODES)
control2.f2 = VK_G; // Tecla para el Boton 2 (WIN 32 VK_KEYCODES)

//FIN DE CODIGO EN C

 

RESPUESTA

La respuesta es el resultado de procesar el control del jugador, en ella reflejaremos que es lo que el jugador quiere hacer independientemente de con que lo haga, es lo que tendremos que mirar para reflejar en el juego, de esta manera conseguiremos que el control del juego sea igual con teclado o joystick por ejemplo, para esto también usaremos un struct de c:

//PRINCIPIO DE CODIGO EN C
//Estructura para reflejar la respuesta segun el control
typedef struct {

unsigned short int iz,de,ar,ab; // Indica lo que quiere el jugador
// Botones de Fuego, sera distinto de 0 mientras el jugador tenga pulsado el boton de fuego o sea, es como un autofire
unsigned short int f1,f2;
long xmax,xmin,ymax,ymin; // Valores max en los 4 ejes del Joystick
long xvalue,yvalue; // Valores actules del joystick
unsigned short int act1,act2; // Estos botones de disparo se activa solo cuando el
// jugador pulsa el boton de disparo y no se vuelve
// a activar hasta que lo pulse de nuevo, lo que
// quiere decir que no usara autofire
unsigned short int aux1,aux2; // Variables temporales de uso interno
} ___respuesta;
//FIN DE CODIGO EN C

Diferencias entre f1,f2 y act1,act2:

f1 y f2 reflejan simplemente cuando esta el botón pulsado o sea, cuando el jugador lo pulsa, seguirá valiendo distinto de 0 hasta que lo suelte, en cambio act1 y act2 valdrán distinto de 0 solo una vez cuando se pulse el botón y luego valdrá 0 hasta que el jugador lo suelte y lo vuelva a pulsar, para que esto, pues sencillo en algunos juegos, como pueden ser de naves, queremos que el jugador tenga autofire o no, pues en cada caso usaremos una o las otras variables, para que el jugador tenga que dejar pulsado la tecla de fuego o l tenga que ir pulsado y soltando cada vez.

Antes de seguir deberemos saber leer esos controles (teclado y joystick), para ello vamos a usar funciones estándar de WIN 32 y no direct-x para hacerlo a si mas fácil y a la vez para que se puedan usar en cualquier proyecto incluso los que no usan direct-x.

COMO LEER TODO EL TECLADO EN WIN 32

Win 32 provee una fácil manera de leer en cualquier momento todo el estado del teclado, con la función del api:

BOOL GetKeyboardState(PBYTE lpKeyState);

lpKeyState

Puntero a un vector de bytes de 256 de tamaño que recibirá el estado de las teclas para cada una de las "virtual keys".

Devuelve no cero si funciona o cero si no.

Para leer el teclado solamente tendremos que crear un array para contener el resultado de esa función y un par de funciones para usarlo.

//FUNCIONES DE TECLADO WIN-32
//Aqui declaramos el Vector donde alojaremos la pulsacion de las teclas
unsigned char teclapulsada[256];

//Simplemente inicialza el vector, debe de ser llamada al principio del juego
void InitKey()
{

unsigned int z;
for(z=0;z<=255;z++) teclapulsada[z]=0;
}

//Actualiza el vector, debe ser llamada una vez por ciclo de juego
void leeteclas()
{

unsigned int z;
GetKeyboardState(teclapulsada);
for(z=0;z<=255;z++)
{
teclapulsada[z]=teclapulsada[z]&0x80; // &0x80 pulsado
}
}

//Devuelve el codigo de la primrera tecla pulsada, 0 si ninguna esta pulsada
unsigned short int unatecla()
{
unsigned short int z;
for(z=1;z<127;z++)
{
if(teclapulsada[z])
return z;
}
return 0;
}

 

 

COMO USAR EL JOYSTICK EN WIN-32

Usar el JOYSTICK en win-32 es casi tan fácil como usar el teclado para ello en el api hay las siguientes estructuras de datos y funciones:

//Estructura definida en el WIN 32 API para sacar info de un joystick

typedef struct joycaps_tag {
UINT wMid; // ID del fabricante
UINT wPid; // ID del producto
char szPname[MAXPNAMELEN]; // Nombre del Joystick
UINT wXmin; // Valor minimo del eje X
UINT wXmax; // Valor maximo del eje X
UINT wYmin; // Valor minimo del eje Y
UINT wYmax; // Valor maximo del ejeY
UINT wZmin; // Valor minimo del eje Z
UINT wZmax; // Valor maximo del eje Z
UINT wNumButtons; // Numero de Botones

UINT wPeriodMin; // Intervalos para el Joystick Capturado

UINT wPeriodMax; // Intervalos para el Joystick Capturado

} JOYCAPS;

//Estructura definida en el WIN 32 API para sacar la posicion de un Joystick

typedef struct joyinfo_tag {
UINT wXpos; // Posicion en el eje X
UINT wYpos; // Posicion en el eje Y
UINT wZpos; // Posicion en el eje Z
UINT wButtons; // Estado actual de los botones
// JOY_BUTTON1 para el boton 1
// JOY_BUTTON2 para el boton 2
// JOY_BUTTON3 para el boton 3
// JOY_BUTTON4 para el boton 4
} JOYINFO;

 

UINT joyGetNumDevs(VOID);

Esta función devuelve el numero de Joystick conectados al sistema 0 si no hay ninguno

MMRESULT joyGetDevCaps(
UINT
uJoyID,
LPJOYCAPS
pjc,
UINT
cbjc
);

uJoyID

El ID del joystick que queremos obtener sus capacidades.

pjc

Un puntero a una estructura JOYCAPS que será rellenada por esta función

cbjc

El tamaño del pjc

Esta función devuelve distinto de 0 si funciona y rellena la información pedida en la estructura JOYCAPS dada.

MMRESULT joyGetPos(
UINT
uJoyID,
LPJOYINFO
pji
);

uJoyID

El ID del Joystic que queremos conocer su posición actual

pji

puntero a una estructura JOYINFO que será rellenada por esta función.

Esta función devuelve la posición de un joystick y valor distinto de 0 si todo va bien.

 

Para manejar todo esto utilizaremos las siguientes funcione y estructuras:

//COMIENZO DE CODIGO EN C
UINT NJOYSTICKS; //Nº de joysticks conectados o no
UINT NJOYSTICKSPLUG; //Nº de joysticks conectados
JOYCAPS joycaps; //Capacidades del joystick

//Valores para determinar los movimientos del joystick
//Este structura la usaremos para crear un array con los joystick que hay en
//en el sistema independientemente de cuales sea sus ID
typedef struct {

signed int JDER; //Cordenada a partir de la cual es movimiento a la derecha
signed int JIZQ; //Cordenada a partir de la cual es movimiento a la izquierda
signed int JARR; //Cordenada a partir de la cual es movimiento a arriba
signed int JABA; //Cordenada a partir de la cual es movimiento a abajo
UINT JOY_ID; //ID del Joystick para el sistema
unsigned short int Ajustado;//Si esta Ajustado
long xmax,xmin,ymax,ymin; //Maximo del Joystick
} ___joyvalue;

//Array con todos los joystick conectados
//o sea cuando en el control decimos que el joystick es igual a 1
//nos referimos a que es el primero conectado independientemente de su ID
//si decimos que es el 2 es el segundo etc, si desconectamos algun,
//de esta manera es mas facil cuando en juego se quiere usar joystick (el primero)
//sin importar cual sea
#define maxjoys 10

___joyvalue joyvalue[maxjoys];

//Informacion para recoger la posicion de un joystick
JOYINFO joy;

//Busca Joysticks en el sistema incializa datos
//calcula a partir de donde hay movimiento
//y devuelve cuantos hay conectados
unsigned short int InitJoy()
{
NJOYSTICKS=joyGetNumDevs(); //Esta funcion devuelve el nº de joyticks
//que el sistema soporta, no los conectados
NJOYSTICKSPLUG=0;

JOYINFO pji;
UINT c;

//Para saber cuales estan conectados miramos si la funcion joyGetPos

//Devuelve JOYERR_NOERROR
for (c=0;c<NJOYSTICKS;c++) if (joyGetPos(c,&pji)==JOYERR_NOERROR){

//Guardamos su ID y cogemos sus caps
joyvalue[NJOYSTICKSPLUG+1].JOY_ID=c;
joyvalue[NJOYSTICKSPLUG+1].Ajustado=1;
joyGetDevCaps (joyvalue[NJOYSTICKSPLUG+1].JOY_ID,&joycaps,sizeof(joycaps));

//Aqui miramos la estructura joycaps para calcular los maximos y los
//minimos de los potenciometros, y dividiendo averiguamos a partir
//de que cordenada consideramos que es un movimiento
//que sera cuando la palanca pase la mitad de la distancia de uno de los
//ejes y su maximo o sea, cuando movamos el joystick a la derecha
//se considerarar derecha cuando la palanca pase la mitada entre el
//centro y el maximo a la derecha.
signed int mx=(joycaps.wXmax+joycaps.wXmin)/2;
signed int my=(joycaps.wYmax+joycaps.wYmin)/2;
joyvalue[NJOYSTICKSPLUG+1].JDER=(mx+joycaps.wXmax)/2;
joyvalue[NJOYSTICKSPLUG+1].JIZQ=(mx+joycaps.wXmin)/2;
joyvalue[NJOYSTICKSPLUG+1].JARR=(my+joycaps.wYmin)/2;
joyvalue[NJOYSTICKSPLUG+1].JABA=(my+joycaps.wYmax)/2;

//guardamos los valores maximos
joyvalue[NJOYSTICKSPLUG+1].xmax=joycaps.wXmax;
joyvalue[NJOYSTICKSPLUG+1].xmin=joycaps.wXmin;
joyvalue[NJOYSTICKSPLUG+1].ymax=joycaps.wYmax;
joyvalue[NJOYSTICKSPLUG+1].ymin=joycaps.wYmin;

//hay un joystick mas conectado
NJOYSTICKSPLUG++;
}
else joyvalue[NJOYSTICKSPLUG+1].Ajustado=0;
return (unsigned short int) (NJOYSTICKSPLUG)
}

//FIN DE CODIGO EN C

COMO TRANSFORMAR UN CONTROL EN UNA RESPUESTA

La idea es fácil, crearemos una función que se encarga de esa tarea, que procese el teclado y el joystick dado, el joystick que nosotros tengamos asignados en control y que al final rellene la estructura respuesta. La tendremos que llamar para cada control y respuesta que deseemos usar, además antes de usarla tendremos que llamar a las funciones de inicialización de joystick y teclado, también una vez por ciclo a la función de actualización del teclado.

//COMIENZO DE CODIGO EN C

//Esta funcion transforma un control en respuesta y devuelve distinto de 0
//si ha habado alguna respuesta, 0 si no
unsigned short int control(___control &control,___respuesta &respuesta)
{
//variables para el uso enla funcion
unsigned short int pulsa=0,izqu=0,dere=0,arri=0,abaj=0,fue1=0,fue2=0;
//Si usamos teclado
if (control.teclado){
arri=teclapulsada[control.ar];
abaj=teclapulsada[control.ab];
izqu=teclapulsada[control.iz];
dere=teclapulsada[control.de];
fue1=teclapulsada[control.f1];
fue2=teclapulsada[control.f2];
}

//Si usamos joystick y es valido
if( ((control.joystick)!=0)&&(joyvalue[control.joystick].Ajustado==1) ){

//cogemos los valores del joystick
joyGetPos (joyvalue[control.joystick].JOY_ID,&joy);

//actualizamos los valores, usamos OR "|"
//en los joyvalue teniamos guardados en que puntos de los ejes
//se considera el movimiento para ese lado para ello en el
//control teniamos especificado el joystick en el sistema
//tenemos el array joyvalue con los valores que queremos
izqu=((signed int)joy.wXpos<=joyvalue[control.joystick].JIZQ)|izqu;
dere=((signed int)joy.wXpos>=joyvalue[control.joystick].JDER)|dere;
arri=((signed int)joy.wYpos<=joyvalue[control.joystick].JARR)|arri;
abaj=((signed int)joy.wYpos>=joyvalue[control.joystick].JABA)|abaj;
fue1=((signed int)(joy.wButtons&JOY_BUTTON1))|fue1;
fue2=((signed int)(joy.wButtons&JOY_BUTTON2))|fue2;

//analogico
(respuesta.xmax)=joyvalue[control.joystick].xmax;
(respuesta.xmin)=joyvalue[control.joystick].xmin;
(respuesta.ymax)=joyvalue[control.joystick].ymax;
(respuesta.ymin)=joyvalue[control.joystick].ymin;
(respuesta.xvalue)=joy.wXpos;
(respuesta.yvalue)=joy.wYpos;

}

//Para retornar Si habia alguna respuesta
if(izqu||dere||arri||abaj||fue1||fue2)

pulsa=1;
else
pulsa=0;

//parte especial que controla las acciones que se hacen con conbinaciones
//de los botones de juego
(respuesta.iz)=izqu;
(respuesta.de)=dere;
(respuesta.ar)=arri;
(respuesta.ab)=abaj;
(respuesta.f1)=fue1;
(respuesta.f2)=fue2;

// "act1": se activa al pulsar el boton 1 despues de no haberlo pulsado
// aux1 se usa para saber si antes estaba pulsado o no
(respuesta.act1)=0;
if(fue1==0)

(respuesta.aux1)=1;
else
{ if(respuesta.aux1)
{
(respuesta.aux1)=0;
(respuesta.act1)=1;
}
}

// "act2": se activa al pulsar el boton 2 despues de no haberlo pulsado
// aux2 se usa para saber si antes estaba pulsado o no
(respuesta.act2)=0;
if(fue2==0)

(respuesta.aux2)=1;
else
{ if(respuesta.aux2)
{
(respuesta.aux2)=0;
(respuesta.act2)=1;
}
}

//retornamos si hay respuesta o no
return pulsa;

}

ÚLTIMA REVISIÓN EN JULIO DE 1999


[Desarrollo de Videojuegos]
[Sistemas de control e interacción]


DESARROLLO DE VIDEOJUEGOS
X
MACEDONIA Magazine