手把手教你编写游戏模拟器 - Chip8篇(3)

 

手把手教你编写游戏模拟器 - Chip8篇(3)

 

 

 

翻译整理分析:by Yiran Xie

 

*如要转载请附上本文链接

 

书接上文(第二篇),下面简单讨论下chip8模拟器剩余的main.cpp文件。main中包含了opengl的glut编程来实现图像与输入系统,我对这块一点经验也没有,所以也是摸着石头过河。

 

首先安装opengl的glut库,简单地总结一下吧。glut库可能已经过时,不过好在还能用。

下载地址http://www.opengl.org/resources/libraries/glut/glutdlls37beta.zip 

Windows+ VC环境下安装的步骤: 
1、将下载的压缩包解开,将得到5个文件 
2、把gl.h放到visual studio的vc的include下,例如$\Microsoft Visual Studio 9.0\VC\include 
3、将glut.lib和glut32.lib放到静态函数库所在文件夹,例如$\Microsoft Visual Studio 9.0\VC\lib
4、将glut.dll和glut32.dll放到windows\system32下 

最后,当前project->linker->input->Additional Dependencies,加入glut32.lib glu32.lib。这样应该就能顺利编译了.

具体main.cpp代码如下,我做了下删减(去除了一些typedef和被作者抛弃的旧版的函数)这样看着更清楚一些。

///////////////////////////////////////////////////////////////////////////////
// Project description
// Name: myChip8
//
// Author: Laurence Muller
// Contact: [email protected]
//
// License: GNU General Public License (GPL) v2 
// ( http://www.gnu.org/licenses/old-licenses/gpl-2.0.html )
//
// Copyright (C) 2011 Laurence Muller / www.multigesture.net
///////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <glut.h>
#include "chip8.h"

// Display size
#define SCREEN_WIDTH 64
#define SCREEN_HEIGHT 32

chip8 myChip8;//作为全局变量
int ratio = 10;//window窗口大小和实际模拟器像素之间的比例

//windows窗口大小
int display_width  = SCREEN_WIDTH * ratio;
int display_height = SCREEN_HEIGHT * ratio;

void display();
void reshape_window(GLsizei w, GLsizei h);
void keyboardUp(unsigned char key, int x, int y);
void keyboardDown(unsigned char key, int x, int y);

unsigned char screenData[SCREEN_HEIGHT][SCREEN_WIDTH][3];
void setupTexture();

void DisplayAndSleep()
{
    display();
    _sleep(2);
}

int main(int argc, char **argv) 
{        
    if(argc < 2)
    {
        printf("Usage: myChip8.exe chip8application\n\n");
        return 1;
    }

    //读取rom
    if(!myChip8.loadApplication(argv[1]))        
        return 1;
        
    // Setup OpenGL
    glutInit(&argc, argv);          
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);

    glutInitWindowSize(display_width, display_height);
    glutInitWindowPosition(320, 320);
    glutCreateWindow("myChip8 by Laurence Muller");
    
    glutDisplayFunc(display);
    glutIdleFunc(DisplayAndSleep);
    glutReshapeFunc(reshape_window);        
    glutKeyboardFunc(keyboardDown);
    glutKeyboardUpFunc(keyboardUp); 

    setupTexture();            
    glutMainLoop(); 
    

    return 0;
}

// Setup Texture
void setupTexture()
{
    // Clear screen
    for(int y = 0; y < SCREEN_HEIGHT; ++y)        
        for(int x = 0; x < SCREEN_WIDTH; ++x)
            screenData[y][x][0] = screenData[y][x][1] = screenData[y][x][2] = 0;

    // Create a texture 
    glTexImage2D(GL_TEXTURE_2D, 0, 3, SCREEN_WIDTH, SCREEN_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid*)screenData);

    // Set up the texture
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); 

    // Enable textures
    glEnable(GL_TEXTURE_2D);
}

void updateTexture(const chip8& c8)
{    
    // Update pixels
    for(int y = 0; y < 32; ++y)        
        for(int x = 0; x < 64; ++x)
            if(c8.gfx[(y * 64) + x] == 0)
                screenData[y][x][0] = screenData[y][x][1] = screenData[y][x][2] = 0;    // Disabled
            else 
                screenData[y][x][0] = screenData[y][x][1] = screenData[y][x][2] = 255;  // Enabled
        
    // Update Texture
    glTexSubImage2D(GL_TEXTURE_2D, 0 ,0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid*)screenData);

    glBegin( GL_QUADS );
        glTexCoord2d(0.0, 0.0);        glVertex2d(0.0,              0.0);
        glTexCoord2d(1.0, 0.0);     glVertex2d(display_width, 0.0);
        glTexCoord2d(1.0, 1.0);     glVertex2d(display_width, display_height);
        glTexCoord2d(0.0, 1.0);     glVertex2d(0.0,              display_height);
    glEnd();
}

void display()
{
    myChip8.emulateCycle();
        
    if(myChip8.drawFlag)
    {
        // Clear framebuffer
        glClear(GL_COLOR_BUFFER_BIT);
        updateTexture(myChip8);

        // Swap buffers!
        glutSwapBuffers();    

        // Processed frame
        myChip8.drawFlag = false;
    }
}

void reshape_window(GLsizei w, GLsizei h)
{
    glClearColor(0.0f, 0.0f, 0.5f, 0.0f);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, w, h, 0);        
    glMatrixMode(GL_MODELVIEW);
    glViewport(0, 0, w, h);

    // Resize quad
    display_width = w;
    display_height = h;
}

void keyboardDown(unsigned char key, int x, int y)//更新按键按下信息
{
    if(key == 27)//如果是esc按键,则退出。否则,更新相应chip8.key[]中的对应按键
        exit(0);

    /*人为定义的键盘布局如下:
    1234
    qwer
    asdf
    zxcv*/
    if(key == '1')        myChip8.key[0x1] = 1;
    else if(key == '2')    myChip8.key[0x2] = 1;
    else if(key == '3')    myChip8.key[0x3] = 1;
    else if(key == '4')    myChip8.key[0xC] = 1;

    else if(key == 'q')    myChip8.key[0x4] = 1;
    else if(key == 'w')    myChip8.key[0x5] = 1;
    else if(key == 'e')    myChip8.key[0x6] = 1;
    else if(key == 'r')    myChip8.key[0xD] = 1;

    else if(key == 'a')    myChip8.key[0x7] = 1;
    else if(key == 's')    myChip8.key[0x8] = 1;
    else if(key == 'd')    myChip8.key[0x9] = 1;
    else if(key == 'f')    myChip8.key[0xE] = 1;

    else if(key == 'z')    myChip8.key[0xA] = 1;
    else if(key == 'x')    myChip8.key[0x0] = 1;
    else if(key == 'c')    myChip8.key[0xB] = 1;
    else if(key == 'v')    myChip8.key[0xF] = 1;

    //printf("Press key %c\n", key);
}

void keyboardUp(unsigned char key, int x, int y)//更新按键释放信息
{
    if(key == '1')        myChip8.key[0x1] = 0;
    else if(key == '2')    myChip8.key[0x2] = 0;
    else if(key == '3')    myChip8.key[0x3] = 0;
    else if(key == '4')    myChip8.key[0xC] = 0;

    else if(key == 'q')    myChip8.key[0x4] = 0;
    else if(key == 'w')    myChip8.key[0x5] = 0;
    else if(key == 'e')    myChip8.key[0x6] = 0;
    else if(key == 'r')    myChip8.key[0xD] = 0;

    else if(key == 'a')    myChip8.key[0x7] = 0;
    else if(key == 's')    myChip8.key[0x8] = 0;
    else if(key == 'd')    myChip8.key[0x9] = 0;
    else if(key == 'f')    myChip8.key[0xE] = 0;

    else if(key == 'z')    myChip8.key[0xA] = 0;
    else if(key == 'x')    myChip8.key[0x0] = 0;
    else if(key == 'c')    myChip8.key[0xB] = 0;
    else if(key == 'v')    myChip8.key[0xF] = 0;
}

现在原作者的代码中似乎缺少了对于帧数的控制,cpu几乎在全力运行,帧数太高。代码理解起来难度不大,主要的还是对于glut32相关库函数的熟悉,这块我目前了解的极少,打算有空时进一步研究一下(有待继续更新)。

 

不过到了这一步,openGL or glut32并不是唯一的选择,用SDL,DirectX或者其他库也能实现同样的功能。

 

举个例子,我前两天写了个c的版本,已经成功移植到了自己的x86 OS内核上(这个内核在编写时参考了Osask和OrangeOS,留待以后再细说)。主要是用到了文件读取\释放、窗口新建、绘画与刷新、定时器、按键读取这些API。换句话说,只要任何库能实现这些API,都可以用来作为glut32的替代实现。

 

具体来说,先对模拟器结构体进行初始化,接着读取rom,之后就是主loop,loop中更新按键状态、执行emulateCycle,最后根据drawflag去选择是否绘画(如要绘画,把gfx[]中的值映射到窗体上对应像素即可)。

 

其中有几点是我在编写过程中额外注意到的,一是要想办法去降低帧数(使之趋近于60hz),比如我是用了计时器;二是如果你用的是像我这样的山寨的显示API的话(即没有做过任何优化),那么有可能编写不当会出现大面积闪烁的情况。我的解决办法是尽量不要全局刷新,而仅作局部刷新。实践上,维持一个gfx_old[]数组,每次比对gfx[]与gfx_old[],仅刷新发生了变化的点(从亮变暗或从暗变亮的点)

 

最后补一张,chip8模拟器跑在我的OS内核上的图。图为同时开启三个进程,运行三款不同的ROM

手把手教你编写游戏模拟器 - Chip8篇(3)_第1张图片

你可能感兴趣的:(模拟器)