2D sidescroller: Scene Graph (Code Design)

Started by
11 comments, last by OrangyTang 17 years, 9 months ago
Hi all, I'm writing a 2D sidescrolling platform-ish game (Think mario, etc), and I'm writing an engine for it (And any other projects). I've come to the scene graph, and I'm wondering how to split things up. So far I have this:

+ SceneObject (Abstract)
  + SceneObject2d (Abstract)
  | + Sprite
  | | + AlphaSprite
  | |   + AnimatedSprite
  | + Map
  + SceneObject3d (Abstract, not implemented)
Although I'm not sure if that's the best way to handle things. Should I make AlphaSprite and Sprite the same thing? It makes the engine code simpler if they're different (Because I use ID3DXSprite, rendering alpha blended needs done slightly differently from normal sprites), and also I'm not convinced AnimatedSprite is going to work too well where it is. All 2D objects have a priority setting so I can nicely handle overlapping objects. Alpha'd objects get sorted back-to-front and non alpha'd objects get sorted front to back. Both are also sorted by texture. Any comments / suggestions on how to improve it? Cheers, Steve
Advertisement
If the rendering is really the only diffrence between Alpha Sprite and Sprite you could probably just test the bit depth of the image loaded to see if the sprite needs to support alpha blending or not. Then if you decide to add an alpha channel to a sprite you won't have to go into code and change its class.
CONSRUCTOR{    LOAD_IMAGE()    if(IMAGE.channels == 4)        alpha = true;    else alpha = false;}DRAW_ROUTINE{    if(!alpha)        OPAQUE_DRAW();    else        ALPHA_DRAW();}


this also means you could have animated opaque sprites which could be an optomization over forcing all animated sprites to have an alpha channel.
____________________________"This just in, 9 out of 10 americans agree that 1 out of 10 americans will disagree with the other 9"- Colin Mochrie
The reason I have alpha'd and opaque sprites seperated, is that alpha'd sprites need sorted back to front so alpha works, but normal sprites are sorted front to back to take maximum advantage of the Z-buffer.

SceneObject2d has 3 functions which are used in rendering:
  • FirstRender() - Called before any objects of this type (Sprite, alpha sprite, etc) are rendered, so the object can set up the render pipeline for rendering batches of this type
  • Render() - Called once per object to render
  • EndRender() - Called after the last object of this type has been rendered, so objects of this type can reset the render pipeline, submit batches, etc.

    For sprites, FirstRender calls ID3DXSprite::Begin(), telling it to sort front-to-back and by texture, Render() calls ID3DXSprite::Draw() and EndRender() calls ID3DXSprite::End(), which all makes the best possible usage of the GPU.
    Alpha'd sprites are exactly the same, except I tell ID3DXSprite::Begin() to sort them back-to-front, sort by texture, and enable alpha blending.

    If I combine them into one class, I still need to distinguish between the two types so I can efficiently batch them.
  • Thats not actually a scene graph though is it? All you've done is define an inheritance tree and not really describe how you're going to use it.

    Firstly I don't think AlphaSprite and AnimatedSprite should inherit from Sprite. AlphaSprite and Sprite should probably both come off SceneObject2D, and remove AnimatedSprite totally. I'd handle animation separately (probably as some kind of animation sequence/object which could be attached to a sprite).

    'Map' is a pretty funny class to have in there. Ideally your game map would be constructed out of SceneObject2D and SceneObject3D types. Why do you need a Map like that?

    You've also not addressed what are (IMHO) the two most difficult tasks of a 2d renderer - handling state/shaders for different sprites (which includes figuring out how to specify multiple textures and coords for them), and how you'll handle multipass and render-to-texture.
    Quote:Original post by OrangyTang
    Thats not actually a scene graph though is it? All you've done is define an inheritance tree and not really describe how you're going to use it.
    The scene graph maintains two internal lists (a std::multiset), 2D objects and 3D objects (3D objects I'm leaving just now). It goes through each list and renders the object in turn.
    The set sorts objects as they're added to the scene graph, and it (currently) sorts by subtype (Sprite, alpha sprite, map, etc), and then by priority. Maps are rendered first, then sprites and alpha sprites.

    Quote:Original post by OrangyTang
    Firstly I don't think AlphaSprite and AnimatedSprite should inherit from Sprite. AlphaSprite and Sprite should probably both come off SceneObject2D, and remove AnimatedSprite totally. I'd handle animation separately (probably as some kind of animation sequence/object which could be attached to a sprite).
    That was something I was thinking of last night. I just thought it might be nicer if the engine handled all that.

    Quote:Original post by OrangyTang
    'Map' is a pretty funny class to have in there. Ideally your game map would be constructed out of SceneObject2D and SceneObject3D types. Why do you need a Map like that?
    It's an efficiency thing. A map is made from a bunch of tilesheets, jammed onto larger (2048x2048, or something similar) textures, and is one dynamic vertex buffer for rendering. I could do the same with sprites perhaps, but this way just seems more efficient.

    Quote:Original post by OrangyTang
    You've also not addressed what are (IMHO) the two most difficult tasks of a 2d renderer - handling state/shaders for different sprites (which includes figuring out how to specify multiple textures and coords for them), and how you'll handle multipass and render-to-texture.
    ID3DXSprite will batch sprites based on texture for me, so I have less to worry about there. As for handing different states / shaders for different sprites, I'm not sure what you mean. All sprites use the same states / shaders, the only difference between them is the texture (which ID3DXSprite handles for me).

    Thanks for the replies [smile]
    Quote:Original post by Evil Steve
    Quote:Original post by OrangyTang
    Thats not actually a scene graph though is it? All you've done is define an inheritance tree and not really describe how you're going to use it.
    The scene graph maintains two internal lists (a std::multiset), 2D objects and 3D objects (3D objects I'm leaving just now). It goes through each list and renders the object in turn.
    The set sorts objects as they're added to the scene graph, and it (currently) sorts by subtype (Sprite, alpha sprite, map, etc), and then by priority. Maps are rendered first, then sprites and alpha sprites.

    That sounds good, I'm starting my own 2d renderer sometime soon and I'm planning on doing the same thing for this bit.

    Animation still can be handled by the 'engine', but I think inheritance is the wrong way. Animation has-a Sprite, not is-a Sprite and so I think composition would be a better approach. Maybe the scenegraph root could hold on to a seperate collection of animations and handle updating them all at the same time.

    Quote:
    It's an efficiency thing. A map is made from a bunch of tilesheets, jammed onto larger (2048x2048, or something similar) textures, and is one dynamic vertex buffer for rendering. I could do the same with sprites perhaps, but this way just seems more efficient.
    ID3DXSprite will batch sprites based on texture for me, so I have less to worry about there. As for handing different states / shaders for different sprites, I'm not sure what you mean. All sprites use the same states / shaders, the only difference between them is the texture (which ID3DXSprite handles for me).

    This awkward distinction between your own batching (via Map) and external batching (via DxSprite) sounds like a really bad idea IMHO. Personally I'd suggest something like a 'Renderable' interface (for both 2d and 3d) which is managed by the scenegraph. Maps, Sprites and SceneObject3d could all implement this interface and internally handle any sort of batching you want. I don't see why you need such a deep inheritance tree where lots of the levels aren't doing anything.

    Sprites therefore wouldn't do any batching, but be just drawn straight away. Maps (a bad name, but I'll stick with it so this makes sense) would consist of a whole bunch of Sprites (much like a Composite object) and handle the batching of all of them. A game level then might have a whole bunch of Maps, maybe one for the background sprites, one for the tiles, one for the gameplay sprites, and one for the hud. Maybe something like a cursor would be a standalone sprite with it's priority set to be over everything else. All sprites therefore would render using a subsection of a tilesheet (even if sometimes the 'tilesheet' is just a single sprite).

    You might not need RTT, but you'll certainly need some kind of state management, as you'll at least want to do different blend modes. If you've only got a small amount of state changing then you could make all Renderables setup and put back the state to known defaults.

    You also havn't got anything to deal with particle systems, which I think are something of a special case in terms of rendering and batching. I'd probably ignore the sprite stuff for particles and just have a ParticleSystem which implements the Renderable interface.
    Code Design can be great and all, but if you make it more complicated for the code-user (probably yourself), then why do it? I think that the code-user will appreciate if the amount of types was kept little.

    Don't complicate your code, just make the alpha rendering a 'hack'. No one (code users) gives a f-ck except you.
    Quote:Original post by Pipo DeClown
    Code Design can be great and all, but if you make it more complicated for the code-user (probably yourself), then why do it? I think that the code-user will appreciate if the amount of types was kept little.

    Don't complicate your code, just make the alpha rendering a 'hack'. No one (code users) gives a f-ck except you.

    Premature pessimisation.

    I think the distinction between Alpha/Non-Alpha sprites is fine - it means he can sort front-to-back for non-Alpha sprites to reduce overdraw. Though the only difference between the two, it seems to me, is that one little flag. Though anything else I can think of would truly be an overcomplication.

    I'd agree completely with OrangyTang though - the animated sprite should NOT be there, because an animation (hypothetically) would be composed of multiple images, whereas a sprite seems to simply be a sprite.

    Additionally, an animation is going to require an update(DWORD timestep) somewhere in there, which the other sprite classes don't. I think it'd be reasonable to move the animation out of the scene graph entirely and implement it in a high-level way, by actually manipulating objects within the scenegraph, instead of being one.

    But lol? What do I know. I don't even have that DS devkit that I'm going to stealzors from yuo Steve... Hur hur hur. *buys a plane ticket to edinburgh*
    You might be right with the Alpha sorting, but couldn't that be solved within the normal sorting?

    Anyway, I stand corrected.
    Quote:Original post by Mushu
    I think the distinction between Alpha/Non-Alpha sprites is fine - it means he can sort front-to-back for non-Alpha sprites to reduce overdraw. Though the only difference between the two, it seems to me, is that one little flag. Though anything else I can think of would truly be an overcomplication.

    I'd agree completely with OrangyTang though - the animated sprite should NOT be there, because an animation (hypothetically) would be composed of multiple images, whereas a sprite seems to simply be a sprite.

    If I wanted to be really awkward I'd mention the case where you've got a animation consisting of a whole bunch of sprite, some of which have alpha and some don't. The first reaction may be "don't do that" but it could have some valid uses.

    In this case having a Sprite with an isAlpha flag is obviously much better than having derived classes, and simpler to code too. [grin]

    This topic is closed to new replies.

    Advertisement