⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 items.htm

📁 物件管理系统源代码
💻 HTM
📖 第 1 页 / 共 2 页
字号:
<html>
<head>
<title>Item Management System</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
</head>

<body bgcolor="#FFFFFF">
<div align="center"> 
  <p><b>Item Management Systems <br>
    </b></p>
  <p><b>Victor Nicollet</b></p>
</div>
<blockquote> 
  <div align="center"> 
    <p align="left"><i><b>Introduction</b></i></p>
  </div>
</blockquote>
<div align="left">
  <p>A lot of games allow the player to pick up, carry around, use, sell, buy, 
    drop, drink, wear various items. For such a thing to happen without becoming 
    an overwhelming task for the programmers, a coherent system for managing all 
    these actions becomes necessary. In this article, I will present and discuss 
    a simple and basic system for managing objects, that can easily be extended 
    to cover more complex cases.</p>
</div>
<blockquote>
  <p><b><i>System architecture : what's what?</i></b></p>
</blockquote>
<p>Keeping all item information along with each item is overkill: all <i>small 
  health potions</i> have the same name, looks and properties. Sure, some objects 
  might have &quot;personal&quot; data such as ammunition, charges, enchantments, 
  nicknames or durability that must be stored on the item itself, but this can 
  be added later on.</p>
<p>The items will be represented by objects of a <font face="Courier New, Courier, mono">cItem</font> 
  class that will act as a black box - users of the class do not need to know 
  how it is working on the inside. These objects will actually contain an item 
  index which indicates what kind of item they are. To get the properties of an 
  object, there will be a <font face="Courier New, Courier, mono">cItemDatabase</font> 
  that holds all the information for all the possible item indices an object can 
  have, and which can be asked for this for information.</p>
<p>Finally, to represent piles of various items with various amounts (whether 
  it is a pile on the ground, a pile being traded to somebody else, or in the 
  player's backpack) there will be a <font face="Courier New, Courier, mono">cItemPack</font> 
  class which acts just like any pile of items: you can count the objects, add 
  some, or remove others.</p>
<blockquote> 
  <p><b><i>The items</i></b></p>
</blockquote>
<p>The first and foremost step is to implement the class that will represent the 
  items - since it's what we're going to move around and use most of the time 
  anyway. An &quot;item&quot; variable should obviously have the following properties 
  :</p>
<ul>
  <ul>
    <ul>
      <ul>
        <ul>
          <li>Be set to another object</li>
          <li>Compared to another object (to see if it's the same)</li>
        </ul>
      </ul>
    </ul>
  </ul>
</ul>
<p>Because we will need to manipulate the objects, we also need the object to 
  have the following (from a programming standpoint) :</p>
<ul>
  <ul>
    <ul>
      <ul>
        <ul>
          <li>Be able to return an unique identifier for debugging purposes</li>
          <li>Be able to be built from that unique identifier</li>
          <li>All items must be fully ordered</li>
        </ul>
      </ul>
    </ul>
  </ul>
</ul>
<p>This leads to the following class definition:</p>
<blockquote> 
  <blockquote> 
    <blockquote> 
      <blockquote> 
        <p><font face="Courier New, Courier, mono" size="-1">class cItem { </font></p>
        <blockquote> 
          <p><font face="Courier New, Courier, mono" size="-1">unsigned long type; 
            </font></p>
        </blockquote>
        <p><font face="Courier New, Courier, mono" size="-1">public: </font></p>
        <blockquote> 
          <p><font face="Courier New, Courier, mono" size="-1">cItem( unsigned 
            long );<br>
            cItem & operator= ( const cItem & ); <br>
            bool operator== ( const cItem & ) const; <br>
            bool operator< ( const cItem & ) const; <br>
            unsigned long getID( ) const; </font></p>
        </blockquote>
        <p><font face="Courier New, Courier, mono" size="-1">};</font></p>
      </blockquote>
    </blockquote>
  </blockquote>
</blockquote>
<p>This is it: a constructor (from an identifier), assignment and comparison operators, 
  and an identifier function. The private <font face="Courier New, Courier, mono">type</font> 
  variable is the item index that will be used later to get the item properties 
  from the item database.</p>
<p>Also note that the <font face="Courier New, Courier, mono">const</font> keyword 
  appears in a lot of places: it is quite a good programming practice to mark 
  as <font face="Courier New, Courier, mono">const</font> variables that should 
  not be modified by the function they are passed to, and as <font face="Courier New, Courier, mono">const</font> 
  functions those member functions that can be called without altering the object. 
  This way, when the <font face="Courier New, Courier, mono">cItem</font> class 
  will get bigger later on, it will become better to pass objects by reference, 
  in which case marking certain variables as <font face="Courier New, Courier, mono">const</font> 
  prevents those hard-to-spot bugs where one of those references is mistakenly 
  modified (they cause a compiler error instead).</p>
<p>As far as the behavior of the object goes, the implementation is quite straightforward:</p>
<blockquote> 
  <blockquote> 
    <blockquote> 
      <blockquote>
        <p><font face="Courier New, Courier, mono" size="-1"> cItem::cItem( unsigned 
          long t ) : type( t ) { } <br>
          cItem &cItem::operator =( const cItem &amp; o) { type = copy.type; return( 
          *this ); } </font></p>
      </blockquote>
    </blockquote>
  </blockquote>
</blockquote>
<p>The constructors and assignment operator are quite easy right now, since they 
  simply have to set the type to whatever is required. The type of the object 
  is used for comparisons as well:</p>
<blockquote> 
  <blockquote> 
    <blockquote> 
      <blockquote> 
        <p><font size="-1" face="Courier New, Courier, mono">unsigned long cItem::getID( 
          void ) const { return( type ); }<br>
          bool cItem::operator ==( const cItem & i ) const { return( type == i.type 
          ); } <br>
          bool cItem::operator <( const cItem &i ) const { return( type < i.type 
          ); } </font></p>
      </blockquote>
    </blockquote>
  </blockquote>
</blockquote>
<p>As the system grows, so will the item class and its members, but it will happen 
  transparently: new things will be added, but the behavior of the previously 
  implemented methods will remain the same.</p>
<blockquote> 
  <p><b><i>Item Piles</i></b></p>
</blockquote>
<p>Joe user would expect an item pile to have the following properties:</p>
<ul>
  <ul>
    <ul>
      <ul>
        <ul>
          <li>Turning an item into a pile with a single item</li>
          <li>Creating an empty pile</li>
          <li>Removing all the objects from a pile</li>
          <li>Adding a specific amount of a certain object to a pile</li>
          <li>Removing a specific amount of an object (if there are not enough, 
            then mention how many more objects would have been necessary).</li>
          <li>Counting the objects of a certain type.</li>
          <li>Combining two piles together.</li>
        </ul>
      </ul>
    </ul>
  </ul>
</ul>
<p>It is similar to associating to each item an integer (the amount of said item 
  present in the pile). As far as implementation goes, the user will need to access 
  (read and/or modify) at a moment's notice the amount of a particular item present 
  in the pile, which rules out all list containers and their <i>O(n)</i> random 
  access. </p>
<p>A vector where the indices are the item types would be the fastest method, 
  allowing <i>O(1)</i> access, but this approach has two problems : first, item 
  piles can contain as little as a single object, so allocating an array just 
  for one is wasted memory (especially when the vector's length is equal to the 
  number of different items in the game). Second, an item is not just a number: 
  it also has other properties than its type. Having two items with the same type 
  but with different properties in a single pile would lead to big problems (forcing 
  us into a vector of item containers later on). The only way to avoid this is 
  basing the access not on the type of object, but on the comparison functions 
  for the object.</p>
<p>The next best random access time is <i>O(log n)</i>, and can be implemented 
  with either sorted vectors or maps (or sets) without any serious memory hits. 
  Going the vector (or set) way would be impractical, because we would have to 
  store item-amount pairs in the container, making the implementation more complex 
  to code. A map, however, works perfectly.</p>
<p>This leads us to the class definition for item piles:</p>
<blockquote> 
  <blockquote> 
    <blockquote> 
      <blockquote> 
        <p><font size="-1" face="Courier New, Courier, mono">class cItemPack { 
          </font></p>
        <blockquote> 
          <p><font size="-1" face="Courier New, Courier, mono">std::map< cItem, 
            unsigned long > contents; </font></p>
        </blockquote>
        <p><font size="-1" face="Courier New, Courier, mono">public: </font></p>
        <blockquote> 
          <p><font size="-1" face="Courier New, Courier, mono"> cItemPack( cItem 
            & , unsigned long ); <br>
            void clear( ); <br>
            unsigned long add( const cItem & , const unsigned long ); <br>
            unsigned long remove( const cItem & , const unsigned long ); <br>
            unsigned long getAmount( const cItem & ) const; <br>
            const std::map< cItem, unsigned long > & getItems( ) const; <br>
            cItemPack & operator= ( const cItemPack & ); <br>
            cItemPack & operator+= ( const cItemPack & ); <br>
            cItemPack operator+ ( const cItemPack & ) const; </font></p>
        </blockquote>
        <p><font size="-1" face="Courier New, Courier, mono">};</font></p>
      </blockquote>
    </blockquote>
  </blockquote>
</blockquote>
<p>I have chosen to allow only positive amounts of objects. While this could have 
  been useful to represent a NPC merchant's &quot;plans&quot; about what to buy 
  or sell, it only adds unnecessary complication, since in almost all cases an 
  item pile represents a physical entity where negative amounts are impossible. 
  Next up is the implementation:</p>
<blockquote> 
  <blockquote> 
    <blockquote> 
      <blockquote>
        <p><font size="-1" face="Courier New, Courier, mono"> cItemPack::cItemPack( 
          cItem & i, unsigned long a ) { contents[i] = a; }<br>
          </font><font size="-1" face="Courier New, Courier, mono">void cItemPack::clear( 
          void ) { contents.clear( ); } <br>
          cItemPack & cItemPack::operator=( const cItemPack & o ) { </font></p>
        <blockquote>
          <p><font size="-1" face="Courier New, Courier, mono">contents = o.contents; 
            <br>
            return( *this );</font></p>
        </blockquote>
        <p><font size="-1" face="Courier New, Courier, mono"> }</font></p>
      </blockquote>
    </blockquote>
  </blockquote>
</blockquote>
<p>The two constructors, the clear function and the assignment operator are pretty 
  straightforward: there is nothing to specifically initialize. The &quot;from 
  item&quot; constructor showcases the ease of use associated with the map implementation: 
  items can actually be used as indices to access elements of the map. The following 
  function adds a definite amount of a certain object to the pile, and returns 
  the amount of objects after the addition:</p>
<blockquote> 
  <blockquote> 
    <blockquote> 
      <blockquote> 
        <p> <font face="Courier New, Courier, mono" size="-1">unsigned long cItemPack::add( 
          const cItem & i, const unsigned long a ) {</font></p>
        <blockquote> 
          <p><font face="Courier New, Courier, mono" size="-1">return( contents[i] 
            += a );</font></p>
        </blockquote>
        <p><font face="Courier New, Courier, mono" size="-1">}</font></p>
      </blockquote>
    </blockquote>
  </blockquote>
</blockquote>
<p>A function that returns the amount of a certain type of object could work as 
  well, but it would be convenient if it could be used on a <font face="Courier New, Courier, mono">const</font> 
  object. The problem with this is that the [] operator on a map is not a <font face="Courier New, Courier, mono">const</font> 
  function, so the code needs to work around this by using a const iterator, setting 
  it to a possible amount of that item in the map, and if the item is not present, 
  return 0.</p>
<blockquote>
  <blockquote> 
    <blockquote> 
      <blockquote> 
        <p><font face="Courier New, Courier, mono" size="-1"> unsigned long cItemPack::getAmount( 
          const cItem & i ) const { </font></p>
        <blockquote> 
          <p><font face="Courier New, Courier, mono" size="-1">std::map< cItem,unsigned 
            long >::const_iterator j;<br>
            j = contents.find( i );<br>
            if( j == contents.end( ) ) { return( 0 ); }<br>
            else { return( j->second ); } </font></p>
        </blockquote>
        <p><font face="Courier New, Courier, mono" size="-1">}</font></p>
      </blockquote>
    </blockquote>
  </blockquote>
</blockquote>
<p>However, a little bit more care needs to be taken of the removal function, 
  because we have to check if there are enough elements inside the pile to be 
  removed (you would not want the player to unequip 200 helmets and end up with 
  199 bonus headgear). The value returned by the function will be the amount of 
  items that could not be removed (because there were some missing) :</p>
<blockquote> 
  <blockquote> 
    <blockquote> 
      <blockquote> 
        <p><font size="-1" face="Courier New, Courier, mono">unsigned long cItemPack::remove( 
          const cItem & i, const unsigned long a ) { </font></p>
        <blockquote> 
          <p><font size="-1" face="Courier New, Courier, mono">unsigned long t 
            = contents[i]; <br>
            if( a > t ) { contents[i] = 0; return( a-t ); } <br>
            else { contents[i] = t-a; return( 0 ); } </font></p>
        </blockquote>
        <p><font size="-1" face="Courier New, Courier, mono">}</font></p>
      </blockquote>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -