Skip to main content
GameDev.net gamedev.net
🔒 Locked

Cross-Platform Game Engine....basic Class design questions

Started by Teknofreek Jul 5, 2002 at 4:44 AM 4 replies 1.4k views
Original Post
Teknofreek
Teknofreek
Ok, I''m in the very beginning of putting together a class structure that one day, far down the road, will hopefully develop into a nice cross-platform(Win, PS2, XBox, etc) game engine. And so I''m wondering, what are people''s general thoughts/practices on the class hierarchy design of such a beast. In other words, which things are general enough that you''d put it in the base class? Which things would you put in the base class, but declare as virtual? And how would you setup the build process for multi-platform use. My current thinking has been along these lines: 1. Declare a base "CGameEngine" class that has a few virtual functions....Init(), Shutdown(), and Update() 2. Derive my "CGame" class from "CGameEngine" 3. Assign the target platform to a member variable of CGame, like "m_TargetPlatform" 4. CGame would then implement the Init, Shutdown, and Update functions & call the appropriate stuff depending on the target platform Now, the only problem is that I now have a bad feeling that by using the target platform in at such a high level in the hierarchy that I''m gonna run into all kinds of scenarios where I have almost-duplicate code all over the place for each platform since for many tasks things will basically work the same except for a small detail here or there. So now, I''m thinking that it might be better to still assign CGame a "m_TargetPlatform" but not use it right away. Instead, I hold on to that assignment until I get to much lower level things, like SetupGraphics, or SetupPolys() or Render() and switch based on the target platform there. However, the problem I see with this approach (trying to keep things abstracted away from the platform for as long as possible) is that I may be backing myself into a corner where I''m forced to represent data in an odd manner in order to meet the varied needs of each platform. Perhaps in the end there is no perfect answer, but I''m curious as to what you guys all think about this kinda thing? -John PS: On a somewhat related tangent, is there any advantage or disadvantage to declaring functions as virtual in a class when in doubt as to whether you''ll want to change the implemenation further down the line? For instance, is there any kind of speed/size penalty hit for making them virtual?
John
Sandman
Sandman
It sounds like you want one binary to work on multiple platforms. This isn't going to happen, unless you are developing it in something like Java.

However, you can still design an engine that can be recompiled easily on different platforms. I would suggest that you design a IPlatform interface which exposes all the functionality that you need from the OS, but in a platform independent way. You then create a CLinuxPlatform, a CWindowsPlatform, etc, which all inherit the IPlatform interface and implement its functions.

e.g:


    
class IPlatform
{
public:

virtual ~IPlatform(void);

virtual bool Initialize(void)=0;
virtual IRenderer* CreateRenderer(const int width, const int height, const bool fullscreen) = 0;
virtual IInput* CreateInput(void)=0;
virtual IAudio* CreateAudio(void)=0;
virtual bool ShutDown(void)=0;

protected:

IPlatform(void) : initialized(false)
{
}

bool initialized;
};


The Renderer, Input and Audio parts of the program may all contain some platform specific code, although ideally you'll try and avoid this. Access them through non-platform specific interfaces though, and there shouldn't be any problems.

[edited by - Sandman on July 5, 2002 8:25:21 AM]
Teknofreek
Teknofreek
>>It sounds like you want one binary to work on multiple platforms. This isn''t going to happen, unless you are developing it in something like Java.<<

Oh god no, nothing like that!

>>However, you can still design an engine that can be recompiled easily on different platforms.<<

Yeah, that''s what I''m trying to do. Sounds like I didn''t do a very good job of explaining it

>>I would suggest that you design a IPlatform interface which exposes all the functionality that you need from the OS, but in a platform independent way. You then create a CLinuxPlatform, a CWindowsPlatform, etc, which all inherit the IPlatform interface and implement its functions.<<

Ok, the interface idea is a good one, and kind of along the lines of what I was thinking of doing. Thanks for the example! However, your example also illustrates what I was really trying to get at. If I make a base class like that, and each platform derives from it and implements it''s own thing right off the bat, then I''m going to basically end up with multiple versions of the whole program, right?

>>The Renderer, Input and Audio parts of the program may all contain some platform specific code, although ideally you''ll try and avoid this. Access them through non-platform specific interfaces though, and there shouldn''t be any problems.<<

Ok, so maybe it makes more sense to leave the base class rather generic and put use the interface method for the lower level stuff, like Render(), PlayBGMusic(), GetInput(), and so on. That way, all the higher level data, like Characters, state of the game, option settings, and so on are all handled in generic high-level classes. Then there could be interfaces for lower level classes, like an IAudio from which I could derive a new PCAudio, XBOXAudio, and so on class. Does that sound like it''ll work out well?

Also, there''s a slight penalty for looking up a virtual function, right? Are there any things that I should try using them for, where it could impact performance? For example, if I have a virtual Render() function and each platform class implements it''s own version of it, then am I going to incur a slight speed hit for every frame I render? If so, it seems like I should try to avoid doing that.

What do you think?

-John
John
Shannon Barber
Shannon Barber
There''s no easy answers for the architecture of your scene graph.

So long as you design it properly, you can use a virtual render based graph. Unreal is a commercial engine that took an extremely OO approach.

quote:

Now, the only problem is that I now have a bad feeling that by using the target platform in at such a high level in the hierarchy that I''m gonna run into all kinds of scenarios where I have almost-duplicate code all over the place for each platform since for many tasks things will basically work the same except for a small detail here or there.


This is almost inevetiable, though using multiple-inheritence & templates in the implementations of the OO interfaces can reduce this redundancy.

quote:

PS: On a somewhat related tangent, is there any advantage or disadvantage to declaring functions as virtual in a class when in doubt as to whether you''ll want to change the implemenation further down the line? For instance, is there any kind of speed/size penalty hit for making them virtual?


Well, yes there is a small penalty. Odds are good, that if a function call is aceptable, then a virtual call is also acceptable. This is non-issue if you are considering using a function pointer as the alternative - that''s essentially the same thing as a virtual.

quote:

However, the problem I see with this approach (trying to keep things abstracted away from the platform for as long as possible) is that I may be backing myself into a corner where I''m forced to represent data in an odd manner in order to meet the varied needs of each platform.


Well, a short time ago, OpenGL was the mantra for portability. It still is for most cases, except when you consider the DreamCast or XBox, which have good Dx support, but questionable OpenGL support. Supposedly the development houses have been hammering them both for better OpenGL support, but there''s only so much that can be done once the console has shipped.
Someone with direct experience may be able to more accurately state the current status. From what I understood, OpenGL worked, but there were missing extention to place it on par with the corresponding Dx version available. This didn''t mean it was impossible to implement as desired, it just means it''s more (platform specific) work.

Curerntly, there''s no freely available C++ compiler for the PS2 nor, for that matter, a freely available C compiler. (There is an assembler) - Unless you buy the linux kit (surely it must come with at least a C compiler, likely gcc?)

quote:

However, the problem I see with this approach (trying to keep things abstracted away from the platform for as long as possible) is that I may be backing myself into a corner where I''m forced to represent data in an odd manner in order to meet the varied needs of each platform.


The data loader can translate between forms when required. You have to do 90% of the work here by default.

Magmai Kai Holmlor

"Oh, like you''ve never written buggy code" - Lee

[Look for information | GDNet Start Here | GDNet Search Tool | GDNet FAQ | MSDN RTF[L] | SGI STL Docs | STFW | Asking Smart Questions ]

[Free C++ Libraries | Boost | ACE | Loki | MTL | Blitz++ | wxWindows| Spirit(xBNF)]
The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara
Jason Zelos
Jason Zelos
I started a game with the same idea, as much a learning excercise as anything, this is what I got.


cWindows //initializes window, and calls c2DEngine (or cGame etc)
- c2DEngine //main game code
- cGraphics //DX could be OpenGL, SDL etc
- cInput // DX
- cSound // DX
- cErrorLog //will be replaced by console eventually
- vector< cTile >
- list< cObject >
- list< cPawn >



To change to a different platform, I would need to replace cWindows with cLinux or cMACOS etc. The Window reference handles that need to be passed through to cGraphics are typedef'd.

like:

#if WINDOWS
typedef HWND WindowRef;
#elif MACOS
typedef WindowReference WindowRef;
#endif

So only the low level stuff gets the correct types without my intervention (aside from a recompile). I would in most cases also need to rewrite the low level code from DX to SDL or similar, I cannot see anyway round this.

I have other classes like cResource, as I keep all graphics within cGraphics only passing a reference, cGraphics needs to load the images itself. To help with this my cResource class parses a file for a given chunk, it then returns a pointer to the bytes pre-loaded in memory. The cGraphics class copy's these bytes onto a surface (for DX) and deletes the memory. This way I can keep the hardware dependent stuff hidden from the main class.

,Jay

Edit: vector and list(s) are more game classes, cTile, cObject and cPawn. I keep forgetting to put the html codes in for brackets rather than the brackets themselves.


[edited by - Jason Zelos on July 6, 2002 9:56:33 AM]
Dobbs
Dobbs
quote:

Ok, the interface idea is a good one, and kind of along the lines of what I was thinking of doing. Thanks for the example! However, your example also illustrates what I was really trying to get at. If I make a base class like that, and each platform derives from it and implements it''s own thing right off the bat, then I''m going to basically end up with multiple versions of the whole program, right?



As Magmai Kai Holmlor said, this is inevitable to some degree. There''s no getting around the fact that platform A needs to call function X to accomplish the same thing as platform B calling function Y, but before/after/between those calls there''s probably platform independent code. As I see it you have two choices:

1 - Interlace platform dependent and independent code using #ifdef. Advantages: most people find this easier in the short term than method #2. Disadvantages: will probably lead to ugly code, a pain to modify. You may run into situations where the platform dependent code is so different that this method simply isn''t feasible.

2 - Sandman''s suggestion of separate implementations of an interface class like IPlatform. As Magmai said, you can reduce code duplication with intelligent use of inheritance & templates. Advantages: eliminates the disadvantages of method #1 if done correctly.

quote:
>>The Renderer, Input and Audio parts of the program may all contain some platform specific code, although ideally you''ll try and avoid this. Access them through non-platform specific interfaces though, and there shouldn''t be any problems.<<

Ok, so maybe it makes more sense to leave the base class rather generic and put use the interface method for the lower level stuff, like Render(), PlayBGMusic(), GetInput(), and so on. That way, all the higher level data, like Characters, state of the game, option settings, and so on are all handled in generic high-level classes. Then there could be interfaces for lower level classes, like an IAudio from which I could derive a new PCAudio, XBOXAudio, and so on class. Does that sound like it''ll work out well?


Yes that sounds very good. The basic process you should have here is:

1. Figure out what code is unavoidably platform dependent.

2. Figure out what functionality you want in these areas. In other words, write out the class interface you want.

3. Get working on implementations of the interface. You seem to have steps 1 & 2 mostly down.

quote:
Also, there''s a slight penalty for looking up a virtual function, right? Are there any things that I should try using them for, where it could impact performance? For example, if I have a virtual Render() function and each platform class implements it''s own version of it, then am I going to incur a slight speed hit for every frame I render? If so, it seems like I should try to avoid doing that.


There is a small performance hit, but it''s probably not going to be your speed bottleneck.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.