1. Trang chủ
  2. » Công Nghệ Thông Tin

Object oriented Game Development -P8 pot

30 286 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 30
Dung lượng 320,19 KB

Nội dung

nism mentioned above, but by having a class rather than a function pointer we can carry state information about on a per-object basis. The loader also has an interface for loading the resource. Once we have a file in memory, the data are raw – we have to turn them into something useful, a process I call instantiation. Then we typically do some- thing with them. Then we (optionally) get rid of them. We can think of the lifecycle of a resource as the simple state machine shown in Figure 5.31. A resource starts off in a bundle. When the load request comes in, it starts loading and is in the Loading state. When loading completes, the resource is either in its raw data form or compressed; if the latter, it gets decompressed and is now in the Raw state. Then the resource is instantiated. This need not occur immediately – some resource types can stay raw until they’re needed. (At this point, the data can be optionally put into a Locked state; they cannot be dis- posed of until they are unlocked.) The resource is then Active and considered usable; after some time, the asset may be disposed of. This state management is handled within the DMGD component (see Figure 5.32). Now, loading a resource can be a time-consuming process. If we get several loads completing on the same frame, a whole series of call-backs will be initi- ated and we could find a drop in performance. One strategy to avoid this is to make the file server aware of how quickly the game is running and scale the number of call-backs it initiates in a frame by that: const int MAX_CALLBACKS_60FPS = 8; void FileServer::Update( Time aDeltaT ) { //… int iNumCallbacks = (MAX_CALLBACKS_60FPS * Time(1.0f/60.0f))/aDeltaT; if ( iNumCallbacks > MAX_CALLBACKS_60FPS) { iNumCallbacks = MAX_CALLBACKS_60FPS; } } Object-oriented game development196 Loading Compressed Locked Raw Active Expired Figure 5.31 State diagram showing the lifecycle of a resource. 8985 OOGD_C05.QXD 1/12/03 2:38 pm Page 196 The ResourceManager class drives the show. It is a templated class: you create a resource manager for each type of asset you want to load, and an associated loader for that type derived from the file server’s Loader class. The manager sup- ports the following basic operations: ● Load resource: clearly, you can’t do anything without loading the data! ● Get resource: acquires a pointer to a loaded resource, performing reference counting. This operation forces instantiation of the resource if it has not already been done. ● Free resource: decreases a resource’s reference count. Even if this hits zero, the resource won’t be deleted – it is marked as expired. ● Purge resource: the resource is forcibly removed from the manager, freeing all allocated resources irrespective of reference counting or any other status. ● Lock resource: prevents a resource from being freed. ● Unlock resource: guess. Now we delve a little deeper into the resource management issue. There are a number of problems to solve; some are pretty obvious, but the others are a bit subtler. First, let’s deal with the obvious one. Most games have considerably more assets than available RAM, and although they won’t all be in memory simultaneously, we may have to deal with the situation that we run out of the space we reserved for that type of asset. The component model for game development 197 ResourceManager<T> FileServer FSVR File server *Resources 1010101010100101 0101010101110101 0101010100101010 0010101001010101 Resource Data Loader ID Resource<T>Loader<T> Loader T DMGD ID Raw data Figure 5.32 DEMIGOD and the file server component diagrams. 8985 OOGD_C05.QXD 1/12/03 2:38 pm Page 197 So, if we do run out of room, what do we do? Well, we could throw up our hands in horror and assert or halt or quit or something else antisocial, but we can actually do a bit better than that. How about we look for an asset that is no longer being used and purge that, thus making room for the new guy? Nice plan! But how do we know what’s not being used? Well, we can look to see if any resources have the state Expired. If they have, then we can purge them with- out hesitation. But let’s suppose that there are no assets with this status. What then? One possibility is to keep a least recently used (LRU) counter. Each active resource’s counter is incremented every game loop and reset to zero when the resource is accessed. The asset with the highest LRU count is a candidate for removal. This technique will work nicely in some circumstances but not in others. In fact, the particular strategy for finding a purge candidate will depend on the mechanics of your game, so the best plan is to let the user create and configure the strategy. This scheme is outlined in Figure 5.33. The abstract purge strategy could look like this: // File: DMGD_PurgeStrategy.hpp namespace FSVR { class Resource; } namespace DMGD { class PurgeStrategy { public: virtual int Evaluate( FSVR::Resource * aResources, int iNumResources ) = 0; } Object-oriented game development198 ResourceManager<T> PurgeStrategy PurgeLRU DMGD *Strategies PurgeExpired Figure 5.33 Purge strategies for the DMGD cache. 8985 OOGD_C05.QXD 1/12/03 2:38 pm Page 198 The Evaluate() virtual function returns the index of a resource that can be purged, or –1 if none fulfils the criteria. Add to the resource manager the follow- ing method: void AddPurgeStrategy( PurgeStrategy * pStrategy ); and allow the resource manager to process the strategies in the order they are added (you could add a priority parameter, but that would overcomplicate the system): FSVR::Resource * pRsrcs = &m_Resources[0]; int iRsrcCount = m_Resources.size(); for( int j = 0; j < m_PurgeStrategies.size(); ++j ) { PurgeStrategy * pPS = m_PurgeStrategies[j]; int iPurge = pPS->Evaluate(pRsrcs,iRsrcCount); if (iPurge >= 0) { PurgeResource(i); break; } } OK, so we’ve decided that we want to get rid of a resource. What do we do? Something like void Resource<T>::Purge() { delete m_pData; } looks fine, but this might cause you a couple of really nasty problems. Consider the case that T is a model that has graphical data – textures or vertex buffers, maybe – in video memory. Now, most rendering systems today use multiple buffering to keep graphical updates tear-free and smooth (see Figure 5.34). The component model for game development 199 Buffer 2 (visible) Buffer 1 (current) Figure 5.34 Graphics being shown in the visible buffer cannot be deleted until (at least) the end of the display frame. 8985 OOGD_C05.QXD 1/12/03 2:38 pm Page 199 The act of deleting may well purge the video memory associated with a resource that is currently in the visible buffer, possibly resulting in a business- class ticket to Crashington when the buffers are switched. Moral: some data cannot be deleted immediately; you need to schedule their deletion at a point in time when it’s safe to do so. To achieve this, we add a garbage-disposal system to the resource manager (see Figure 5.35). When an item is to be purged, its data are removed from the resource man- ager and placed in an entry in the garbage list. An integer is used to count the number of frames that elapse; when this hits a specified value, the asset can be deleted and the entry is removed from the list. This is when the second really nasty problem hits. The purge operation can often be expensive, and if a number of resources are purged at the same time, then the frame rate can take a spike in the wrong direction. To get around this, we allow the user to specify the maximum number of purges that can take place per game loop. Note that because this is a parameterised class (i.e. the type T) we can set this maximum value on a per-class basis, so if one of your objects is particularly time-consuming to dispose of, then you can process fewer of them every cycle: template<class T> void GarbageDisposal<T>::Update( Time ) { int iPurgeCount = 0; Object-oriented game development200 ResourceManager<T> Garbage disposal int DMGD GarbageDisposal<T> *Entries Entry<T> CounterInstances T Figure 5.35 Object diagram for the DMGD garbage- disposal system. 8985 OOGD_C05.QXD 1/12/03 2:38 pm Page 200 iterator itKak = m_Garbage.begin(); iterator itEnd = m_Garbage.end(); while( itKak != itEnd ) { iterator itNxt = itKak; ++itNxt; Entry & anEntry = *itKak; if ( anEntry.m_iCounter > 0 ) { anEntry.m_iCounter; } else { delete anEntry.m_pInstance; m_Garbage.erase( itKak ); ++iPurgeCount; if ( iPurgeCount == m_iMaxPurgesPerFrame ) { break; } } itKak = itNxt; } } The last problem we’re going to look at in this section is one mentioned a few times here and elsewhere, in somewhat hushed tones: fragmentation. Refer back to the state diagram in Figure 5.31 and consider the dynamic memory opera- tions that can take place when a compressed resource is loaded: 1 The load operation itself creates a buffer and fills it with data from storage (one new, no deletes). 2 The decompressor reads the header from this buffer and allocates a new buffer to send the uncompressed information to (two news, no deletes). 3 Having decompressed the data, the original compressed buffer can now be deleted (two news, one delete). 4 The raw data are instantiated. Any number of dynamic memory operations can occur, depending on how complex the parameter class T is. It’s safe to say that at least one T is allocated (у three news, one delete). 5 Having instantiated the raw data, they can now be deleted. The object is now loaded and ready to use (у three news, two deletes). The component model for game development 201 8985 OOGD_C05.QXD 1/12/03 2:38 pm Page 201 I make that at least five dynamic memory operations per object. That is just beg- ging to fragment main RAM, and maybe even video RAM or sound RAM too. One feels that we can do better, and we can, but it’s a bit fiddly. The trick is to do a single new once per object in a buffer sufficiently large. See Figure 5.36 to get an idea of how this works. The compressed data are loaded at an offset from the start of the buffer. This offset is calculated by the compressor, which must calculate it so that no overwrite of compressed data occurs before they are used. A trial-and-error method is usually employed, as the operation is relatively quick and offline. The algorithm is illustrated by the following pseudo-code: offset = data_size – compressed_size while Decompress( compressed_data, offset )==ERR_OVERLAP offset += DELTA end while The value DELTA can be either a fixed constant or a heuristically determined value. The end result is a buffer of size offset + compressed_size, large enough to decompress the packed data without overwriting unused data. Note that this works with any linear compression scheme, for example RLE or LZ. At the cost of some extra RAM, we’ve eliminated two news and one delete. Now, let’s get rid of some more. This bit is harder because it depends on design- ing the classes within the resource manager in a particular way: their instantiation must cause no extra dynamic memory operations. Consider the following simplified class: class Mesh { private: Vector3 * m_avVertices; int * m_aiTriangles; int m_iNumVertices; int m_iNumTriangles; public: Mesh( int iNumVertices, int iNumTriangles ); }; Object-oriented game development202 Buffer start Expansion space Load address Contiguous data buffer 10101101010101000000000001010101010101011110101010101010101111111111 11101010101010101010101010101010101010101010101010101010101010101010 Compressed data Figure 5.36 Decompressing data on top of themselves. 8985 OOGD_C05.QXD 1/12/03 2:38 pm Page 202 We could write the constructor as follows: Mesh::Mesh( int iNumVertices, int iNumTriangles ) : m_avVertices( new Vector3 [ iNumVertices ] ) , m_aiTriangles( new int [ iNumTriangles ] ) , m_iNumVertices( iNumVertices ) , m_iNumTriangles( iNumTriangles ) { } This causes two dynamic memory operations – no use for our resource system. Consider the memory layout shown in Figure 5.37, however. By allocating the mesh and the vertex and triangle buffers in contiguous memory, instantiation becomes a matter of lashing up the pointers; no dynamic allocations are required: Mesh::Mesh( int iNumVertices, int iNumTriangles ) : m_avVertices( 0 ) , m_aiTriangles( 0 ) , m_iNumVertices( iNumVertices ) , m_iNumTriangles( iNumTriangles ) { m_avVertices = (Vector3 *)(this+1); m_aiTriangles = (int *)(m_avVertices+iNumVertices); } Of course, the memory has already been allocated (it’s the loading/decompres- sion buffer), so we need to use an in-place method to perform the construction: void Mesh::NewInPlace(int iNumVertices,int iNumTriangles) { m_iNumVertices = iNumVertices; m_iNumTriangles = iNumTriangles; m_avVertices = (Vector3 *)(this+1); m_aiTriangles = (int *)(m_avVertices+iNumVertices); } (Alternatively, we can use the C++ ‘placement new operator’.) The component model for game development 203 Triangle dataMesh Vertex data Figure 5.37 Keeping data contiguous helps to alleviate fragmentation. 8985 OOGD_C05.QXD 1/12/03 2:38 pm Page 203 Congratulations! We’ve reduced the dynamic memory operations down to just one new, at the cost of some extra RAM and some restrictions on the layout of the participant class. Now let’s tie up some of the loose ends I’ve dangled in this section. Figure 5.38 shows how a game might manage texture resources using components we’ve been discussing. In code, this is something like this: // File: TextureLoader.hpp #include <DMGD\DMGD_Loader.hpp> namespace REND { class Texture; } class TextureLoader : public DMGD::Loader<REND::Texture> { public: TextureLoader(); REND::Texture *Instantiate(char *pData,int iSize); void OnLoadComplete( DMGD::Resource * ); }; // File: ResourceManager.hpp #include <DMGD\DMGD_ResourceManager.hpp> #include <REND\REND_Texture.hpp> Object-oriented game development204 REND TextureLoader DMGD ResourceManager<T> DMGD::Loader<REND::Texture> DMGD::ResourceManager<REND::Texture> ResourceManager Texture manager Loader Texture TextureLoader Figure 5.38 The application’s resource management system implements concrete versions of the DMGD abstract classes. 8985 OOGD_C05.QXD 1/12/03 2:38 pm Page 204 class ResourceManager { public: /* Blah */ private: DMGD::ResourceManager<REND::Texture> m_TextureMgr; }; // File: TextureLoader.cpp #include "TextureLoader.hpp" #include <REND\REND_TextureLoader.hpp> #include <REND\REND_Texture.hpp> /*virtual*/ REND::Texture * TextureLoader::Instantiate( char * pRaw, int iRawSize ) { REND::TextureLoader aLoader; REND::Texture * pTexture = 0; // Note: all textures assumed to be in TGA format. aLoader.ParseTGA( pRaw, pTexture ); return( pTexture ); } /*virtual*/ void TextureLoader::OnLoadComplete(FSVR::Resource *pRes) { // Make the texture immediately available. REND::Texture * pTexture = Instantiate( GetRawData(), GetRawDataSize() ); DMGD::Resource<Texture> * pTextureResource = static_cast<DMGD::Resource<Texture> *>(pRes); pTextureResource->SetData( pTexture ); } 5.4.10 Newtonian physics Having a background in astrophysics, physics is a topic close to my heart, and I am not alone: there is a small but dedicated community of game developers who have an interest in raising the profile of physics in video games, heralding it as a technology whose time has come. True, there is a general movement towards realism in games. As consoles evolve and graphics become higher-resolution, use more realistic lighting models The component model for game development 205 8985 OOGD_C05.QXD 1/12/03 2:38 pm Page 205 [...]... pm Page 206 Object- oriented game development and support higher-detail models, any elements that behave in unnatural ways tend to stand out For a truly immersive experience (the physics proponents argue), objects in games need to behave more like objects in the real world: stacking, sliding, rolling, colliding, even bending, cracking and smashing To get these sorts of behaviours into the game, you need... Also observe that all Objects have mass, a fundamental property of Newtonian mechanics MATHS IsIntegrable PHYS Matrix33 Orientation Vector3 Position ReferenceFrame float Mass Object *Controllers Controller Figure 5.40 Bindings between the physics and maths components 8985 OOGD_C05.QXD 210 1/12/03 2:38 pm Page 210 Object- oriented game development Here’s the Object interface: class Object : public ReferenceFrame... * Typedefs, constants and enumerations */ /* * Lifecycle */ Object( ); Object( float fMass ); ~Object( ); /* * Polymorphic methods */ virtual void Update( MATHS::Integrator * pInt, float fDeltaT ) = 0; // Update state of object wrt time virtual void SetMass( float fMass ); // Sets the mass of an object virtual void SetInfiniteMass(); // Makes an object immovable /* * Unique methods */ float GetMass() const;... motivated to put physics in your game, there are many excellent resources to show you how to do the maths required to get up and flying Now that you know how to architect the C++ code, nothing can stop you except the laws of physics themselves (and in games at least, you can actually change them, Mr Scott) 219 8985 OOGD_C05.QXD 1/12/03 220 2:38 pm Page 220 Object- oriented game development Figure 5.46 Adding... Almost all games have physics of some description: objects move – they have positions and velocities if not accelerations; objects collide and bounce off each other or explode This suggests that game physics is nothing more than the updating of positions and velocities over time But clearly this isn’t what the hardcore mean, so let’s try the following definition: Physics in the context of games is a... equivalently control the motion of objects within the game By ‘consistently’, I mean that the rules are applied every game loop (or more frequently) There are no special cases for controlling the motion – we don’t, for example, stop at some point and switch to (say) animation By ‘equivalently’, I mean that the rules are applied to all the objects in the set If the objects are required to have differing... interface allows us to share a few integrators between many objects, as opposed to having either an integrator per object or one integrator for all objects (a yukky singleton integrator) From the Object base class, we can now start building concrete classes There are two families of these – the body classes that are solids, and the rest A Body is just an Object with mass properties and some underlying geometrical... inertia tensors are simple to represent) Box 3 Box 2 r12 m1 Box 1 Figure 5.42 The car modelled as point masses r23 m2 m3 8985 OOGD_C05.QXD 1/12/03 212 2:38 pm Page 212 Object- oriented game development As I said earlier, if you use physics in games it quickly gets pretty technical Of the three methods, the third is the most flexible and as a big bonus, the point mass model can be updated easily in real time... things, such as falling through surfaces (we may want a ball to roll over an undulating terrain, for example) Or we may want it to 213 8985 OOGD_C05.QXD 1/12/03 214 2:38 pm Page 214 Object- oriented game development follow some other object We do this by means of constraints, and we allow a body to have an arbitrary number of them Constraints are similar to controllers, but they evaluate external forces rather... 1/12/03 216 Figure 5.45 Adding the rigid body class to the physics component 2:38 pm Page 216 Object- oriented game development MATHS IsIntegrable Matrix33 Acceleration Vector3 Quaternion Angular momentum Velocity PHYS Orientation Orientation Position ReferenceFrame float State Total force Total torque State Mass Object *Controllers Controller RigidBody Restitution Inertia tensor Body Body *Constraints . iPurgeCount = 0; Object- oriented game development2 00 ResourceManager<T> Garbage disposal int DMGD GarbageDisposal<T> *Entries Entry<T> CounterInstances T Figure 5.35 Object diagram. can introduce forces implicitly via controllers, a physical object having a number of these pushing them around: Object- oriented game development2 08 Z' Y' X' Z Y X (x,y,z) Figure. as Object- oriented game development2 10 8985 OOGD_C05.QXD 1/12/03 2:38 pm Page 210 forwards Euler or Guassians). This choice of interface allows us to share a few integrators between many objects,

Ngày đăng: 01/07/2014, 15:20