我编写 GameBoy Color Emulator 是为了娱乐和学习。为 Gameboy classic 生成颜色效果很好,但是在彩色版本模拟器中会生成不良颜色。
我声明了 BPI,BPD和OPI,OPD 。我设置了从背景内存和精灵中提取颜色的方式,并像在经典版本中一样将其更改为十六进制颜色。在某些情况下,某些元素的颜色正确。
BPI,BPD和OPI,OPD :
public int[] backgroundMemory = new int[0x40];
public int[] spriteMemory = new int[0x40];
public int backgroundPaletteIndex;
public int BackgroundPaletteData
{
get => backgroundMemory[backgroundPaletteIndex & 0x3F];
set
{
backgroundMemory[backgroundPaletteIndex & 0x3F] = value;
if ((backgroundPaletteIndex & 0x80) != 0)
{
backgroundPaletteIndex = (0x80 | ((backgroundPaletteIndex + 1) & 0x3F));
}
}
}
public int objectPaletteIndex;
public int ObjectPaletteData
{
get => spriteMemory[objectPaletteIndex & 0x3F];
set
{
spriteMemory[objectPaletteIndex & 0x3F] = value;
if ((objectPaletteIndex) != 0)
{
++objectPaletteIndex;
}
}
}
从内存中写入 BPI,BPD,OPI,OPD
case 0xFF68:
ppu.backgroundPaletteIndex = value;
break;
case 0xFF69:
ppu.BackgroundPaletteData = value;
break;
case 0xFF6A:
ppu.objectPaletteIndex = value;
break;
case 0xFF6B:
ppu.ObjectPaletteData = value;
break;
从内存中读取 BPI,BPD,OPI,OPD
case 0xFF68:
return ppu.backgroundPaletteIndex;
case 0xFF69:
return ppu.BackgroundPaletteData;
case 0xFF6A:
return ppu.objectPaletteIndex;
case 0xFF6B:
return ppu.ObjectPaletteData;
更新背景
public void UpdateBackground()
{
var tileMapAddress = backgroundTileMapDisplaySelect ? 0x1C00 : 0x1800;
if (backgroundAndWindowTileDataSelect)
{
for (var i = 0; i < 32; ++i)
{
for (var j = 0; j < 32; ++j, ++tileMapAddress)
{
if (!backgroundTileInvalidated[i, j] && !invalidateAllBackgroundTilesRequest) continue;
backgroundTileInvalidated[i, j] = false;
var tileDataAddress = _memory.videoRam[tileMapAddress] << 4;
var y = i << 3;
var x = j << 3;
for (var k = 0; k < 8; ++k)
{
var lowByte = _memory.videoRam[tileDataAddress++];
var highByte = _memory.videoRam[tileDataAddress++] << 1;
for (var b = 7; b >= 0; --b)
{
var index = (0x02 & highByte) | (0x01 & lowByte);
if (_colorMode)
{
backgroundBuffer[y + k, x + b] = GetGbcColor(backgroundMemory, index);
}
else
{
backgroundBuffer[y + k, x + b] = backgroundPalette[index];
}
lowByte >>= 1;
highByte >>= 1;
}
}
}
}
}
else
{
for (var i = 0; i < 32; ++i)
{
for (var j = 0; j < 32; ++j, ++tileMapAddress)
{
if (!backgroundTileInvalidated[i, j] && !invalidateAllBackgroundTilesRequest) continue;
backgroundTileInvalidated[i, j] = false;
int tileDataAddress = _memory.videoRam[tileMapAddress];
if (tileDataAddress > 127)
{
tileDataAddress -= 256;
}
tileDataAddress = 0x1000 + (tileDataAddress << 4);
var y = i << 3;
var x = j << 3;
for (var k = 0; k < 8; ++k)
{
var lowByte = _memory.videoRam[tileDataAddress++];
var highByte = _memory.videoRam[tileDataAddress++] << 1;
for (var b = 7; b >= 0; --b)
{
var index = (0x02 & highByte) | (0x01 & lowByte);
if (_colorMode)
{
backgroundBuffer[y + k, x + b] = GetGbcColor(backgroundMemory, index);
}
else
{
backgroundBuffer[y + k, x + b] = backgroundPalette[index];
}
lowByte >>= 1;
highByte >>= 1;
}
}
}
}
}
invalidateAllBackgroundTilesRequest = false;
}
更新精灵
public void UpdateSpriteTiles()
{
for (var i = 0; i < 256; ++i)
{
if (!spriteTileInvalidated[i] && !invalidateAllSpriteTilesRequest) continue;
spriteTileInvalidated[i] = false;
var address = i << 4;
for (var y = 0; y < 8; ++y)
{
var lowByte = _memory.videoRam[address++];
var highByte = _memory.videoRam[address++] << 1;
for (var x = 7; x >= 0; --x)
{
var paletteIndex = (0x02 & highByte) | (0x01 & lowByte);
lowByte >>= 1;
highByte >>= 1;
if (paletteIndex > 0)
{
if (_colorMode)
{
spriteTile[i, y, x, 0] = GetGbcColor(spriteMemory,paletteIndex);
spriteTile[i, y, x, 1] = GetGbcColor(spriteMemory, paletteIndex);
}
else
{
spriteTile[i, y, x, 0] = objectPalette0[paletteIndex];
spriteTile[i, y, x, 1] = objectPalette1[paletteIndex];
}
}
else
{
spriteTile[i, y, x, 0] = 0;
spriteTile[i, y, x, 1] = 0;
}
}
}
}
invalidateAllSpriteTilesRequest = false;
}
更新窗口
public void UpdateWindow()
{
var tileMapAddress = windowTileMapDisplaySelect ? 0x1C00 : 0x1800;
if (backgroundAndWindowTileDataSelect)
{
for (var i = 0; i < 18; ++i)
{
for (var j = 0; j < 21; ++j)
{
if (!backgroundTileInvalidated[i, j] && !invalidateAllBackgroundTilesRequest) continue;
var tileDataAddress = _memory.videoRam[tileMapAddress + ((i << 5) | j)] << 4;
var y = i << 3;
var x = j << 3;
for (var k = 0; k < 8; ++k)
{
var lowByte = _memory.videoRam[tileDataAddress++];
var highByte = _memory.videoRam[tileDataAddress++] << 1;
for (var b = 7; b >= 0; --b)
{
var index = (0x02 & highByte) | (0x01 & lowByte);
if (_colorMode)
{
windowBuffer[y + k, x + b] = GetGbcColor(backgroundMemory, index);
}
else
{
windowBuffer[y + k, x + b] = backgroundPalette[index];
}
lowByte >>= 1;
highByte >>= 1;
}
}
}
}
}
else
{
for (var i = 0; i < 18; ++i)
{
for (var j = 0; j < 21; ++j)
{
if (!backgroundTileInvalidated[i, j] && !invalidateAllBackgroundTilesRequest) continue;
int tileDataAddress = _memory.videoRam[tileMapAddress + ((i << 5) | j)];
if (tileDataAddress > 127)
{
tileDataAddress -= 256;
}
tileDataAddress = 0x1000 + (tileDataAddress << 4);
var y = i << 3;
var x = j << 3;
for (var k = 0; k < 8; ++k)
{
var lowByte = _memory.videoRam[tileDataAddress++];
var highByte = _memory.videoRam[tileDataAddress++] << 1;
for (var b = 7; b >= 0; --b)
{
var index = (0x02 & highByte) | (0x01 & lowByte);
if (_colorMode)
{
windowBuffer[y + k, x + b] = GetGbcColor(backgroundMemory,index);
}
else
{
windowBuffer[y + k, x + b] = backgroundPalette[index];
}
lowByte >>= 1;
highByte >>= 1;
}
}
}
}
}
}
更新图形
private void UpdateModel(bool updateBitmap)
{
if (updateBitmap)
{
var backgroundBuffer = _ppu.backgroundBuffer;
var windowBuffer = _ppu.windowBuffer;
var oam = _memory.oam;
for (int y = 0, pixelIndex = 0; y < Height; ++y)
{
_ppu.ly = y;
_ppu.lcdcMode = LcdcModeType.SearchingOamRam;
if (_cpu.lcdcInterruptEnabled
&& (_ppu.lcdcOamInterruptEnabled
|| (_ppu.lcdcLycLyCoincidenceInterruptEnabled && _ppu.lyCompare == y)))
{
_cpu.lcdcInterruptRequested = true;
}
ExecuteProcessor(800);
_ppu.lcdcMode = LcdcModeType.TransferingData;
ExecuteProcessor(1720);
_ppu.UpdateWindow();
_ppu.UpdateBackground();
_ppu.UpdateSpriteTiles();
var backgroundDisplayed = _ppu.backgroundDisplayed;
var scrollX = _ppu.scrollX;
var scrollY = _ppu.scrollY;
var windowDisplayed = _ppu.windowDisplayed;
var windowX = _ppu.windowX - 7;
var windowY = _ppu.windowY;
for (var x = 0; x < Width; ++x, ++pixelIndex)
{
uint intensity = 0;
if (backgroundDisplayed)
{
intensity = backgroundBuffer [0xFF & (scrollY + y), 0xFF & (scrollX + x)];
}
if (windowDisplayed && y >= windowY && y < windowY + Height && x >= windowX && x < windowX + Width
&& windowX >= -7 && windowX < Width && windowY >= 0 && windowY < Height)
{
intensity = windowBuffer [y - windowY, x - windowX];
}
_pixels [pixelIndex] = intensity;
}
if (_ppu.spritesDisplayed)
{
var spriteTile = _ppu.spriteTile;
if (_ppu.largeSprites)
{
for (var address = 0; address < Width; address += 4)
{
int spriteY = oam [address];
int spriteX = oam [address + 1];
if (spriteY == 0 || spriteX == 0 || spriteY >= 160 || spriteX >= 168)
{
continue;
}
spriteY -= 16;
if (spriteY > y || spriteY + 15 < y)
{
continue;
}
spriteX -= 8;
var spriteTileIndex0 = 0xFE & oam [address + 2];
var spriteTileIndex1 = spriteTileIndex0 | 0x01;
var spriteFlags = oam [address + 3];
var spritePriority = (0x80 & spriteFlags) == 0x80;
var spriteYFlipped = (0x40 & spriteFlags) == 0x40;
var spriteXFlipped = (0x20 & spriteFlags) == 0x20;
var spritePalette = (0x10 & spriteFlags) == 0x10 ? 1 : 0;
if (spriteYFlipped)
{
var temp = spriteTileIndex0;
spriteTileIndex0 = spriteTileIndex1;
spriteTileIndex1 = temp;
}
var spriteRow = y - spriteY;
if (spriteRow >= 0 && spriteRow < 8)
{
var screenAddress = (y << 7) + (y << 5) + spriteX;
for (var x = 0; x < 8; ++x, ++screenAddress)
{
var screenX = spriteX + x;
if (screenX >= 0 && screenX < Width)
{
var color = spriteTile [spriteTileIndex0,
spriteYFlipped ? 7 - spriteRow : spriteRow,
spriteXFlipped ? 7 - x : x, spritePalette];
if (color <= 0) continue;
if (spritePriority)
{
if (_pixels [screenAddress] == 0xFFFFFFFF)
{
_pixels [screenAddress] = color;
}
} else
{
_pixels [screenAddress] = color;
}
}
}
continue;
}
spriteY += 8;
spriteRow = y - spriteY;
if (spriteRow < 0 || spriteRow >= 8) continue;
{
var screenAddress = (y << 7) + (y << 5) + spriteX;
for (var x = 0; x < 8; ++x, ++screenAddress)
{
var screenX = spriteX + x;
if (screenX < 0 || screenX >= Width) continue;
var color = spriteTile [spriteTileIndex1,
spriteYFlipped ? 7 - spriteRow : spriteRow,
spriteXFlipped ? 7 - x : x, spritePalette];
if (color <= 0) continue;
if (spritePriority)
{
if (_pixels [screenAddress] == 0xFFFFFFFF)
{
_pixels [screenAddress] = color;
}
} else
{
_pixels [screenAddress] = color;
}
}
}
}
} else
{
for (var address = 0; address < Width; address += 4)
{
int spriteY = oam [address];
int spriteX = oam [address + 1];
if (spriteY == 0 || spriteX == 0 || spriteY >= 160 || spriteX >= 168)
{
continue;
}
spriteY -= 16;
if (spriteY > y || spriteY + 7 < y)
{
continue;
}
spriteX -= 8;
var spriteTileIndex = oam [address + 2];
var spriteFlags = oam [address + 3];
var spritePriority = (0x80 & spriteFlags) == 0x80;
var spriteYFlipped = (0x40 & spriteFlags) == 0x40;
var spriteXFlipped = (0x20 & spriteFlags) == 0x20;
var spritePalette = (0x10 & spriteFlags) == 0x10 ? 1 : 0;
var spriteRow = y - spriteY;
var screenAddress = (y << 7) + (y << 5) + spriteX;
for (var x = 0; x < 8; ++x, ++screenAddress)
{
var screenX = spriteX + x;
if (screenX < 0 || screenX >= Width) continue;
var color = spriteTile [spriteTileIndex,
spriteYFlipped ? 7 - spriteRow : spriteRow,
spriteXFlipped ? 7 - x : x, spritePalette];
if (color <= 0) continue;
if (spritePriority)
{
if (_pixels [screenAddress] == 0xFFFFFFFF)
{
_pixels [screenAddress] = color;
}
} else
{
_pixels [screenAddress] = color;
}
}
}
}
}
_ppu.lcdcMode = LcdcModeType.HBlank;
if (_cpu.lcdcInterruptEnabled && _ppu.lcdcHBlankInterruptEnabled)
{
_cpu.lcdcInterruptRequested = true;
}
ExecuteProcessor(2040);
AddTicksPerScanLine();
}
} else
{
for (var y = 0; y < Height; ++y)
{
_ppu.ly = y;
_ppu.lcdcMode = LcdcModeType.SearchingOamRam;
if (_cpu.lcdcInterruptEnabled
&& (_ppu.lcdcOamInterruptEnabled
|| (_ppu.lcdcLycLyCoincidenceInterruptEnabled && _ppu.lyCompare == y)))
{
_cpu.lcdcInterruptRequested = true;
}
ExecuteProcessor(800);
_ppu.lcdcMode = LcdcModeType.TransferingData;
ExecuteProcessor(1720);
_ppu.lcdcMode = LcdcModeType.HBlank;
if (_cpu.lcdcInterruptEnabled && _ppu.lcdcHBlankInterruptEnabled)
{
_cpu.lcdcInterruptRequested = true;
}
ExecuteProcessor(2040);
AddTicksPerScanLine();
}
}
_ppu.lcdcMode = LcdcModeType.VBlank;
if (_cpu.vBlankInterruptEnabled)
{
_cpu.vBlankInterruptRequested = true;
}
if (_cpu.lcdcInterruptEnabled && _ppu.lcdcVBlankInterruptEnabled)
{
_cpu.lcdcInterruptRequested = true;
}
for (var y = 144; y <= 153; ++y)
{
_ppu.ly = y;
if (_cpu.lcdcInterruptEnabled && _ppu.lcdcLycLyCoincidenceInterruptEnabled
&& _ppu.lyCompare == y)
{
_cpu.lcdcInterruptRequested = true;
}
ExecuteProcessor(4560);
AddTicksPerScanLine();
}
if (Audio != null)
_memory.soundChip.OutputSound(Audio);
}
获取GBCColor
private static uint GetGbcColor(int[] paletteMemory, int paletteIndex)
{
var rawValue = (paletteMemory[paletteIndex * 2] | (paletteMemory[(paletteIndex*2)+1] & 0x7F)<<8);
var r = ((rawValue & 0x1F)*255)/31;
var g = (((rawValue >> 5) & 0x1F)*255)/31;
var b = (((rawValue >> 10) & 0x1F)*255)/31;
return (uint)(0xFF000000 | (r << 16) | (g << 8) | (b << 0));
}
菜单的工作方式:
https://photos.app.goo.gl/M2QTHVTzoPS1jiKS9
就像他们现在所做的那样:
https://photos.app.goo.gl/GW7SCm3ZXHyxRikGA
Gameboy颜色的第一个房间:
https://photos.app.goo.gl/BNHDgmuPw5j1c9Lq6
在模拟器的第一个房间:
https://photos.app.goo.gl/934tm2X1jEazkcPK7
我在乎最简单的解决方案
预先感谢