Sprites III: Transparency (C & ASM with SDCC) Pincha aquí para verlo en español In this tutorial we will see how to paint a sprite with transparency over a background. For this we will use the graphics of arcade Bomberman (Dynablaster).'ll Use a background of the game and the main character (already retouched): A little bigger: and the background of the game: As can be seen the main character's sprite, pink is the color we want to not paint (transparent color), to make the character fits into the background. We will convert the graphics with ConvImgCPC as we saw in previous tutorials. These are the values I used for the main character: Since the sprite and background must share the same palette, we must check the colors in the program we want to keep fixed, before opening the background graphic. In this case the sprite uses 10 colors, so we mark the 10 checkboxes: Then we opened the background and convert it, these are the values I used: Once we have the two graphics exported in asm, are converted to c language as we have seen in other tutorials. Being in a similar way to this: /* ;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 }; If we apply what we have learned until now in other tutorials, would paint the background image and then paint over the sprite, moving and would be as follows: //////////////////////////////////////////////////////////////////////// // 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); } } //////////////////////////////////////////////////////////////////////// The result would be:
To achieve transparency, we will make a new function to paint the sprite, based on the previous, but do not paint the pixels with color 0 (pink) to work transparency, the source code would look like: //////////////////////////////////////////////////////////////////////// // 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); } } //////////////////////////////////////////////////////////////////////// The result would be:
As can be seen the sprite leaves a trail behind, erasing the background ... Many would think that the easiest thing would always paint the entire background and then over the sprite, let's try it. The source code would look like: //////////////////////////////////////////////////////////////////////// // 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); } } //////////////////////////////////////////////////////////////////////// The result would be:
As can be seen this method, besides being very slow, causing a big flicker. As we are working directly on the video memory is impossible to avoid flicker using this method. To solve the flicker and not have to paint the entire background every time we try to use a double buffer, but only of the size of sprite, not full screen. What we do is dump the background piece on double buffering, paint over the sprite transparently and finally dump the double buffer on the screen. In this case I used a mixture of methods in C and Asm, very simple, perfectly illustrating the method used. The complete source code would be as follows: //////////////////////////////////////////////////////////////////////// // 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); } } //////////////////////////////////////////////////////////////////////// The result would be:
As we see, this time if it looks good, avoiding unnecessary flicker. You could download a zip with all files (source code, bat to compile, binary and dsk's) here: Sprites_III.zip |
www.CPCMania.com 2012 |