Sprites III: Transparencia (C y ASM con SDCC) En este tutorial vamos a ver como pintar un sprite con transparencia sobre un fondo, para que encaje perfectamente. Para ello vamos a utilizar los graficos del arcade Bomberman (Dynablaster).Vamos a usar un fondo del juego y el personaje principal (ya retocado): En grande: y el fondo del juego: Como puede verse en el sprite del personaje principal, el rosa es el color que pretendemos que no se pinte (color transparente), para que el personaje encaje en el fondo. Vamos a convertir los graficos con ConvImgCPC como ya vimos en tutoriales anteriores. Estos son los valores que he usado para el personaje principal: Ya que el sprite y el fondo deben compartir la misma paleta, debemos marcar en el programa los colores que queremos mantener fijos, antes de abrir el gráfico del fondo. En este caso el sprite usa 10 colores, así que marcamos las 10 casillas, dejándolo así: A continuación abrimos el fondo y lo convertimos, estos son los valores que he usado: Una vez tenemos los dos graficos exportados en asm, los convertimos a c como ya hemos visto en otros tutoriales. Quedando de una manera similar a esta: /* ;Généré par ConvImgCpc Version 0.16 ; Mode 0 ; 12x23 ; Linear */ #define SPRITE_WIDTH 12 #define SPRITE_HEIGHT 23 #define NUM_COLORS 16 const unsigned char Palette[NUM_COLORS] = {8,0,26,15,24,6,20,14,11,1,10,12,25,9,13,19}; const char Sprite[] = { 0x00, 0xC0, 0xC0, 0x00, 0x00, 0xC0, 0xC0, 0xC0 , 0xC0, 0x00, 0x00, 0x00, 0x00, 0xC4, 0xC8, 0x00 , 0xC0, 0x84, 0x0C, 0x0C, 0x48, 0xC0, 0x00, 0x00 , 0x00, 0xC4, 0xC8, 0xC0, 0x0C, 0x0C, 0x0C, 0x0C , 0x0C, 0x48, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0xC0 , 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0xC0, 0x00 , 0x00, 0x00, 0x00, 0x84, 0x0C, 0x0C, 0x0C, 0x0C , 0xCC, 0xCC, 0xC8, 0x00, 0x00, 0x00, 0x00, 0x84 , 0x0C, 0x0C, 0x0C, 0x4C, 0x30, 0x90, 0x60, 0x00 , 0x00, 0x00, 0x00, 0x84, 0x0C, 0x0C, 0x0C, 0x4C , 0x30, 0x90, 0x60, 0x00, 0x00, 0x00, 0x00, 0x84 , 0x0C, 0x0C, 0x0C, 0x4C, 0x30, 0x90, 0x60, 0x00 , 0x00, 0x00, 0x00, 0x84, 0x0C, 0x0C, 0x0C, 0x0C , 0xCC, 0xCC, 0xC8, 0x00, 0x00, 0x00, 0x00, 0xC0 , 0x0C, 0x0C, 0xC0, 0xC0, 0x0C, 0xFC, 0xC0, 0x00 , 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0xAC, 0x48 , 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 , 0xC1, 0x42, 0x0C, 0x0C, 0x94, 0x68, 0xC0, 0x00 , 0x00, 0x00, 0x00, 0x00, 0xC1, 0x42, 0x0C, 0x0C , 0x94, 0x3C, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00 , 0xC0, 0xC0, 0xC0, 0xC0, 0xC4, 0x60, 0xC0, 0x00 , 0x00, 0x00, 0x00, 0x00, 0xC1, 0x42, 0xF0, 0xF0 , 0x94, 0x3C, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00 , 0xC0, 0xC0, 0xC0, 0xC0, 0x81, 0x42, 0x00, 0x00 , 0x00, 0x00, 0x00, 0x00, 0xC0, 0xD4, 0x0C, 0x0C , 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0 , 0xC0, 0xD4, 0x0C, 0x0C, 0x48, 0xC0, 0x00, 0x00 , 0x00, 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0 , 0xC0, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0xC0 , 0xC0, 0xD0, 0xF0, 0xF0, 0xF0, 0xC0, 0xC0, 0x00 , 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0 , 0xC0, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xC0 , 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x00 , 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xC0 , 0xC0, 0xC0, 0x00, 0x00 }; /* ;Généré par ConvImgCpc Version 0.16 ; Mode 0 ; 80x200 ; Linear */ const char Background[] = { 0x9B, 0x31, 0x30, 0x33, 0x67, 0x33, 0x30, 0x32 , 0x32, 0x32, 0x33, 0x33, 0x31, 0x9B, 0x30, 0x32 , 0x32, 0x32, 0x33, 0x33, 0x31, 0x9B, 0x30, 0x32 , 0x32, 0x32, 0x33, 0x33, 0x31, 0x9B, 0x30, 0x32 , 0x32, 0x32, 0x33, 0x33, 0x31, 0x9B, 0x30, 0x32 , 0x32, 0x32, 0x33, 0x33, 0x31, 0x9B, 0x30, 0x32 , 0x32, 0x32, 0x33, 0x33, 0x31, 0x9B, 0x30, 0x32 , 0x32, 0x32, 0x33, 0x33, 0x31, 0x9B, 0x30, 0x32 , 0x32, 0x32, 0x33, 0x33, 0x31, 0x9B, 0x30, 0x32 , 0x32, 0x32, 0x33, 0x9B, 0x33, 0x30, 0x9B, 0x31 , 0x32, 0x33, 0x31, 0x32, 0x30, 0x65, 0x33, 0x33 , 0x30, 0x31, 0x9A, 0xCA, 0xB2, 0x32, 0x33, 0x33 , 0x30, 0x31, 0x9A, 0xCA, 0xB2, 0x32, 0x33, 0x33 , 0x30, 0x31, 0x9A, 0xCA, 0xB2, 0x32, 0x33, 0x33 , 0x30, 0x31, 0x9A, 0xCA, 0xB2, 0x32, 0x33, 0x33 , 0x30, 0x31, 0x9A, 0xCA, 0xB2, 0x32, 0x33, 0x33 , 0x30, 0x31, 0x9A, 0xCA, 0xB2, 0x32, 0x33, 0x33 , 0x30, 0x31, 0x9A, 0xCA, 0xB2, 0x32, 0x33, 0x33 , 0x30, 0x31, 0x9A, 0xCA, 0xB2, 0x32, 0x33, 0x33 , 0x30, 0x31, 0x9A, 0x30, 0x31, 0x32, 0x32, 0x32 , 0x31, 0x31, 0x33, 0x30, 0x9B, 0x30, 0x32, 0xCF ... , 0x32, 0x33, 0x33, 0x33, 0x31, 0x9B, 0x31, 0x32 , 0x33, 0x33, 0x33, 0x33, 0x31, 0x9B, 0x31, 0x32 , 0x33, 0x33, 0x33, 0x33, 0x31, 0x9B, 0x30, 0x33 , 0x32, 0x33, 0x33, 0x33, 0x31, 0x9B, 0x31, 0x32 , 0x33, 0x33, 0x33, 0x33, 0x31, 0x9B, 0x30, 0x33 , 0x32, 0x33, 0x33, 0x91, 0x33, 0x65, 0x30, 0x32 }; Si aplicamos lo que hemos aprendido hasta ahora en otros tutoriales, pintaríamos el fondo y encima el sprite moviéndose y quedaría de la siguiente manera: //////////////////////////////////////////////////////////////////////// // sprite01.c // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> #include <string.h> #include "Sprite.h" #include "Background.h" #define MAX_X 79 void SetColor(unsigned char nColorIndex, unsigned char nPaletteIndex) { __asm ld a, 4 (ix) ld b, 5 (ix) ld c, b call #0xBC32 ;SCR SET INK __endasm; } void SetPalette(const unsigned char *pPalette) { unsigned char nColor = 0; for(nColor = 0; nColor < NUM_COLORS; nColor++) SetColor(nColor, pPalette[nColor]); } void PutSpriteMode0(unsigned char *pAddress, unsigned char nWidth, unsigned char nHeight, unsigned char *pSprite) { __asm LD L, 4(IX) LD H, 5(IX) LD C, 6(IX) LD B, 7(IX) LD E, 8(IX) LD D, 9(IX) _loop_alto: PUSH BC LD B,C PUSH HL _loop_ancho: LD A,(DE) LD (HL),A INC DE INC HL DJNZ _loop_ancho POP HL LD A,H ADD #0x08 LD H,A SUB #0xC0 JP NC, _sig_linea LD BC, #0xC050 ADD HL,BC _sig_linea: POP BC DJNZ _loop_alto __endasm; } //////////////////////////////////////////////////////////////////////// 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; } //////////////////////////////////////////////////////////////////////// void main() { unsigned char nSprite = 0; int nX = 20; int nY = 100; char nXDir = 1; unsigned int nLastMoveTime = GetTime(); //SCR_SET_MODE 0 __asm ld a, #0 call #0xBC0E __endasm; //SCR SET BORDER 0 __asm ld b, #0 ;black ld c, b call #0xBC38 __endasm; SetPalette(Palette); PutSpriteMode0((unsigned char *)0xC000, 80, 200, Background); while(1) { if(GetTime() - nLastMoveTime < 15) continue; nLastMoveTime = GetTime(); //move nX += nXDir; if(nX <= 0) { nX = 0; nXDir = 1; } if(nX >= (MAX_X - SPRITE_WIDTH)) { nX = MAX_X - SPRITE_WIDTH; nXDir = -1; } //paint PutSpriteMode0((unsigned char *)(0xC000 + ((nY / 8u) * 80u) + ((nY % 8u) * 2048u) + nX), SPRITE_WIDTH, SPRITE_HEIGHT, Sprite); } } //////////////////////////////////////////////////////////////////////// El resultado sería el siguiente:
Para conseguir la transparencia, vamos a hacer una nueva función para pintar el sprite, basada en la anterior, pero que no pinte los pixeles con color 0 (el rosa) y así funcione la transparencia, el código fuente quedaría así: //////////////////////////////////////////////////////////////////////// // sprite02.c // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> #include <string.h> #include "Sprite.h" #include "Background.h" #define MAX_X 79 void SetColor(unsigned char nColorIndex, unsigned char nPaletteIndex) { __asm ld a, 4 (ix) ld b, 5 (ix) ld c, b call #0xBC32 ;SCR SET INK __endasm; } void SetPalette(const unsigned char *pPalette) { unsigned char nColor = 0; for(nColor = 0; nColor < NUM_COLORS; nColor++) SetColor(nColor, pPalette[nColor]); } void PutSpriteMode0(unsigned char *pAddress, unsigned char nWidth, unsigned char nHeight, unsigned char *pSprite) { __asm LD L, 4(IX) LD H, 5(IX) LD C, 6(IX) LD B, 7(IX) LD E, 8(IX) LD D, 9(IX) _loop_alto: PUSH BC LD B,C PUSH HL _loop_ancho: LD A,(DE) LD (HL),A INC DE INC HL DJNZ _loop_ancho POP HL LD A,H ADD #0x08 LD H,A SUB #0xC0 JP NC, _sig_linea LD BC, #0xC050 ADD HL,BC _sig_linea: POP BC DJNZ _loop_alto __endasm; } void PutSpriteMode0Trans(unsigned char *pAddress, unsigned char nWidth, unsigned char nHeight, unsigned char *pSprite) { __asm LD L, 4(IX) LD H, 5(IX) LD C, 6(IX) LD B, 7(IX) LD E, 8(IX) LD D, 9(IX) _loop_alto2: PUSH BC LD B,C PUSH HL _loop_ancho2: LD A,(DE) CP #0 JP Z, _notpaint LD (HL),A _notpaint: INC DE INC HL DJNZ _loop_ancho2 POP HL LD A,H ADD #0x08 LD H,A SUB #0xC0 JP NC, _sig_linea2 LD BC, #0xC050 ADD HL,BC _sig_linea2: POP BC DJNZ _loop_alto2 __endasm; } //////////////////////////////////////////////////////////////////////// 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; } //////////////////////////////////////////////////////////////////////// void main() { unsigned char nSprite = 0; int nX = 20; int nY = 100; char nXDir = 1; unsigned int nLastMoveTime = GetTime(); //SCR_SET_MODE 0 __asm ld a, #0 call #0xBC0E __endasm; //SCR SET BORDER 0 __asm ld b, #0 ;black ld c, b call #0xBC38 __endasm; SetPalette(Palette); PutSpriteMode0((unsigned char *)0xC000, 80, 200, Background); while(1) { if(GetTime() - nLastMoveTime < 15) continue; nLastMoveTime = GetTime(); //move nX += nXDir; if(nX <= 0) { nX = 0; nXDir = 1; } if(nX >= (MAX_X - SPRITE_WIDTH)) { nX = MAX_X - SPRITE_WIDTH; nXDir = -1; } //paint PutSpriteMode0Trans((unsigned char *)(0xC000 + ((nY / 8u) * 80u) + ((nY % 8u) * 2048u) + nX), SPRITE_WIDTH, SPRITE_HEIGHT, Sprite); } } //////////////////////////////////////////////////////////////////////// El resultado sería el siguiente:
Como puede verse el sprite va dejando una estela tras de sí, borrando el fondo... Muchos pensarían que lo más sencillo sería pintar siempre el fondo completo y luego encima el sprite, pues vamos a comprobar que pasaría. El código fuente quedaría así: //////////////////////////////////////////////////////////////////////// // sprite03.c // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> #include <string.h> #include "Sprite.h" #include "Background.h" #define MAX_X 79 void SetColor(unsigned char nColorIndex, unsigned char nPaletteIndex) { __asm ld a, 4 (ix) ld b, 5 (ix) ld c, b call #0xBC32 ;SCR SET INK __endasm; } void SetPalette(const unsigned char *pPalette) { unsigned char nColor = 0; for(nColor = 0; nColor < NUM_COLORS; nColor++) SetColor(nColor, pPalette[nColor]); } void PutSpriteMode0(unsigned char *pAddress, unsigned char nWidth, unsigned char nHeight, unsigned char *pSprite) { __asm LD L, 4(IX) LD H, 5(IX) LD C, 6(IX) LD B, 7(IX) LD E, 8(IX) LD D, 9(IX) _loop_alto: PUSH BC LD B,C PUSH HL _loop_ancho: LD A,(DE) LD (HL),A INC DE INC HL DJNZ _loop_ancho POP HL LD A,H ADD #0x08 LD H,A SUB #0xC0 JP NC, _sig_linea LD BC, #0xC050 ADD HL,BC _sig_linea: POP BC DJNZ _loop_alto __endasm; } void PutSpriteMode0Trans(unsigned char *pAddress, unsigned char nWidth, unsigned char nHeight, unsigned char *pSprite) { __asm LD L, 4(IX) LD H, 5(IX) LD C, 6(IX) LD B, 7(IX) LD E, 8(IX) LD D, 9(IX) _loop_alto2: PUSH BC LD B,C PUSH HL _loop_ancho2: LD A,(DE) CP #0 JP Z, _notpaint LD (HL),A _notpaint: INC DE INC HL DJNZ _loop_ancho2 POP HL LD A,H ADD #0x08 LD H,A SUB #0xC0 JP NC, _sig_linea2 LD BC, #0xC050 ADD HL,BC _sig_linea2: POP BC DJNZ _loop_alto2 __endasm; } //////////////////////////////////////////////////////////////////////// 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; } //////////////////////////////////////////////////////////////////////// void main() { unsigned char nSprite = 0; int nX = 20; int nY = 100; char nXDir = 1; unsigned int nLastMoveTime = GetTime(); //SCR_SET_MODE 0 __asm ld a, #0 call #0xBC0E __endasm; //SCR SET BORDER 0 __asm ld b, #0 ;black ld c, b call #0xBC38 __endasm; SetPalette(Palette); PutSpriteMode0((unsigned char *)0xC000, 80, 200, Background); while(1) { if(GetTime() - nLastMoveTime < 15) continue; nLastMoveTime = GetTime(); //move nX += nXDir; if(nX <= 0) { nX = 0; nXDir = 1; } if(nX >= (MAX_X - SPRITE_WIDTH)) { nX = MAX_X - SPRITE_WIDTH; nXDir = -1; } //paint PutSpriteMode0((unsigned char *)0xC000, 80, 200, Background); PutSpriteMode0Trans((unsigned char *)(0xC000 + ((nY / 8u) * 80u) + ((nY % 8u) * 2048u) + nX), SPRITE_WIDTH, SPRITE_HEIGHT, Sprite); } } //////////////////////////////////////////////////////////////////////// El resultado sería el siguiente:
Como puede verse este método ademas de lento provoca un parpadeo horrible. Como estamos trabajando directamente sobre la memoria de video es imposible evitar el parpadeo usando este método. Para solucionar el parpadeo y el tener que pintar todo el fondo cada vez vamos a probar a usar un doble buffer, pero sólo del tamaño del sprite, no de la pantalla. Lo que haremos es volcar el trozo de fondo sobre el doble buffer, pintar en el de manera transparente el sprite y finalmente volcar el doble buffer en la pantalla. En este caso he utilizado una mezcla de métodos en C y en Asm, muy sencillos y que ilustran perfectamente el método usado. El código fuente completo quedaría así: //////////////////////////////////////////////////////////////////////// // sprite04.c // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> #include <string.h> #include "Sprite.h" #include "Background.h" #define MAX_X 79 const char SpriteBuffer[SPRITE_WIDTH * SPRITE_HEIGHT] = {0}; void SetColor(unsigned char nColorIndex, unsigned char nPaletteIndex) { __asm ld a, 4 (ix) ld b, 5 (ix) ld c, b call #0xBC32 ;SCR SET INK __endasm; } void SetPalette(const unsigned char *pPalette) { unsigned char nColor = 0; for(nColor = 0; nColor < NUM_COLORS; nColor++) SetColor(nColor, pPalette[nColor]); } void PutSpriteMode0(unsigned char *pAddress, unsigned char nWidth, unsigned char nHeight, unsigned char *pSprite) { __asm LD L, 4(IX) LD H, 5(IX) LD C, 6(IX) LD B, 7(IX) LD E, 8(IX) LD D, 9(IX) _loop_alto: PUSH BC LD B,C PUSH HL _loop_ancho: LD A,(DE) LD (HL),A INC DE INC HL DJNZ _loop_ancho POP HL LD A,H ADD #0x08 LD H,A SUB #0xC0 JP NC, _sig_linea LD BC, #0xC050 ADD HL,BC _sig_linea: POP BC DJNZ _loop_alto __endasm; } void PutSpriteLinealTrans(unsigned char *pAddress, unsigned char *pSprite, unsigned int nBytes) { __asm LD L, 4(IX) LD H, 5(IX) LD E, 6(IX) LD D, 7(IX) LD C, 8(IX) LD B, 9(IX) _loop: LD A,(DE) CP #0 JP Z, _notpaint LD (HL),A _notpaint: INC DE INC HL DEC BC LD A, #0 ADD A, B ADD A, C JP NZ, _loop __endasm; } //////////////////////////////////////////////////////////////////////// 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; } //////////////////////////////////////////////////////////////////////// void main() { unsigned char nSprite = 0; int nX = 20; int nY = 100; char nXDir = 1; unsigned int nLastMoveTime = GetTime(); int nAux = 0; //SCR_SET_MODE 0 __asm ld a, #0 call #0xBC0E __endasm; //SCR SET BORDER 0 __asm ld b, #0 ;black ld c, b call #0xBC38 __endasm; SetPalette(Palette); PutSpriteMode0((unsigned char *)0xC000, 80, 200, Background); while(1) { if(GetTime() - nLastMoveTime < 10) continue; nLastMoveTime = GetTime(); //move nX += nXDir; if(nX <= 0) { nX = 0; nXDir = 1; } if(nX >= (MAX_X - SPRITE_WIDTH)) { nX = MAX_X - SPRITE_WIDTH; nXDir = -1; } //copy background to sprite double buffer for(nAux = 0; nAux < SPRITE_HEIGHT; nAux++) memcpy(SpriteBuffer + nAux * SPRITE_WIDTH, Background + 80 * (nY + nAux) + nX, SPRITE_WIDTH); //paint sprite to double buffer with transparency PutSpriteLinealTrans(SpriteBuffer, Sprite, SPRITE_WIDTH * SPRITE_HEIGHT); //paint the result to screen PutSpriteMode0((unsigned char *)(0xC000 + ((nY / 8u) * 80u) + ((nY % 8u) * 2048u) + nX), SPRITE_WIDTH, SPRITE_HEIGHT, SpriteBuffer); } } //////////////////////////////////////////////////////////////////////// El resultado sería el siguiente:
Como vemos, esta vez si que queda bien, evitando parpadeos innecesarios. Podéis bajar un zip con todos ficheros (código fuente, bat's para compilar, binarios y dsk's) aquí: Sprites_III.zip |
www.CPCMania.com 2012 |