Campo de estrellas 3D En los anteriores tutoriales Campo de estrellas 2D y Midiendo tiempos y optimizando Campo de estrellas 2D vimos como programar y optimizar un efecto de campo de estrellas en dos dimensiones, vamos ahora a hacer un sencillo efecto de campo de estrellas 3D. En este efecto vamos a simular que viajamos en la cabina de una nave espacial y avanzamos entre las estrellas. Cada una de las estrellas va a tener una posición en el eje x, en el eje y en el eje z (profundidad), lo que haremos es mover la posición de cada estrella en el eje z hasta llegar a 0 y volveremos a empezar. Para pintar en la pantalla, debemos calcular la posición en 2D de una estrella en 3D, que es realmente sencillo: x2D = x3D / z3D e y2D = y3D / z3D. Nuestra pantalla en modo 0 es de 160x200, pero en 3D vamos a ampliar este rango en la x e y de cada estrella, ya que en caso contrario todas estarían en el centro de la pantalla hasta que la z fuera muy cercana a 0. Este sería el código completo 3Dstars01.c (para sdcc): //////////////////////////////////////////////////////////////////////// // 3Dstars01.c // 3D Star Field // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> #include <string.h> unsigned char GetMode0PixelColorByte(unsigned char nColor, unsigned char nPixel) { unsigned char nByte = 0; if(nPixel == 0) { nByte &= 85; if(nColor & 1) nByte |= 128; if(nColor & 2) nByte |= 8; if(nColor & 4) nByte |= 32; if(nColor & 8) nByte |= 2; } else { nByte &= 170; if(nColor & 1) nByte |= 64; if(nColor & 2) nByte |= 4; if(nColor & 4) nByte |= 16; if(nColor & 8) nByte |= 1; } return nByte; } unsigned char *GetLineAddress(unsigned char nLine) { return (unsigned char *)0xC000 + ((nLine / 8) * 80) + ((nLine % 8) * 2048); } //////////////////////////////////////////////////////////////////////// unsigned char char1,char2,char3,char4; unsigned int GetTime() { unsigned int nTime = 0; __asm CALL #0xBD0D ;KL TIME PLEASE PUSH HL POP DE LD HL, #_char3 LD (HL), D LD HL, #_char4 LD (HL), E __endasm; nTime = (char3 << 8) + char4; return nTime; } //////////////////////////////////////////////////////////////////////// struct _tStar { int nX; int nY; int nZ; int nVelocity; unsigned char nColor; unsigned char *pLastAddress; }; #define STARS_NUM 25 struct _tStar aStars[STARS_NUM]; #define MAX_Z 64 #define ORIGIN_X 80 #define ORIGIN_Y 100 void main() { unsigned int nFPS = 0; unsigned int nTimeLast = 0; unsigned char nStar = 0; struct _tStar *pStar = NULL; int nScreenX = 0; int nScreenY = 0; memset(aStars, 0, sizeof(aStars)); //SCR_SET_MODE 0 __asm ld a, #0 call #0xBC0E __endasm; //PALETE __asm ld a, #0 ld b, #0 ;black ld c, b call #0xBC32 ;SCR SET INK ld a, #1 ld b, #12 ;Yellow ld c, b call #0xBC32 ;SCR SET INK ld a, #2 ld b, #25 ;Pastel Yellow ld c, b call #0xBC32 ;SCR SET INK ld a, #3 ld b, #24 ;Bright Yellow ld c, b call #0xBC32 ;SCR SET INK __endasm; //SCR SET BORDER 0 __asm ld b, #0 ;black ld c, b call #0xBC38 __endasm; //Init for(nStar = 0; nStar < STARS_NUM; nStar++) { aStars[nStar].nX = rand() % 3200 - 1600; aStars[nStar].nY = rand() % 4000 - 2000; aStars[nStar].nZ = rand() % MAX_Z; aStars[nStar].nColor = GetMode0PixelColorByte(1 + rand() % 3, 0); aStars[nStar].nVelocity = 1 + rand() % 5; aStars[nStar].pLastAddress = NULL; } nTimeLast = GetTime(); while(1) { for(nStar = 0; nStar < STARS_NUM; nStar++) { pStar = &aStars[nStar]; //delete star if(pStar->pLastAddress != NULL) *pStar->pLastAddress = 0; //move star pStar->nZ -= pStar->nVelocity; //paint star nScreenX = ORIGIN_X + pStar->nX / (pStar->nZ > 0 ? pStar->nZ : 1); nScreenY = ORIGIN_Y + pStar->nY / (pStar->nZ > 0 ? pStar->nZ : 1); if(nScreenX >= 0 && nScreenX < 160 && nScreenY >= 0 && nScreenY < 200) { pStar->pLastAddress = GetLineAddress(nScreenY) + (nScreenX / 2); *pStar->pLastAddress = pStar->nColor; } else pStar->nZ = MAX_Z; if(pStar->nZ <= 0) pStar->nZ = MAX_Z; //printf("%d,%d (%d,%d,%d)\n\r", nScreenX, nScreenY, pStar->nX, pStar->nY, pStar->nZ); } nFPS++; if(GetTime() - nTimeLast >= 300) { //TXT SET CURSOR 0,0 __asm ld h, #1 ld l, #1 call #0xBB75 __endasm; printf("%u ", nFPS); nTimeLast = GetTime(); nFPS = 0; } } } //////////////////////////////////////////////////////////////////////// Si lo ejecutamos en un emulador veríamos algo similar a esto (mucho más fluido en el emulador): Como se puede ver, conseguimos unos 24 frames por segundo con 25 estrellas... la verdad es que se queda un poco corto. Como ya vimos en el tutorial anterior, lo mejor es precalcular todo lo que podamos. En este caso podemos precalcular absolutamente todo, cada estrella como máximo tendría 64 posiciones, podemos precalcularlas todas para evitar hacer las operaciones y dedicarnos únicamente a borrar/pintar. Así quedaría el código modificado para precalcular todo: //////////////////////////////////////////////////////////////////////// // 3Dstars02.c // 3D Star Field // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> #include <string.h> unsigned char GetMode0PixelColorByte(unsigned char nColor, unsigned char nPixel) { unsigned char nByte = 0; if(nPixel == 0) { nByte &= 85; if(nColor & 1) nByte |= 128; if(nColor & 2) nByte |= 8; if(nColor & 4) nByte |= 32; if(nColor & 8) nByte |= 2; } else { nByte &= 170; if(nColor & 1) nByte |= 64; if(nColor & 2) nByte |= 4; if(nColor & 4) nByte |= 16; if(nColor & 8) nByte |= 1; } return nByte; } unsigned char *GetLineAddress(unsigned char nLine) { return (unsigned char *)0xC000 + ((nLine / 8) * 80) + ((nLine % 8) * 2048); } //////////////////////////////////////////////////////////////////////// unsigned char char1,char2,char3,char4; unsigned int GetTime() { unsigned int nTime = 0; __asm CALL #0xBD0D ;KL TIME PLEASE PUSH HL POP DE LD HL, #_char3 LD (HL), D LD HL, #_char4 LD (HL), E __endasm; nTime = (char3 << 8) + char4; return nTime; } //////////////////////////////////////////////////////////////////////// #define MAX_Z 64 #define ORIGIN_X 80 #define ORIGIN_Y 100 struct _tStar { unsigned char nColor; unsigned char nActualAddress; unsigned char *aAddress[MAX_Z + 1]; }; #define STARS_NUM 25 struct _tStar aStars[STARS_NUM]; void main() { unsigned int nFPS = 0; unsigned int nTimeLast = 0; unsigned char nStar = 0; unsigned char nAddress = 0; struct _tStar *pStar = NULL; int nScreenX = 0; int nScreenY = 0; memset(aStars, 0, sizeof(aStars)); //SCR_SET_MODE 0 __asm ld a, #0 call #0xBC0E __endasm; //PALETE __asm ld a, #0 ld b, #0 ;black ld c, b call #0xBC32 ;SCR SET INK ld a, #1 ld b, #12 ;Yellow ld c, b call #0xBC32 ;SCR SET INK ld a, #2 ld b, #25 ;Pastel Yellow ld c, b call #0xBC32 ;SCR SET INK ld a, #3 ld b, #24 ;Bright Yellow ld c, b call #0xBC32 ;SCR SET INK __endasm; //SCR SET BORDER 0 __asm ld b, #0 ;black ld c, b call #0xBC38 __endasm; //Init memset(aStars, 0, sizeof(aStars)); for(nStar = 0; nStar < STARS_NUM; nStar++) { int nX = rand() % 3200 - 1600; int nY = rand() % 4000 - 2000; int nZ = MAX_Z; int nVelocity = 1 + rand() % 5; pStar = &aStars[nStar]; pStar->nColor = GetMode0PixelColorByte(1 + rand() % 3, 0); for(nAddress = 0; nAddress < MAX_Z; nAddress++) { nScreenX = ORIGIN_X + nX / (nZ > 0 ? nZ : 1); nScreenY = ORIGIN_Y + nY / (nZ > 0 ? nZ : 1); if(nScreenX >= 0 && nScreenX < 160 && nScreenY >= 0 && nScreenY < 200) { pStar->aAddress[nAddress] = GetLineAddress(nScreenY) + (nScreenX / 2); } else { break; } nZ -= nVelocity; if(nZ < 0) break; } pStar->nActualAddress = rand() % nAddress; } nTimeLast = GetTime(); while(1) { for(nStar = 0; nStar < STARS_NUM; nStar++) { pStar = &aStars[nStar]; //delete star *pStar->aAddress[pStar->nActualAddress] = 0; //move star pStar->nActualAddress++; if(pStar->aAddress[pStar->nActualAddress] == NULL) pStar->nActualAddress = 0; //paint star *pStar->aAddress[pStar->nActualAddress] = pStar->nColor; } nFPS++; if(GetTime() - nTimeLast >= 300) { //TXT SET CURSOR 0,0 __asm ld h, #1 ld l, #1 call #0xBB75 __endasm; printf("%u ", nFPS); nTimeLast = GetTime(); nFPS = 0; } } } //////////////////////////////////////////////////////////////////////// Con estas optimizaciones, pasaríamos de 24 frames por segundo a 81... Como 25 estrellas son pocas vamos a generar una versión final del código, eliminando el calculo y carteles de frames por segundo y aumentando el número de estrellas de 25 a 125, para que el efecto quede más bonito. Este sería el código fuente final: //////////////////////////////////////////////////////////////////////// // 3Dstars03.c // 3D Star Field // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> #include <string.h> unsigned char GetMode0PixelColorByte(unsigned char nColor, unsigned char nPixel) { unsigned char nByte = 0; if(nPixel == 0) { nByte &= 85; if(nColor & 1) nByte |= 128; if(nColor & 2) nByte |= 8; if(nColor & 4) nByte |= 32; if(nColor & 8) nByte |= 2; } else { nByte &= 170; if(nColor & 1) nByte |= 64; if(nColor & 2) nByte |= 4; if(nColor & 4) nByte |= 16; if(nColor & 8) nByte |= 1; } return nByte; } unsigned char *GetLineAddress(unsigned char nLine) { return (unsigned char *)0xC000 + ((nLine / 8) * 80) + ((nLine % 8) * 2048); } #define MAX_Z 64 #define ORIGIN_X 80 #define ORIGIN_Y 100 struct _tStar { unsigned char nColor; unsigned char nActualAddress; unsigned char *aAddress[MAX_Z + 1]; }; #define STARS_NUM 125 struct _tStar aStars[STARS_NUM]; void main() { unsigned char nStar = 0; unsigned char nAddress = 0; struct _tStar *pStar = NULL; int nScreenX = 0; int nScreenY = 0; memset(aStars, 0, sizeof(aStars)); //SCR_SET_MODE 0 __asm ld a, #0 call #0xBC0E __endasm; //PALETE __asm ld a, #0 ld b, #0 ;black ld c, b call #0xBC32 ;SCR SET INK ld a, #1 ld b, #12 ;Yellow ld c, b call #0xBC32 ;SCR SET INK ld a, #2 ld b, #25 ;Pastel Yellow ld c, b call #0xBC32 ;SCR SET INK ld a, #3 ld b, #24 ;Bright Yellow ld c, b call #0xBC32 ;SCR SET INK __endasm; //SCR SET BORDER 0 __asm ld b, #0 ;black ld c, b call #0xBC38 __endasm; //Init memset(aStars, 0, sizeof(aStars)); for(nStar = 0; nStar < STARS_NUM; nStar++) { int nX = rand() % 3200 - 1600; int nY = rand() % 4000 - 2000; int nZ = MAX_Z; int nVelocity = 1 + rand() % 5; pStar = &aStars[nStar]; pStar->nColor = GetMode0PixelColorByte(1 + rand() % 3, 0); for(nAddress = 0; nAddress < MAX_Z; nAddress++) { nScreenX = ORIGIN_X + nX / (nZ > 0 ? nZ : 1); nScreenY = ORIGIN_Y + nY / (nZ > 0 ? nZ : 1); if(nScreenX >= 0 && nScreenX < 160 && nScreenY >= 0 && nScreenY < 200) { pStar->aAddress[nAddress] = GetLineAddress(nScreenY) + (nScreenX / 2); } else { break; } nZ -= nVelocity; if(nZ < 0) break; } pStar->nActualAddress = rand() % nAddress; } while(1) { for(nStar = 0; nStar < STARS_NUM; nStar++) { pStar = &aStars[nStar]; //delete star *pStar->aAddress[pStar->nActualAddress] = 0; //move star pStar->nActualAddress++; if(pStar->aAddress[pStar->nActualAddress] == NULL) pStar->nActualAddress = 0; //paint star *pStar->aAddress[pStar->nActualAddress] = pStar->nColor; } } } //////////////////////////////////////////////////////////////////////// Si lo ejecutamos en el emulador, veríamos algo similar a esto (mucho más fluido y rápido en el emulador): Podéis bajar un zip con todos ficheros (código fuente, bat's para compilar, binarios y dsk's) aquí: Campo_de_estrellas_3D.zip |
www.CPCMania.com 2012 |