学习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,我这里使用的parser是TinyXml。这个程序中,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基本没变化。----如果把控制台打开,则会严重影响FPS,FPS下滑到279!