我的第一个 C++ 配合 XML以及GameMonkey脚本的DEMO程序

     学习XML一天,学习GameMonkey2天,现在综合起来作了次练习,在C++程序中嵌入GameMonkey脚本程序,并让脚本程序控制游戏的主要逻辑,先把代码贴出来,有时间写篇讲GameMonkey运用到游戏中的文章---相比而言,我觉得GameMonkey比python ,lua 更容易。

    

// main.cpp,该程序演示一个小球的移动,基于HGE引擎,以XML作为配置文件,

以GameMonkey作为嵌入的脚本

 

/*

     MoveBall Demo

     This program just show how to use XML and GameMonkey in a game!

     I use XML to config something,

     and use GameMonkey to control the Ball's Moving

 

     Kevin Lynx

     2006.10.12

    

     PS. this program is based on HGE engine.

     And I use TinyXml to parse the XML file.

 

     About using XML:

     I use XML to tell this program how to create a hgeSprite for the ball.

     Like the texture filename, like the rect on the texture to create

     a hgeSprite object. ---It likes the hge resource script!

*/

#include <fstream>

#include <hge.h>

#include <hgefont.h>

#include <hgesprite.h>

 

#include "../../../TinyXML/tinyxml.h"

 

#include <gmThread.h>

#include <gmCall.h>

 

#pragma  comment( lib, "hge.lib" )

#pragma  comment( lib, "hgehelp.lib" )

 

#pragma  comment( lib, "../../../TinyXML/tinyxml.lib" )

 

#pragma  comment( lib, "GM_R.lib" )

 

//-----------------------------------------------------------------------

//-------const variables------------------------------------------------

const    int  WINDOW_WIDTH  =    800;

const    int  WINDOW_HEIGHT =    600;

 

//-----------------------------------------------------------------------

 

//------------------------------------------------------------------------

//-------some global variabls---------------------------------------------

HGE           *g_hgeEngine;

hgeSprite *g_ballSp;

hgeFont       *g_font;

//------------------------------------------------------------------------

 

//------------------------------------------------------------------------

//-------some functions---------------------------------------------------

bool     FrameFunc();

hgeSprite *CreateSpriteFromXML( const char *xmlFile );

 

//------------------------------------------------------------------------

 

//------------------------------------------------------------------------

//------declare a namespace-----------------------------------------------

namespace nsBall

{

     class    CBall

     {

     public:

         CBall( hgeSprite *sp )

         {

              m_x = m_y = m_vx = m_vy = 0.0f ;

 

              m_sp = sp;

         }

         ~CBall() {}

 

         void Update( float x, float y, float vx, float vy )

         {

              m_x  =    x;

              m_y  =    y;

              m_vx=    vx;

              m_vy=    vy;

         }

         void Render()

         {

              m_sp->Render( m_x, m_y );

         }

     private:

         CBall()  {}

     private:

         float    m_x;

         float    m_y;

        

         float    m_vx;

         float    m_vy;

 

         hgeSprite *m_sp;

     };

 

     //declare some variables

     CBall         *ns_ball;

     gmMachine *ns_gm;

    

     //-----------------------------------------------

     //some functions

    

     //will be registered in the script

     int  GM_CDECL setBall( gmThread *a_thread )

     {

         GM_CHECK_NUM_PARAMS( 4 );

         GM_CHECK_FLOAT_PARAM( x, 0 );

         GM_CHECK_FLOAT_PARAM( y, 1 );

         GM_CHECK_FLOAT_PARAM( vx, 2 );

         GM_CHECK_FLOAT_PARAM( vy, 3 );

 

         ns_ball->Update( x, y, vx, vy );

 

         return GM_OK;

     }

     //describe the setBall function in the script

     gmFunctionEntry    fnEn[] = { { "UpdateBall", setBall } };

 

     bool BindLib()

     {

         ns_gm->RegisterLibrary( fnEn, 1 );

 

         return true;

     }

     int gmLoadAndExecuteScript( gmMachine &a_machine, const char *a_filename )

     {

         std::ifstream file(a_filename);

         if (!file)

             return GM_EXCEPTION;

    

         std::string fileString = std::string(std::istreambuf_iterator<char>(file),

                                         std::istreambuf_iterator<char>());

         file.close();

         return a_machine.ExecuteString(fileString.c_str(), NULL, false);

     }

 

     bool Init( float w, float h, hgeSprite *sp )

     {

         //init the gm

         ns_gm    =    new  gmMachine();

 

         BindLib();

 

         gmLoadAndExecuteScript( *ns_gm, "script.txt" );

 

         //execute the script

         ns_gm->Execute( 0 );

 

         //create the ball

         ns_ball  =    new  CBall( sp );

 

         //call scripte function

         gmCall   callMgr;

         callMgr.BeginGlobalFunction( ns_gm, "init" );

         callMgr.AddParamFloat( w );

         callMgr.AddParamFloat( h );

         callMgr.AddParamInt( WINDOW_WIDTH );

         callMgr.AddParamInt( WINDOW_HEIGHT );

         callMgr.End();

        

         return true;

     }

 

     void Release()

     {

         delete   ns_gm;

         delete   ns_ball;

     }

 

     void UpdateBall( float time )

     {

         //call the script function

         gmCall   callMgr;

         callMgr.BeginGlobalFunction( ns_gm, "update" );

         callMgr.AddParamFloat( time );

         callMgr.End();

     }

    

};

 

//------------------------------------------------------------------------

 

int  WINAPI   WinMain( HINSTANCE, HINSTANCE, LPSTR, int )

{

#ifndef  NDEBUG

     // create a console so we can use printf() to debug

     AllocConsole();

     freopen("CONOUT$", "a", stdout); // redirect printf to console

#endif

 

     g_hgeEngine   =    hgeCreate( HGE_VERSION );

 

     g_hgeEngine->System_SetState( HGE_WINDOWED, true );

     g_hgeEngine->System_SetState( HGE_SCREENWIDTH, WINDOW_WIDTH );

     g_hgeEngine->System_SetState( HGE_SCREENHEIGHT, WINDOW_HEIGHT );

     g_hgeEngine->System_SetState( HGE_FRAMEFUNC, FrameFunc );

     g_hgeEngine->System_SetState( HGE_LOGFILE, "log.txt" );

     g_hgeEngine->System_SetState( HGE_TITLE, "Test XML and GameMonkey in a game" );

     g_hgeEngine->System_SetState( HGE_USESOUND, false );

 

     g_hgeEngine->System_Initiate();

 

     //----do some customer init-------------------------------------------

     g_ballSp =    CreateSpriteFromXML( "res.xml" );

     g_font        =    new  hgeFont( "font//font.fnt" );

     g_font->SetScale( 0.6 );

 

     nsBall::Init( 12, 12, g_ballSp );

     //--------------------------------------------------------------------

     g_hgeEngine->System_Log( "Game Data Init OK" );

 

     g_hgeEngine->System_Start();

 

 

     nsBall::Release();

 

     g_hgeEngine->Texture_Free( g_ballSp->GetTexture() );

     delete   g_ballSp;

     delete   g_font;

 

     g_hgeEngine->System_Shutdown();

     g_hgeEngine->Release();

 

     return 0;

}

 

bool FrameFunc()

{

     if( g_hgeEngine->Input_GetKeyState( HGEK_ESCAPE ) )

     {

         return true;

     }

 

     nsBall::UpdateBall( g_hgeEngine->Timer_GetDelta() );

 

     g_hgeEngine->Gfx_BeginScene();

     g_hgeEngine->Gfx_Clear( 0 );

 

     nsBall::ns_ball->Render();

 

     g_font->printf( 0, 0, "fps : %d ", g_hgeEngine->Timer_GetFPS() );

 

     g_hgeEngine->Gfx_EndScene();

 

     return false;

}

 

hgeSprite *CreateSpriteFromXML( const char *xmlFile )

{

     if( xmlFile == NULL )

         return NULL;

 

     //use TinyXml API to deal with XML file

 

     TiXmlDocument *xmlDoc = new TiXmlDocument();

 

     if( !xmlDoc->LoadFile( xmlFile ) )

     {

         //load xml failed

         g_hgeEngine->System_Log( "CreateSpriteFromXML : load xml file: %s failed", xmlFile );

         delete   xmlDoc;

         return false;

     }

 

     TiXmlElement  *root    =    xmlDoc->FirstChildElement( "MoveBall" );

 

     TiXmlElement  *ballSp  =    root->FirstChildElement( "ballSp" );

 

     //get the attribute of the ballSp

     char     textureFile[64] = {0};

     int           x,y,w,h;

     HTEXTURE tx = 0;

 

     strcpy( textureFile, ballSp->Attribute( "texture" ) );

 

     ballSp->Attribute( "x", &x );

     ballSp->Attribute( "y", &y );

     ballSp->Attribute( "width", &w );

     ballSp->Attribute( "height", &h );

 

     //ok,now i have got all data i need

     //so, release the TinyXml objects

     xmlDoc->Clear();

     delete   xmlDoc;

 

     //and now ,i create the hgeSprite

     tx   =    g_hgeEngine->Texture_Load( textureFile );

 

     hgeSprite *tmp = new hgeSprite( tx, x, y, w, h );

 

     return tmp;

}

//res.xml, 主要用来保存数据的,相当于HGE的resource script

<?xml version="1.0" encoding="utf-8"?>

<MoveBall>

     <ballSp texture = "texture.png" x = "0" y = "0" width = "12" height = "12" >

     </ballSp>

</MoveBall>

//script.txt ,GM脚本程序,控制了小球移动的主要逻辑

 

/*

     GameMonkey Script Source Code.

     This program will be embedded in the MoveBall Program.

    

     Just a Test.

    

     Kevin Lynx

     2006.10.12

*/

global   ball_x;

global   ball_y;

global   ball_w;

global   ball_h;

global   ball_vx;

global   ball_vy;

 

global   win_w;

global   win_h;

 

global init   =    function( w, h, winw, winh )

{

     global   ball_x;

     global   ball_y;

     global   ball_w;

     global   ball_h;

     global   ball_vx;

     global   ball_vy;

 

     global   win_w;

     global   win_h;

 

     print( "Script : init " );

     print( x + " " + y + " " + w + " " + h + " " + winw + " " + winh );

 

     //it's convient , you see, you need not recomplie the project

     //and you can make many sample data

 

     ball_x   =    100.0f;  //note: 100 will get an error! 100.0f is correct

     ball_y   =    100.0f;

     ball_w   =    w;

     ball_h   =    h;

     ball_vx  =    200.0f;

     ball_vy  =    200.0f;

    

     win_w    =    winw;

     win_h    =    winh;

};

 

global update=     function( time )

{

     print( "Script : update : ball_x = " + ball_x + " ball_y = "

              + ball_y + " ball_vx = " + ball_vx + " ball_vy = " + ball_vy );

    

     global   ball_x;

     global   ball_y;

     global   ball_w;

     global   ball_h;

     global   ball_vx;

     global   ball_vy;

 

     global   win_w;

     global   win_h;

 

     ball_x += ball_vx * time;

     ball_y += ball_vy * time;

    

     //limit the ball in the window

     if( ball_x < 0 )

     {

         ball_x -= ball_vx * time;

         ball_vx = - ball_vx;

     }

     if( ball_y < 0 )

     {

         ball_y -= ball_vy * time;

         ball_vy = - ball_vy;

     }

     if( ball_x > win_w - ball_w )

     {

         ball_x -= ball_vx * time;

         ball_vx = - ball_vx;

    

     }

     if( ball_y > win_h - ball_h )

     {

         ball_y -= ball_vy * time;

         ball_vy = - ball_vy;

     }

    

     //then update the ball in the host

     UpdateBall( ball_x, ball_y, ball_vx, ball_vy );

};  

 

//一些文档

Kevin Lynx

2006.10.12

 

     MoveBall这个程序用来练习在C++游戏程序中使用XML以及GameMonkey脚本。

    

     对于XML,我这里使用的parserTinyXml。这个程序中,XML只是作为配置文件

而已,相当于HGE中的 resource script

     函数CreateSpriteFromXml处理了XML

    

     对于GameMonkey,这里我只是作简单的函数调用----C++里调用脚本里的函数,

脚本里调用C++里的函数。

     它们共同的目标是控制程序里运动的小球。

     C++程序里和脚本里都保存着小球的属性信息,包括坐标,速度,以及尺寸。

游戏每一帧调用脚本里的更新函数,该更新函数又会调用C++程序里的更新函数,

C++中的更新函数因为可以访问类对象了,所以在这里才真正更新小球的属性!

 

     大致流程为:

     FrameFunc --> nsBall::UpdateBall --> script::update --> setBall -->

     CBall::Update

     之所以这样做,是受到< Game Script Mastery >一书中的:

     Host applications provide running scripts with a group of functions,

called an API (which stands for Application Programming Interface), which

they can call to affect the game.的启发!

     Host程序提供给脚本程序API,来让脚本程序控制Host

    

     总体而言,C++程序需要调用两个脚本里的函数,脚本里只调用一个C++程序里的

函数。

 

     感触:流程关系变复杂了,调试不方便----在脚本里print失效了,GM又没有写日志

的内置函数。

 

     为了使脚本里的print函数有效,可以使用以下代码使控制台有效:

     // create a console so we can use printf() to debug

     AllocConsole();

     freopen("CONOUT$", "a", stdout); // redirect printf to console

    

     可以使用 NDEBUG 宏来控制是否显示控制台! NODEBUG 宏由编译器定义,查看 assert

的声明即可知道。

 

     注意:GM脚本里的函数里的变量,默认为局部变量!如果要访问全局的变量(以global

标识的变量),需要在函数里说明一次:

     global usedVar;

     usedVar = someValue;

    

     也可以直接:

     global usedVar = someValue;

    

     这样函数里访问的才是全局变量。

    

     加入脚本后,象小球速度这类需要实现的属性,就完全可以放到脚本里进行初始化!

这样当发现速度不满意时,可以直接修改脚本来实现,就不用再去重新编译一次代码!

 

     GM里的脚本程序要注意给变量赋值的常量,因为GM脚本程序的变量类型是根据你给的

常量而定的,例如: a = 10,那么 a 就是个 int 类型的变量; a = 10.0f ,那么 a 就是

float 类型的变量。不同类型的变量在参与运算时很有可能导致错误。

 

     关于效率:

     这个程序跑的时候FPS维持在350多左右;当去掉FrameFunc  中的 UpdateBall 时,也就是

不调用脚本程序时,FPS基本没变化。----如果把控制台打开,则会严重影响FPSFPS下滑到279!

 

你可能感兴趣的:(我的第一个 C++ 配合 XML以及GameMonkey脚本的DEMO程序)