【转】 使用XML和脚本创建模块化的游戏 第一部分

来源:互联网 发布:怎么开通淘宝店铺 编辑:程序博客网 时间:2024/05/20 16:13

Creating Moddable Games with XML and Scripting Part I

                                          by Oli Wilkinson

                            使用XML和脚本创建模块化的游戏 第一部分

                                               作者:Oli Wilkinson

                                          翻译: Kevin Lynx

                                          2006-10-9 

In this short series of articles, I will explore in how XML and scripting can be combined and used to create modifiable content for games. Game modification is nothing new, being popularised by titles such as Quake 2 and Half-Life. However, despite its popularity there seems to be a lack of articles discussing how to mod-enable your own games. With this series of articles I will explore the use of a JavaScript environment combined with XML to store your game data to allow you to mod-enable your own games.

Roadmap for the series

This series will span the development of a small game, "jsInvaders" and focus solely upon creating and embedding the script and XML parsing interfaces that serve as a facility to modify (mod) the content and behaviours within the game.

To follow this series, I am assuming that you have knowledge of C++, OpenGL and understand the principles of game creation. This series is not a game creation tutorial, so I will not be focusing on normal game aspects such as graphics, sound, game logic, et al. Here's a brief outline of how you can expect this series to progress:

  • Part I

Serves as an introduction to the series; briefly explains what XML is and discusses the use of XML in games. Introduces TinyXml, a small open-source XML parser and shows you how to use it for parsing the basic data required by the game.

  • Part II

A brief introduction to JavaScript (using the Mozilla "SpiderMonkey" engine). Discusses the development of a scripting API and how to write the 'glue' code that sits between the game and the scripting engine.

  • Part III

Brings the series to a close with expansion of the basic scripting API developed in Part II and discusses how to develop a Game Object Model to effectively link XML with your scripted components.

Brief Introduction to XML

XML的简要介绍:

Now that you understand where this series will be headed, I will now introduce you to the basics of XML documents. After reading this section, you should understand exactly what XML is, how to create it and be thinking of ways you can apply XML to your games. I suggest that you read Richard Fine's "XML in Games" available on GameDev.net, as it serves as an excellent introductory text on the subject.

      现在你知道这个系列的教程开头会讲什么,我现在要向你介绍基本的XML文档。读完这一小节过后,你应该完全明白XML是什么,以及如何创建它,并以模块化的思维把它应用到你的游戏中去。我建议你先读一下 Richard Fine GameDev上的<XML in Games>一文,它对这一节里我要说的话题进行了很精彩的描述。

The XML document specifications require each valid document to begin with an XML file declaration:

XML规范描述每一个有效的XML文件都由一个XML文件声明开头:

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

Quite simply, this declaration is stating that the document is XML version 1.0 and is encoded with the UTF-8 character set.

    非常简单,这个声明描述了这个XML文件遵循XML规范1.0版,并且使用UTF-8编码。

XML is a meta-language; in short you are able to define your own set of tokens (or 'tags'), rules and structures. Most XML files are defined by a DTD, or Document Type Definition which dictates the rules and structure of the document by supplying a list of legal tokens. DTD rule creation is important as it allows the XML parser to quickly accept or reject documents on loading. With that said, we are using TinyXml and it is a non-validating parser; it will ignore any DTD it finds and just processes the document elements with no rules. This is fine for our purposes as we can build our own validation rules into the parsing stage.

    XML是一种元语言;简单地说你可以使用它来定义你自己的标记(它是一种标记性语言),规则以及结构体。大多数XML文件由DTD来定义,DTDDocument Type Definition的缩写,DTD用来检测XML文件是否规范,标记是否正确。DTD的创建规则可以让XML分析器很快地进行正确地分析。根据这样的说法,我们决定使用TinyXmlTinyXml就是一种XML分析器,它不处理DTD,它对XML进行分析时不依照任何DTD的规则(实际上忽略了DTD)。
 
    XML documents have a strict hierarchical structure; elements are contained within other elements, which in turn are contained within higher elements in the document tree. This tree-like structure needs a root, the document root, and is the single element which all other elements are descended from. Every element on the next hierarchical level is called a child of an element and elements with the same root element are said to be siblings.

      XML文件拥有很严格的层次结构;元素被其他元素包含,其他元素又被更高层次的元素包含。XML文件里的元素是以树型结构组织的。这个树的根部就是文档(XML文档本身)。在树型层次中,低一层的元素是其高层元素的孩子,高层元素就是其父母;拥有相同父母的元素就是兄弟。(与树数据结构一样)

      The hierarchical structure of XML allows for easy parsing of the tree and can introduce great flexibility when it comes to manipulating the data. Whole branches can be detached and placed elsewhere with little difficulty.

      XML的树型结构使得分析起XML文件时很容易,且在操作数据时也很方便。整个层次结构中的任一分支都可以很容易地被方在任何地方。

Let's take a look at a basic declaration of a jsInvaders level:

    让我们看一下jsInvaders层次的定义:(作者在这一系列教程中,将开发一个名为jsInvaders的示例游戏)
<?xml version="1.0" encoding="utf-8"?>
<invadersgame>
      <level>
            <alien />
            <alien />
            <alien />
      </level>
</invadersgame>

You should be able to see that the document root element is <invadersgame>. The root then contains a level element, which in turn contains three alien elements. In order to clarify this, I'll break down the basic jsInvaders level document:

你应该可以看到,文档元素(最顶层的元素,也就是根元素)就是<invadersgame>,然后根元素包含了一个 level 元素,这个 level 元素又包含了三个 alien 元素。为了更为清晰地说明这一点,我把以上文档分开来说:

  • <invadersgame> is the document root  <invadersgame>是文档根元素
  • <level> is a child of <invadersgame>   <level><invadersgame>的一个孩子。
  • <alien> is a child of <level>               <alien><level>的孩子。
  • The <alien> elements in a <level> element are siblings to each other

<level>包含的每一个<alien>是其他<alien> 的兄弟。

 

If you're used to Object Oriented Programming (OOP), then you'll probably be able to picture how you'd map these elements into classes. By referring to the code attached to this article, you'll see how I've begun mapping these objects into C++ classes for use in the game. So far in the code I have the game classes sketched out for dealing with levels, aliens and the player ship. I won't go into detail about much of the rest of the game classes as it is beyond the scope of this article.

      如果你习惯了面向对象编程,那么很有可能你会把上面那种结构抽象为类结构。在这篇文章里,我就是把这样的结构对应地抽象为C++类的。我已经有了对应于level, alien 以及 player的类。在这个教程里,我不会讲太多游戏编程方面的东西,那不在本教程讨论的范围内。

If you examine the file jsiAlien.h, you'll see that I have defined several properties for the object. Things like 'color', 'position' and 'points value' are all properties of the Alien object that will need values to be of use in the game. XML document elements can also have attributes assigned to them, meaning that you can represent the data for objects such as the Alien object with an XML element tag.

如果你查看 jsiAlien.h 这个文件,你就会看到我为一个类定义了很多属性。颜色,位置,以及会给玩家多少分数都是游戏中 alien 对象需要的属性。XML文件的元素也可以拥有属性,这意味着你可以把游戏中alien对象的属性设置进XML文件对应的元素中去:

<alien xpos="0" ypos="100" color="red" points="10" />

If you look at the declaration above you will see that I have assigned 4 properties to the XML element that represents the Alien. In this example the alien is red, is positioned at (0, 100) and awards 10 points to the player when killed. Taking this forward, we begin to build the XML file that defines a basic game level.

      你会看到以上定义中我为XML元素alien分配了4个属性。在这个例子中,alien是红色,位置在(0100)处,而且当被玩家杀死后它会给玩家奖赏10点分数。我们来定义一个基本的游戏关卡:

<?xml version="1.0" encoding="utf-8"?>
<invadersgame>
      <level>
            <!-- here's where we declare the aliens -->
            <alien xpos="0" ypos="100" color="red" points="10" />
            <alien xpos="100" ypos="100" color="green" points="10" />
            <alien xpos="200" ypos="100" color="blue" points="10" />
      </level>
</invadersgame>

It should be apparent that we now wish to have 3 aliens of different colours, each 100 units apart horizontally. You'll notice that I've begun commenting my XML document with the XML comment tags, <!-- this is a comment -->. I personally find commenting all of my code to be good practice, even in this simple example :). The benefits of good commenting will reveal themselves later on when the files start getting bigger.

      很显然,我们在这个文件里希望有3alien,每一个拥有不同的颜色,且在横坐标上相隔100个单位。在这个文件里我加入了注释<!-- this is a comment -->我个人觉得加入注释是很好的习惯。

Parsing the XML

分析XML

Now that you understand the concept of XML and how document elements relate to the data contained within class objects, I will take you through the process of parsing the XML document. The act of 'parsing' is when the raw document is read in by your program and transformed into the elements that make up the document tree. With jsInvaders, the parsing stage will consist of reading the XML document into memory and creating objects within the game environment based on the data within it.

         现在你已经知道了XML的基本概念,并知道XML中的元素如何对应着C++中的类,我将要告诉你如何去分析一个XML文件。分析也就在你的程序中读进XML文件,然后得到其内表达的树型结构。在jsInvaders里,程序需要先把XML文件读进内存,然后设置游戏相关数据。

For this section you will require an XML parser; I am using TinyXml, a small open-sourced library that was developed for easy handling of XML documents. Bear in mind that there are several XML parsers available which all follow the basic concepts, so pick one you feel comfortable with if you don't wish to use TinyXml.

         在这一节里,你需要个XML分析器;我使用TinyXml,它是一个小巧的并且开源的XML分析库。记住其实有很多XML的分析器,如果你不喜欢TinyXml,你完全可以使用你自己喜欢的。

With TinyXml, all of the elements are contained within a TiXmlDocument structure. The first thing you need to do is create an instance of this object and use it to load and parse the XML file. This is shown in the code fragment below (taken from jsiGame.cpp):

         使用TinyXml,所有的元素都被包含在一个TiXmlDocument的结构体中(其实是一个类,但是基本一样)。你需要做的第一件事情就是创建一个该类(TxXmlDocument)的对象,然后使用它装载一个XML文件并分析。以下是示例代码(选自jsiGame.cpp):

int jsiGame::loadXmlLevels(const char *filename)
{
  TiXmlDocument     *xmlDoc = new TiXmlDocument(filename);
  if (!xmlDoc->LoadFile())
  {
    // Fatal error, cannot load
    return 0;
  }
 
  // Do the document parsing here
 
  xmlDoc->Clear();
 
  // Delete our allocated document and return success ;)
  delete xmlDoc;
  return 1;
}

The TiMxlDocument->LoadFile() method will fail if there is an error with the document; for example it may not exist at the specified location or there may be an error with the actual structure of the XML document itself. After you have processed the XML document, you have to call the Clear() method of the TiXmlDocument in order to free up the memory allocated to loading and parsing the document.

-----------------------------以下为fancybit翻译-----------------------------------------

         TiXmlDocument->LoadFile()中,如果提供的文件有什么错误,该函数就会失败;这些错误包括提供的文件不存在,或者文件是错误的。在使用完XML文件后,你需要调用TiXmlDocument::Clear来释放分配的空间.

With the document structure we defined earlier to hand, we can begin parsing the XML file with TinyXml. To get the document root, we call FirstChildElement() from the document that was just loaded.

我们可以使用刚才得到的TiXmlDocument对象来进一步得到树型结构中的根元素,使用FirstChildElement得到

TiXmlElement *xGame = 0;
xGame = xmlDoc->FirstChildElement("invadersgame");
 
if (!xGame)
{
  // The game element couldn't be found, 
  // we have to say goodbye!
  // clear and delete the doc before exiting
  xmlDoc->Clear();
  delete xmlDoc;
 
  return 0;
}

The TiXmlDocument class is based on TiXmlElement, so the FirstChildElement() call can be used with any TinyXml element. If the requested element couldn't be found, the call will return 0, so it's important to check for failures and handle them appropriately. In the example above, the object xGame now holds a pointer to the root element in the document. This element will now be used as a root for parsing the level data.

TiXmlDocment类是基于 TiXmlElemnt的,因此FirestChildElement()调用能够用于文档对象中的任意的TinyXml元素。如果要求的的元素没被找到,这个调用会返回0,因此,检测并且合理地处理错误是重要的。在上面的例子中,对象xGame现在拥有一个指向文档根元素的指针(也就是说这个xGame对象打开的xml文档根元素即是“invadersgame”)。这个元素现在会作为在解析层数据的根元素。(也就是说 xGame构造后,内部有一个游标,一开始指向整个xml文档的根元素,注意xml文档是单根的树结构的,所以总会有一个根元素。)

TiXmlElement *xLevel = 0;
xLevel = xGame->FirstChildElement("level");
 
if (!xLevel)
{
  // No level elements were found 没有元素被找到
  // return with an error 返回一个错误
  // clear and delete the doc before exiting 退出前清理并且删除doc
  xmlDoc->Clear();
  delete xmlDoc;
  return 0;
}
 
// the level has been found, parse it here... 关卡找到了,在这里解析它

As before, the object xLevel will point to the first level element tag. When jsInvaders encounters a level element, a new instance of a level class is created which is then passed the relevant TiXmlElement for parsing the level data.

如前面这段代码描述的,对象xLevel会指向第一个关卡元素标记。当jsInvaders遇到一个关卡元素,一个新的关卡类实例就被创建,然后在解析层面的数据为之传递关联的TiXmlElement。

To parse the alien elements contained within a level, a similar procedure is adopted. The main difference in the next step is that there will be several alien elements (siblings) and that we will be parsing the attributes from the XML file and using them to fill the jsiAlien class.

Because the alien elements are siblings to each other, we can iterate through the level element searching for more occurrences of the object using TiXmlElement->NextSiblingElement().This is shown in the code fragment below (taken from jsiLevel.cpp):

解析包含在一个关卡中的alien元素采用的是类似的过程。主要的差别在于下一步将会有若干alien元素(互为兄弟关系的)并且我们将会从xml文件中解析其属性数据并且用他们填充jsiAlien类。

因为alien元素互为兄弟关系,我们能够迭代遍历整个关卡来搜索更多xml文档中出现的元素,通过使用TiXmlElement->NextSiblingElement().这展示在下面的代码片段中(放在jsiLevel.cpp)

int jsiLevel::parseXmlLevel(TiXmlElement *xLevel)

{
  TiXmlElement *xAlien = 0;
  int nAlienCount = 0;
 
  xAlien = xLevel->FirstChildElement("alien");
  // Loop through each alien in the level循环遍历关卡中的所有alien
  while(xAlien)
  {
    // create a new alien创建一个新的alien对象
    jsiAlien *alien = createAlien();
    // Parse this particular alien解析特定的alien
 
    // grab the position data from the attributes抓取位置属性的数据
    // check it exists before adding the attribute在增加属性前检测它是否存在
    if (xAlien->Attribute("xpos"))
    {
      alien->position.x = (float)atof(xAlien->Attribute("xpos"));
    }
    if (xAlien->Attribute("ypos"))
    {
      alien->position.y = (float)atof(xAlien->Attribute("ypos"));
    }
 
    alien->color = jsiUtility::colourFromText( xAlien->Attribute("color") );
 
    // move to the next element解析游标移动到下一个元素
    xAlien = xAlien->NextSiblingElement("alien");
    // increment our counter增加计数器变量
    ++nAlienCount;
  }
return 1;
}

You will notice that every time an alien element is encountered, the game requests that the level creates an instance of the jsiAlien object. It should be apparent that the number of aliens created in the level maps 1:1 to the XML document :)

你将会注意到每次遇见一个alien元素,游戏就请求关卡创建一个jsiAlien对象实例。显然,在关卡地图中创建的alien数目和xml文档中的alien元素应该是1:1的:)

Getting an attribute associated with an element is easy, it's just a matter of calling TiXmlElement->Attribute( <name> ).

取得一个与元素关联的属性是容易的,那只要调用TiXmlElement->Attribute(<标签名>)就可以了。

if (xAlien->Attribute("xpos"))
{
  alien->position.x (float)atof(xAlien->Attribute("xpos"));
 
}

Like other TinyXml functions, the Attribute call will return 0 if the attribute doesn't exist. I'm checking for the existence of an attribute before I read it, this allows the author of the XML document to exclude attributes they won't need. It's worth remembering that all attributes are stored as text and you will need to convert them to your desired format before you store them in your objects.

就像其他TinyXml函数一样,Attribute ()调用将会在属性不存在时返回0。我在读取属性之前检测它是欧服存在,这使得xml文档的作者忽略他不关心的属性。应该记住,所有的属性是有序的文本,并且你会需要把他们转换为你想要的格式,在你把它们存储你的对象中之前。

At this stage, we've actually covered all the XML we'll need for now. When it comes to loading textures and scripts, it's a simple case of adding more elements to the XML document and parsing them in the level loading routines. In any case, you'll see that I've expanded the XML document slightly in the next instalment to account for the extra features the game has.

现在,我们实际上转换了所有需要的的XML。相对于在关卡装载例程中用xml格式装载纹理和脚本,这只是一个简单的例子。在任何情况下,你都会看到我“分期付款”地通过略微地扩展xml文档来为游戏增加新特性。

Naturally, TinyXml has more features that the ones we're using here (including the creation and saving of XML documents) so once you get to grips with how it works, it may be useful to start exploring how these work for yourself. I won't be covering them here because I don't need them in this particular project, but it's worth bearing in mind that you might need them someday.

自然地,TinyXml有更多的特性在这里用到(包括xml文档的创建与保存)因此当你掌握了它是如何工作的,对于开始探索如何让它按你的想法工作来说会是一个有用的开端。我不会在这里涉及它们,因为我在这个特定的工程里不需要它们,但是记住,它是有用的,你可能会有一天用到。

That's it for the first part of this series. I'm hoping that we covered enough XML parsing to feel comfortable with what we're trying to achieve here and how to use TinyXml in your own projects. I urge you to experiment, if only by adding configuration options or score saving routines to use XML data, I guarantee that you'll be bitten by the bug and see how useful a file format it is.

这是这个系列的第一个部分。我希望我讲述了足够的XML解析的内容来完成目标并且讲明白如何在你自己的项目中使用TinyXml。我建议你亲手实验,如果只是在增减配置选项或者得分保存过程中使用xml对象,我猜你会与bug不期而遇并且看到一个文件格式是多么有用。

Next time around, I'll be covering how to set up a scripting environment within your project and how to link the game objects we've just created to the scripting runtime. In particular, I'll be focusing on creating a script API that will be used to control the motions and behaviours of objects within the game.

下次,我会讲述如何在你的的项目中建立一个脚本环境和如何在脚本运行的时候链接到我们刚刚建立的游戏对象。特别地,我会关注创建一个在游戏中用于控制监视器和对象行为的脚本API。

Until next time...

Oli

 

第一译者的理解:

目前我觉得,XML文件,就等同于一个结构更为清晰,可读性更强的配置文件! 游戏读取里面的内容,然后根据其内容对游戏内的一些数据进行设置!

要很好的理解Parser库,例如TinyXml,我觉得事先应该对XML进行了解!尤其是一些XML的术语---这些术语很大程度上就会对应于TinyXml库的一些接口。

关于XML的一些术语:

1DOM全称是document object model(文档对象模型)DOM是用来干什么的呢?假设把你的文档看成一个单独的对象,DOM就是如何用HTML或者XML对这个对象进行操作和控制的标准。

 

2元素在HTML我们已经有所了解,它是组成HTML文档的最小单位,在XML中也一样。一个元素由一个标识来定义,包括开始和结束标识以及其中的内容,就象这样:<author>ajie</author>

 

3属性是对标识进一步的描述和说明,一个标识可以有多个属性,例如font的属性还有sizeXML中的属性与HTML中的属性是一样的,每个属性都有它自己的名字和数值,属性是标识的一部分。

 

4标识是用来定义元素的。在XML中,标识必须成对出现,将数据包围在中间。标识的名称和元素的名称是一样的。例如这样一个元素:

<author>ajie</author>

其中<author>就是标识。

5父元素是指包含有其它元素的元素,被包含的元素称为它的子元素。

 

XML文档包含三个部分:

1. 一个XML文档声明;

在所有XML文档的第一行都有一个XML声明。这个声明表示这个文档是一个XML文档,它遵循的是哪个XML版本的规范。一个XML的声明语句就象这样:<?XML version="1.0"?>

 

2. 一个关于文档类型的定义;

 

3. XML标识创建的内容。

 

 

 

书写XML需要注意的:

 

1.所有的标记都必须要有一个相应的结束标记;

 

2.所有的XML标记都必须合理嵌套;

 

3.所有XML标记都区分大小写;

 

4.所有标记的属性必须用""括起来;

 

5.名字中可以包含字母、数字以及其它字母;

 

6.名字不能以数字或"_" (下划线) 开头;

 

7.名字不能以字母 XML ( XML XML ..) 开头;

 

8.名字中不能包含空格。

原创粉丝点击