WindowsPhone-GameBoy模拟器开发五--使用XNA初略实现Gameboy显示系统
开篇前,最近弄了个空间,大家不嫌弃的话可以上去讨论讨论J
这一次,就来简单地实现gameboy的实现机制。先说一下本次内容涉及到的技术,其实也就一项—XNA,用XNA来完成我们最后的显示(windows phone的开发嘛,也只能用XNA了)。
思路大概是这样的:首先通过gameboy的内存取出图像的图块映射数据,通过映射来获取像素的数据,由于像素的数据实际上又是一个颜色值在调色板寄存器上的索引,所以再获取到调色板寄存器的数据,结合该像素的颜色索引值,最终确定该像素的颜色数据。
思路清楚了之后就要说一下这次要用到的技术了,首先决定使用一个2D贴图来当作Gb的背景来显示,2D贴图的数据就是从GB中获取的颜色数据。在XNA中,颜色是用32位数来表示的,看下图
通过上一篇文章知道,GB中模拟的颜色总共有4种
值 |
模拟的颜色 |
0 |
[255, 255, 255] |
1 |
[192, 192, 192] |
2 |
[96, 96, 96] |
3 |
[0, 0, 0] |
用对应的代码表示成
Dictionary<int,UInt32> ColorMap =new Dictionary<int,uint>(){{0, 0xFFFFFFFF},{1, 0xFFC0C0C0},{2, 0xFF606060},{3, 0xFF000000}};
这里最开始的8位永远是FF,因为在Gb的显示系统中没有实现alpha通道(就是透明度),好,接下来先看看运行的结果:
可以看到“内存”上方的框内有些黑黑白白的东西,其实这整个一个框是一个picturebox控件,里面这些黑黑白白的东西就是从Gb内存中显示出来的图像数据,当然,现在里面都是些测试数据,而且Gb的Cpu指令也都还没有实现,不过这不是重点了,重点是终于实现了从GameBoy内存中读取出图像数据并显示了。
下面进一步说明下是怎么实现的,供大家一起交流指点:
public Form1()
{
InitializeComponent();
PresentationParameters mPP = new PresentationParameters();
mPP.BackBufferHeight = pcbBackground.Height;
mPP.BackBufferWidth = pcbBackground.Width;
mPP.DeviceWindowHandle = pcbBackground.Handle;
mPP.DepthStencilFormat = DepthFormat.Depth24;
mPP.PresentationInterval = PresentInterval.Immediate;
mPP.IsFullScreen = false;
mGraphicsDevice = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, GraphicsProfile.Reach, mPP);
mTexture = new Texture2D(mGraphicsDevice, 256, 256);
mSpriteBatch = new SpriteBatch(mGraphicsDevice);
mGBConsole.GPU.SetTheMapData(mLogo);
}
因为这次是在窗体程序中用XNA来显示,所以在初始化XNA中使用的图像设备(GraphicsDevice)对象时要告诉该对象在什么地方上进行画图,把picturebox变量的对象指针传给它的初始化参数中mPP.DeviceWindowHandle = pcbBackground.Handle;其中,这一句 mGBConsole.GPU.SetTheMapData(mLogo);是用来初始化映射数据的,当然这只是测试数据,以后会删去。
然后在窗体中放置一个timer控件,用来定时刷新我们的GB“屏幕”。
private void timer1_Tick(object sender, EventArgs e)
{
mGraphicsDevice.Clear(Framework.Color.White);
mGraphicsDevice.Textures[0] = null;
mFrameData = mGBConsole.OutputFrame();
mTexture.SetData(mFrameData);
mSpriteBatch.Begin();
mSpriteBatch.Draw(mTexture, new Framework.Vector2(0, 0), Framework.Color.White);
mSpriteBatch.End();
mGraphicsDevice.Present();
}
这都是很常规的XNA的绘图代码了,其中mFrameData = mGBConsole.OutputFrame();这一句是从GB的内存中获取到视屏图像的数据。跟进到代码中,最后可以去到一个GameboyGPU的类,这个就是用来处理视频数据的类了,详细的请看代码中的注释:
public class GameboyGPU
{
readonly int TileSize = 8 * 2;//16个地址,每个地址存8位数据,共16KB
byte[] mVRAM = new byte[8 * 1024];//8KB video ram
byte PalleteRegister = 0;
//for test,给内存中的图像块的数据区随机的放上一些数据,该方法会在以后删除
public void InitialData()
{
Random mRandom = new Random();
for (int i = 0; i < 0x17FF;i++ )
{
mVRAM[i] = (byte)(mRandom.Next() % 0xFF);
}
for (int i = 0x1800; i < 0x1FFF; i++)
{
mVRAM[i] = (byte)(mRandom.Next() % 0xFF);
}
}
public byte[] Tiles1Data
{
get {
return mVRAM.Take(0xFFF).ToArray();
}} public byte[] Tiles1Map { get {
return mVRAM.Skip(0x1800).Take(0x3FF).ToArray();
} } public byte[] Tiles2Map
{
get
{
return mVRAM.Skip(0x1C00).Take(0x3FF).ToArray();
}
} //设置图像映射数据(总感觉这个方法不是很好,以后要改改)
public void SetTheMapData(byte[] aMapData)
{
Buffer.BlockCopy(aMapData, 0, mVRAM, 0x1800, aMapData.Length);
} public byte[] GetSingleTileData(int aTileIndex)
{
return Tiles1Data.Skip(aTileIndex * TileSize).Take(TileSize).ToArray();
} //从这个方法开始处理图像数据,传入内存中视频缓存部分的数据和调色板的数据
public UInt32[] OuputFrameData(byte[] aMemoryData, byte aPalleteRegister)
{
mVRAM = aMemoryData;
PalleteRegister = aPalleteRegister;
InitialData();//为测试初始化一些数据
UInt32[] mResult = new UInt32[256 * 256];//每帧图像的大小为256*256个像素
int mResultCount = 0;
foreach (byte tileIndex in Tiles1Map)
{
//根据映射获取到图块数据,再根据图块数据获取到每个像素的颜色值
uint[] mTileColorData = GetColorData( GetSingleTileData(tileIndex));
Buffer.BlockCopy(mTileColorData, 0, mResult, mResultCount, mTileColorData.Length);
mResultCount += mTileColorData.Length;
}
return mResult;
} //获取每个图块的颜色数据
public UInt32[] GetColorData(byte[] aTileData)
{
if (aTileData.Length % 2 != 0)
throw new Exception("VRAM data error");
UInt32[] mResultData = new UInt32[8*8];
int mColorCounter=0;
for (int i = 0; i < aTileData.Length - 1; i++)
{
//由上一篇文章分析得,图块的每行有8个像素,使用两个字节来表示,一个字节表示每个像素的高位,另一个字节用来表示每个像素的低位,两个位能表示的数据范围为0—3共4个数,这个数是一个调色板的索引代码,用这个代码可以去到调色板中查出实际的颜色值。
uint mHigh = aTileData[i];
uint mLow = aTileData[++i];
//caculate the color index
for (int j = 7; j >=0; j--)
{
int mHighValue = (mHigh & (16*(j+1)))==(16*(j+1))?1:0;
int mLowValue = (mLow & (16 * (j + 1))) == (16 * (j + 1)) ? 1 : 0;
mResultData[mColorCounter] = GetColorValue( (uint)mHighValue*2+ (uint)mLowValue);//表示高位的数据要乘以2相当于左移一位,从个位变成十位
if (mColorCounter > 8*8)
throw new Exception("Color data error");
mColorCounter++;
}
}
return mResultData;
} //查询调色板中的颜色获取实际的颜色值
public UInt32 GetColorValue(uint aColorIndex)
{
try
{
//调色板共八位,由右到左每两位划分为一组,共4组,传进来的索引值就是用来索引取哪一组的数据。但是没个组中存放的其实还是一个索引值,用来到颜色表中查找实际的颜色数据,因为每2位划分为一组,索引每组能表示4个索引值,正好对应着Gb能表示的4种颜色
switch (aColorIndex)
{
case 0://获取第1组的颜色索引
return ColorConfig.ColorMap[PalleteRegister&3];
case 1://第2组
return ColorConfig.ColorMap[(PalleteRegister&12)>>2];
case 2: /第3组
return ColorConfig.ColorMap[(PalleteRegister&48)>>4];
case 3: /第4组
return ColorConfig.ColorMap[(PalleteRegister&192) >> 6];
default:
return 0;
}
}
catch
{
return 0;
}
}
}
这里说一下,为什么从图块中映射过来的数据不直接映射到颜色表而是映射到调色板上
通过一幅图来表示一下这个家伙的映射关系:
个人觉得,其实在硬件中,3中的数据应该是直接固化在硬件上的,因为gameboy中模拟的颜色都是固定了的,所以这部分数据是无法修改的。在这个前提下,如果不使用调色板,在要对画面进行修改的时候,就需要修改1中的数据,但是,有些游戏特效,比如画面的反色,只是修改像素的颜色,也需要重新刷新内存中的每个像素点的数据,而如果使用了调色板的话,只需要修改调色板中的数据即可。对于gameboy而言,整版数据有256*256个点的数据需要修改,最坏情况下整个图块区域的数据都要修改,共4KB的数据,而使用了调色板的话,最多只需要修改1Byte的数据,差了4000倍,在速度上快了很多。
好了,这就是比较粗鲁的Gb显示系统的代码了,这次的代码还有非常多的,如显示的画面会不停地闪烁,但是对于观察指令的运行情况应该已经足够了,接下来就是实现cpu的指令了。
代码已上传到codeplex,不嫌弃的话,欢迎大家指点:https://emulatorwp.codeplex.com/SourceControl/list/changesets
WindowsPhone-GameBoy模拟器开发五--使用XNA初略实现Gameboy显示系统的更多相关文章
- WindowsPhone-GameBoy模拟器开发四--Gameboy显示系统分析
这次说一下GB的显示系统,先从一幅Gb的内存分布图说起,请看图: 图中红色框框起来的部分就是这篇文章关注的部分,这一部分的内存地址从8000-9Fff,共8KB,这一部分是从来存储背景和游戏“精灵”的 ...
- cmodel模拟器开发
cmodel模拟器开发 对于一个公司来说,产品的设计周期就是生命线,一般来说都会在设计功能级仿真的c-model后直接转向RTL设计. 在目前的技术下,做cycle-by-cycle的设计和直接RTL ...
- emWin -- 模拟器系列1 - 如何建立模拟器开发环境
面对如此强大的emWin,大家是否都有跃跃欲试的冲动呢?但是没有硬件可以调试的童鞋,难道只能望洋兴叹?非也.非也.Segger公司早就考虑到了.Segger推出模拟器的目的不仅仅是为了解决没有硬件的烦 ...
- 从零开始实现ASP.NET Core MVC的插件式开发(五) - 插件的删除和升级
标题:从零开始实现ASP.NET Core MVC的插件式开发(五) - 使用AssemblyLoadContext实现插件的升级和删除 作者:Lamond Lu 地址:https://www.cnb ...
- STC8H开发(五): SPI驱动nRF24L01无线模块
目录 STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解) STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解) ST ...
- Hadoop架构的初略总结(1)
Hadoop架构的初略总结(1) Hadoop是一个开源的分布式系统基础架构,此架构可以帮助用户可以在不了解分布式底层细节的情况下开发分布式程序. 首先我们要理清楚几个问题. 1.我们为什么需要Had ...
- .net core 和 WPF 开发升讯威在线客服与营销系统:背景和产品介绍
本系列文章详细介绍使用 .net core 和 WPF 开发 升讯威在线客服与营销系统 的过程.本产品已经成熟稳定并投入商用. 在线演示环境:https://kf-m.shengxunwei.com ...
- .net core 和 WPF 开发升讯威在线客服与营销系统:实现对 IE8 的完全完美支持 【干货】
本系列文章详细介绍使用 .net core 和 WPF 开发 升讯威在线客服与营销系统 的过程.本产品已经成熟稳定并投入商用. 在线演示环境:https://kf.shengxunwei.com 注意 ...
- ios 模拟器不显示系统版本了,后边都是 uuid 了,怎么弄回来?系统升级xcode6.4,模拟器找不到选择了?
当我用El Capitan Beta 下 Xcode6.4版本时候出现了问题 常用的Scheme 选择版本不见了 而在Xcode 7.0 beta 6中显示有 简直就是坑,经过查资料其实是一个bug ...
随机推荐
- Browser设置搜索引擎
Browser设置搜索引擎,在com.android.browser.preferences.GeneralPreferencesFragment中加载R.xml.general_preference ...
- java和Discuz论坛实现单点登录,通过Ucenter(用户管理中心)
标题有点问题,没有进行修改. 一 Discuz论坛搭建步骤 1:服务器环境配置 服务器要支持php语言+支持mysql 5.0以上的数据库 + Apache服务器(支持网站的一个服务器,通过域名的能访 ...
- Mono for android真难用
最近要写个Android小项目,真的很少,几个按钮发送tcp或udp而已. 作为.net程序员当前是c#当先,Mono for android真是好,直接用c#一切都是那么熟悉,但发布时问题来了,需几 ...
- 解决Win7下打不开chm文件的方法
win7 无法打开chm操作如下:1,在命令行运行regsvr32 itss.dll2,在命令行运行regsvr32 hhctrl.ocx
- 使用PPT绘制96孔板
什么?96孔板就是Ctrl+C然后再Ctrl+V? 那你用PPT给我画一个384孔板吧……(学生物的应该都知道这货吧?示意图不少用吧?) 还不够麻烦?那就试试基因芯片吧…… 疯掉了有木有? 那么,看看 ...
- Python成长笔记 - 基础篇 (四)函数
1.面向对象:类(class) 2.面向过程:过程(def) 3.函数式编程:函数(def)----python 1.函数:http://egon09.blog.51cto.com/9161406 ...
- bzoj 1064
题意:戳这里 思路:很明显是一个图论模型.. 就两种图形: 1.图中存在环,那么就是所有环的gcd为最大答案.gcd的大于3的最小约数为最小答案 2.不存在环,那么是每个弱连通块的最长链之和为最大答案 ...
- Winform(C#.NET)自动更新组件的使用及部分功能实现(一点改进功能)
接前两篇继续: Winform(C#.NET)自动更新组件的使用及部分功能实现 Winform(C#.NET)自动更新组件的使用及部分功能实现(续) 借鉴文章:http://www.cnblogs.c ...
- 关于mvc中@Html.DropDownListFor和@Html.DropDownList默认值无法选中问题简单总结
当我们在做类似编辑功能的时候,会给定select选中默认值,然而mvc中偶尔这个功能不能用,或者是强类型的@Html.DropDownListFor不能用.凑巧今天遇到问题,解决问题时发现了mvc的一 ...
- 博客恢复更新 工作环境转移到Linux
嗯, 回来了. 工作了, 以后学习和写博的时间只能靠挤了, 相信挤一挤总会有的.最近的一些计划: 重拾基础 玩好linux wid, 2014-04-27