第24课 - 保存游戏


更新时间:2014年2月14日


你是否曾想知道怎样保存你的游戏中的数据?无论是存储游戏中的设置还是进度,你都需要了解文件的输入和输出。这个程序将会保存背景类型和点的坐标。所以当我们重新启动程序时,点和背景将会和程序关闭时保持一致。

这是另一个运动教程的例子,能向你展示如何通过文件的输入或输出来加载或保存游戏数据。
#include "SDL/SDL.h"
#include "SDL/SDL_image.h"
#include <string>
#include <fstream>
你需要包含fstream头文件来进行文件读写。它是一个标准库,并不是SDL的一部分。
//点
class Dot
{
    private:
    //点的X/Y坐标
    int x, y;
    
    //点的速度
    int xVel, yVel;
    
    public:
    //初始化变量
    Dot();
    
    //处理按键并调整点的速度
    void handle_input();
    
    //移动点
    void move();
    
    //在屏幕上显示点
    void show();
    
    //设置点的x/y坐标
    void set_x( int X );
    void set_y( int Y );
    
    //获得点的x/y坐标
    int get_x();
    int get_y();
};
这又是Dot类。这里仅有的一个真正的变化是我们增加了x/y坐标的get/set函数。
bool load_files( Dot &thisDot, Uint32 &bg )
{    
    //加载点的图像
    dot = load_image( "dot.png" );
    
    //如果加载点时出现问题
    if( dot == NULL )
    {
        return false;    
    }
    
    //打开一个文件以供读取
    std::ifstream load( "game_save" );
load_files()函数中,我们加载完图像后创建了一个ifstream对象。

ifstream(即 input file stream,输入文件流)让你能够从一个文件流中获得输入。当你将一个文件名传入构造函数时,它将打开那个文件以供读取。
    //如果文件加载完成
    if( load != NULL )
    {
        //坐标
        int offset;
        
        //关卡名
        std::string level;
        
        //设置x坐标
        load >> offset;
        thisDot.set_x( offset );
        
        //设置y坐标
        load >> offset;
        thisDot.set_y( offset );
如果加载文件时出现问题,ifstream对象将会为NULL。

这里我们检查文件是否加载成功。如果加载成功,我们声明"offset"来提取坐标,并声明"level"来决定如何设置背景。

然后,我们首先从文件获得第一个整数并设为点的x坐标。然后,我们获取第二个整数并将其设为y坐标。正如你看到的那样,我们我们采用与cin相同的方式从ifstream中获取整数。那是因为它们都是istream(即 input stream,输入流)。

文件"game_save"的内容将是类似于这样的内容:
0 0
White Level


我们可以看到,既然文件中的和我们在控制台应用中输入的都是字符,那么我们有理由将它们视为几乎一样。
        //如果x坐标不合法
        if( ( thisDot.get_x() < 0 ) || ( thisDot.get_x() > SCREEN_WIDTH - DOT_WIDTH ) )
        {
            return false;
        }
        
        //如果y坐标不合法
        if( ( thisDot.get_y() < 0 ) || ( thisDot.get_y() > SCREEN_HEIGHT - DOT_HEIGHT ) )
        {
            return false;
        }
考虑到用户可以轻易地修改文件,我们也必须检查从文件中获得的坐标是否合法。
        //跳过行末
        load.ignore();
        
        //获得下一行
        getline( load, level );
        
        //如果在尝试加载数据时发生了错误
        if( load.fail() == true )
        {
            return false;    
        }
然后我们使用ignore()函数跳过了下一个字符('\n')。接着我们通过getline()函数获得并存储下一行内容。getline()函数与使用>>不同,它会获取直到行末前的所有内容。

对于使用Visual C++ 6.0的小伙伴们来说,你们必须使用std::getline()

随后,我们检查从文件读取是否存在问题。如果存在问题,函数fail()会返回true。
        //如果关卡是白色的
        if( level == "White Level" )
        {
            //设置背景色
            bg = SDL_MapRGB( screen->format, 0xFF, 0xFF, 0xFF );
        }
        //如果关卡是红色的
        else if( level == "Red Level" )
        {
            //设置背景色
            bg = SDL_MapRGB( screen->format, 0xFF, 0x00, 0x00 );
        }
        //如果关卡是绿色的
        else if( level == "Green Level" )
        {
            //设置背景色
            bg = SDL_MapRGB( screen->format, 0x00, 0xFF, 0x00 );
        }
        //如果关卡是蓝色的
        else if( level == "Blue Level" )
        {
            //设置背景色
            bg = SDL_MapRGB( screen->format, 0x00, 0x00, 0xFF );
        }
既然我们有了关卡字符串,我们就依照这个设置背景色。
        //关闭文件
        load.close();
    }

    //如果所有内容加载正常
    return true;
}
我们完成读取文件后,我们将文件关闭。
void clean_up( Dot &thisDot, Uint32 &bg )
{
    //释放表面
    SDL_FreeSurface( dot );
    
    //打开一个文件以供写入
    std::ofstream save( "game_save" );
    
    //将坐标写入文件
    save << thisDot.get_x();
    save << " ";
    save << thisDot.get_y();
    save << "\n";
clean_up()函数中,我们创建了一个ofstream来写入文件。ofstream(即 output file stream,输出文件流)让你能够输出到文件流中。

既然ifstream与cin类似,那么显然ofstream与cout类似。

In this piece of code we write the dot's offsets to a file. 在这部分代码中,我们将点的坐标写入了一个文件。
    //背景的RGB值
    Uint8 r, g, b;
    
    //从背景色中获取RGB值
    SDL_GetRGB( bg, screen->format, &r, &g, &b );
然后我们用SDL_GetRGB()函数从背景中获取单独的R、G、B值。
    //如果背景是白色
    if( ( r == 0xFF ) && ( g == 0xFF ) && ( b == 0xFF ) )
    {
        //将关卡类型写入文件
        save << "White Level";
    }
    //如果背景是红色
    else if( r == 0xFF )
    {
        //将关卡类型写入文件
        save << "Red Level";
    }
    //如果背景是绿色
    else if( g == 0xFF )
    {
        //将关卡类型写入文件
        save << "Green Level";
    }
    //如果背景是蓝色
    else if( b == 0xFF )
    {
        //将关卡类型写入文件
        save << "Blue Level";
    }
之后,我们写入关卡类型。
    //关闭文件
    save.close();
    
    //退出SDL
    SDL_Quit();
}
最后,我们关闭文件。
    //退出标志
    bool quit = false;
    
    //初始化
    if( init() == false )
    {
        return 1;
    }
    
    //点
    Dot myDot;

    //背景色
    Uint32 background = SDL_MapRGB( screen->format, 0xFF, 0xFF, 0xFF );
        
    //帧率校准器
    Timer fps;
    
    //加载文件
    if( load_files( myDot, background ) == false )
    {
        return 1;
    }
这里你可以看到我们使用了load_files()函数。
    //当用户还未推出
    while( quit == false )
    {
        //启动帧计数器
        fps.start();
        
        //当有事件要处理
        while( SDL_PollEvent( &event ) )
        {
            //为小点处理事件
            myDot.handle_input();
            
            //如果用户按下了按键
            if( event.type == SDL_KEYDOWN )
            {
                //根据按键更改背景
                switch( event.key.keysym.sym )
                {
                    case SDLK_1: background = SDL_MapRGB( screen->format, 0xFF, 0xFF, 0xFF ); break;
                    case SDLK_2: background = SDL_MapRGB( screen->format, 0xFF, 0x00, 0x00 ); break;
                    case SDLK_3: background = SDL_MapRGB( screen->format, 0x00, 0xFF, 0x00 ); break; 
                    case SDLK_4: background = SDL_MapRGB( screen->format, 0x00, 0x00, 0xFF ); break;     
                }
            }
            
            //如果用户叉掉了窗口
            if( event.type == SDL_QUIT )
            {
                //退出程序
                quit = true;
            }
        }
        
        //移动点
        myDot.move();
        
        //填充背景
        SDL_FillRect( screen, screen->clip_rect, background );
        
        //在屏幕上显示点
        myDot.show();
        
        //更新屏幕
        if( SDL_Flip( screen ) == -1 )
        {
            return 1;    
        }
        
        //捕获帧率
        if( fps.get_ticks() < 1000 / FRAMES_PER_SECOND )
        {
            SDL_Delay( ( 1000 / FRAMES_PER_SECOND ) - fps.get_ticks() );
        }
    }
这里是主循环。要改变背景,你可以按下1,2,34。当你再次启动程序时,点和背景将会和你退出程序时的一样。
    //清理并保存
    clean_up( myDot, background );
}
最终,我们调用clean_up()函数来清理并保存数据。
本课所用的多媒体文件和源代码可从此处下载

下一课:控制杆