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

Object oriented Game Development -P8 pot

30 287 0
Tài liệu đã được kiểm tra trùng lặp

Đ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

Tiêu đề Object-oriented Game Development -P8 Pot
Trường học University of Digital Games Development
Chuyên ngành Object-Oriented Game Development
Thể loại graduation project
Năm xuất bản 2003
Thành phố Unknown
Định dạng
Số trang 30
Dung lượng 320,19 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

One strategy to avoid this is tomake the file server aware of how quickly the game is running and scale thenumber of call-backs it initiates in a frame by that: const int MAX_CALLBACKS_6

Trang 1

nism mentioned above, but by having a class rather than a function pointer wecan carry state information about on a per-object basis The loader also has aninterface 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 thelifecycle 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 thispoint, the data can be optionally put into a Lockedstate; they cannot be dis-posed of until they are unlocked.) The resource is then Activeand consideredusable; after some time, the asset may be disposed of

This state management is handled within the DMGD component (seeFigure 5.32)

Now, loading a resource can be a time-consuming process If we get severalloads 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 tomake the file server aware of how quickly the game is running and scale thenumber 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;

}}

Loading

Figure 5.31State diagram showing

the lifecycle of a

resource

Trang 2

The ResourceManagerclass 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 Loaderclass 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 notalready 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

ResourceManager<T>

FileServerFSVR

File server

*Resources

1010101010100101010101010111010101010101001010100010101001010101

IDRaw data

Figure 5.32DEMIGOD and the fileserver componentdiagrams

Trang 3

So, if we do run out of room, what do we do? Well, we could throw up ourhands in horror and assert or halt or quit or something else antisocial, but wecan actually do a bit better than that How about we look for an asset that is nolonger being used and purge that, thus making room for the new guy? Niceplan! 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 Whatthen? One possibility is to keep a least recently used (LRU) counter Each activeresource’s counter is incremented every game loop and reset to zero when theresource is accessed The asset with the highest LRU count is a candidate forremoval This technique will work nicely in some circumstances but not inothers 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 andconfigure the strategy This scheme is outlined in Figure 5.33

The abstract purge strategy could look like this:

// File: DMGD_PurgeStrategy.hppnamespace FSVR

{class Resource;

}

namespace DMGD{

class PurgeStrategy{

DMGD cache

Trang 4

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?

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)

Buffer 2 (visible)Buffer 1 (current)

Figure 5.34Graphics being shown inthe visible buffer cannot

be deleted until (at least)the end of the displayframe

Trang 5

The act of deleting may well purge the video memory associated with aresource that is currently in the visible buffer, possibly resulting in a business-class ticket to Crashington when the buffers are switched Moral: some datacannot 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 ager and placed in an entry in the garbage list An integer is used to count thenumber of frames that elapse; when this hits a specified value, the asset can bedeleted and the entry is removed from the list

man-This is when the second really nasty problem hits The purge operation canoften 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 placeper 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 isparticularly time-consuming to dispose of, then you can process fewer of themevery cycle:

T

Figure 5.35Object diagram for

the DMGD

garbage-disposal system

Trang 6

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 tosay 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)

Trang 7

I make that at least five dynamic memory operations per object That is just 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

beg-do a single new once per object in a buffer sufficiently large See Figure 5.36 toget 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 nooverwrite of compressed data occurs before they are used A trial-and-errormethod is usually employed, as the operation is relatively quick and offline Thealgorithm is illustrated by the following pseudo-code:

offset = data_size – compressed_sizewhile Decompress( compressed_data, offset )==ERR_OVERLAPoffset += DELTA

end while

The value DELTA can be either a fixed constant or a heuristically determinedvalue The end result is a buffer of size offset + compressed_size, largeenough to decompress the packed data without overwriting unused data Notethat 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: theirinstantiation must cause no extra dynamic memory operations Consider thefollowing simplified class:

class Mesh{

Contiguous data buffer

10101101010101000000000001010101010101011110101010101010101111111111 11101010101010101010101010101010101010101010101010101010101010101010

Compressed data

Figure 5.36Decompressing data on

top of themselves

Trang 8

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 contiguousmemory, instantiation becomes a matter of lashing up the pointers; no dynamic

allocations are required:

Mesh::Mesh( int iNumVertices, int 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’.)

Triangle dataMesh Vertex data

Figure 5.37Keeping data contiguoushelps to alleviate

Trang 9

Congratulations! We’ve reduced the dynamic memory operations down tojust 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 Figure5.38 shows how a game might manage texture resources using componentswe’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 * );

Loader

Texture

TextureLoader

Figure 5.38The application’s

resource management

system implements

concrete versions of the

DMGD abstract classes

Trang 10

// Note: all textures assumed to be in TGA format.

aLoader.ParseTGA( pRaw, pTexture );

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 consolesevolve and graphics become higher-resolution, use more realistic lighting models

Trang 11

and support higher-detail models, any elements that behave in unnatural waystend to stand out For a truly immersive experience (the physics proponentsargue), objects in games need to behave more like objects in the real world: stack-ing, sliding, rolling, colliding, even bending, cracking and smashing.

To get these sorts of behaviours into the game, you need physics Let’s bequite clear what we mean by ‘physics’, though Almost all games have physics

of some description: objects move – they have positions and velocities if notaccelerations; objects collide and bounce off each other or explode This sug-gests that game physics is nothing more than the updating of positions andvelocities over time But clearly this isn’t what the hardcore mean, so let’s trythe following definition:

Physics in the context of games is a set of rules that consistently and equivalently control the motion of objects within the game.

By ‘consistently’, I mean that the rules are applied every game loop (or more quently) There are no special cases for controlling the motion – we don’t, forexample, stop at some point and switch to (say) animation

fre-By ‘equivalently’, I mean that the rules are applied to all the objects in theset If the objects are required to have differing behaviour, they do so by alteringtheir response to the rules via internal parameters

Note that we’re not just thinking of the motion of vehicles in a drivinggame or the balls in a pool game We could also consider using cellularautomata to generate game data But whatever we find ourselves using, we’regoing to realise one thing: we’re very much at the mercy of the rules we choosebecause they generate behaviour; they are not behaviour in themselves Forbetter or worse, the behaviours we get as a result of running our rules may bethe ones we want and/or never expected There can be a lot of work in mitigat-ing the unwanted emergent behaviour; that is the cost of the physics Thebenefit is that when we get the behaviour right for one object, we get it right forall objects that follow the same rules

Now to get to the point: Newtonian physics This is a set of rules that trols the motion of game objects by applying forces to them It’s a simpleenough principle, but there’s a snag: physics is really quite hard Even in asimple world of inflexible boxes and planes, physics can be really tough Forexample, stacking a number of boxes on top of each other in real time could beenough to grind some computers to a halt; and even then, it’s hard to makestable to the extent that it behaves just like a real stack of boxes And if rigidcubes are hard to get working, what does that say about more complex systemssuch as soft, jointed humans?

con-The long and the short of it is that if you want to use Newtonian physics in

a game, then you’ve got to have a darn good reason for doing so For example,

if you’re writing an adventure or platform game, you may be tempted to make apuzzle out of platforms, weights and ropes that are physically simulated Once

Trang 12

it’s set up, it just works Well, that’s the benefit; the cost is that it’s very much

harder to control and constrain than a puzzle based on a state machine and

ani-mations The designer of the puzzle must have a good understanding of the

mechanics of the problem, and in more complex situations they must have a

deep understanding of how to change the physical properties of the objects in a

system to be able to achieve a desired result This is specialist knowledge, and

it’s not a simple task Think about making a big nasty Boss spider walk, applying

a sequence of forces and torques at the joints in the legs to get it perambulating

over an undulating terrain It’s decidedly not easy

Introducing physics into a game must start at the art and design level

Masses and mass distributions must be set within the editing environment

where the game is constructed The first problem that a designer may encounter

is ‘physics creep’ Suppose that one wishes to limit the number of objects that

are physically simulated Those objects had better not be able to interact with

non-simulated objects, as the result may be decidedly unrealistic But we were

using physics to add realism! So, inevitably, it’s hard to avoid more and more of

your simulation space using Newtonian physics as a means of updating itself

Keeping control can be tough

Something else to watch out for when planning a game with physics onboard is units Most game art is created to an arbitrary scale – as long as all

objects are the right size relative to each other, everything will work out just

fine And it’s true that Newtonian physics is oblivious to what the units are – it

still works.3However, physics programmers may well feel more comfortable

with SI units (metres, kilograms, seconds) than other systems, so artwork should

be prepared with this in mind It seems pointless to swallow CPU cycles and

create extra confusion during the game converting between unit systems when

some forethought would save you the trouble Of course, a clever C++

program-mer might like to write a units class that did all the conversions for you

transparently It’s a fun exercise, but that doesn’t make it clever Choose game

world units at the start of development and stick to them

If physics is hard for the designer, then it’s pretty hellish for the mer Again, specialist knowledge may be required – not just physics, but maths

program-well beyond high-school level, maybe even beyond graduate level Even with

that knowledge, the systems can be hard and time-consuming to stabilise to the

extent that they can be used in a commercial game Attempts – such as

Dreamworks’ 1998 Jurassic Park: Trespasser – have not really succeeded either

technically or commercially

However, if you’ve read all that and have decided that you really want to useNewtonian physics in your game, then here is a component that will get you

started If you really want to use high-power physics, you would do well to

con-sider a middle-ware solution – two currently popular systems are Mathengine and

Havok They may allow you to sidestep many months of mental pain.

3 A technical note Only in the SI unit system does Newton’s law F=ma hold true In other systems, it

Trang 13

Right, let’s get down to designing a physics system Since Newtonianphysics is about updating positions and directions (we want things to spin,don’t we?), let’s start by embedding those into a class called a ReferenceFrame.This is a term you meet a lot in physics – it’s precisely what is meant here, so

why not? Figure 5.39 shows the reference frame {(x,y,z),(X ′,Y′,Z′)} – the first

numbers denote position with respect to some other reference frame

{(0,0,0),(X,Y,Z)}, and the second set are vectors representing orientation.

A mention needs to be made of coordinate systems here We have ately chosen to keep our rendering systems and our simulations systemsseparate, and there is a danger that whoever writes the graphics code may nothave the same notions as the person writing the physics code about which way

deliber-the z-axis goes or how angles are measured Many hours of hair-pulling

debug-ging can result, because physics code is full of vector cross-products that haveambiguities in their results (there are two vectors 180 degrees apart that satisfyorthogonality with the arguments) Although it is quite feasible to write ageneric coordinate transformation component, for performance reasons it isbetter to avoid doing so Programmers need to agree on the conventions of axialdirections and senses of rotation about very early on in development

Remember way back when we were looking at the MATHS component, wemet this thing called an Integrator? No? Well, better rewind and review Anintegrator is used to update a dynamic system over time, taking into accountthe variable rates of change of the internal variables over time If we want toupdate our physical object over time, then we want one of those, which meansinheriting the IsIntegrableproperty class Such is the power of componentdesign, we’re now building a huge amount of abstract power into a class andturning it into concrete functionality

Newtonian physics is nothing without forces, and something generally has

to apply those forces At this point in the design, we have only an abstractobject – we don’t know if it’s soft (in which case, forces will deform it) or rigid(forces will only move it) So we defer explicit mention of forces until later inthe hierarchy, because the force interface will be determined by the type of body(a virtual function isn’t really sufficiently flexible, since it has a fixed signature).However, we can introduce forces implicitly via controllers, a physical objecthaving a number of these pushing them around:

Z'

Y'

X'

ZY

Trang 14

Your subclass of Controllereither will know what type of Objectis being

sup-plied or will be able to work it out via your own typing mechanism The

ComputeForcesAndTorques()method should return truealways, unless

some-thing catastrophic happens, in which case returning falseallows the Object

update to be terminated

Figure 5.40 summarises the design so far

Notice the use of multiple inheritance in PHYS::Object Worry not: it will

be nigh invisible by the time we get to classes that really do things Also observe

that all Objectshave mass, a fundamental property of Newtonian mechanics

Vector3IsIntegrable

Controller

float

*ControllersMass

Figure 5.40Bindings between thephysics and mathscomponents

Trang 15

Here’s the Objectinterface:

class Object : public ReferenceFrame, public ::MATHS::IsIntegrable{

// 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;

void AddController(Controller * pController);

void RemoveController(Controller * pController);

// Controller interface};

In the Update()method, we pass in an arbitrary integrator to calculate the newstate Some objects in the game may require very accurate and stable integrators(for the technically minded, Runge–Kutta high-order, or even an implicit inte-grator such as backwards Euler); some may require simple ones (such as

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