Delphi Sprite Engine – Part 2 – The Scene.

pacman

[If you’re new to this series, you may wish to skip ahead to part-7, where I’ve done a partial ‘start-over’]

This is the second part in my short series of posts on building a sprite engine with Delphi. In this part we’re going to resolve goal number 4: Hierarchical scene composition.

Hierarchical Scene Composition

Hierarchical scene composition means that we build up the scene that we’re rendering from components which are arranged in a tree-like structure, a hierarchy.  Objects in the tree are drawn in the order they appear as we recursively render the tree. Lets take a look at an example tree for further clarification. In this scene we’re going to render a bird flying over a city-scape…

schenehierarchy

In this scene, we start by rendering the “Scene Root” node, which doesn’t render anything it’s self, but will call on all of it’s children to render their content. In this case, the root node has only one child “Clear backdrop” which is a component that will erase all of the content of our scene, and paint a backdrop for our scene. Suppose the backdrop is a block of sky blue to represent some sky.

Next up, the “Clear Backdrop” component renders each of it’s children in order. Starting with “Clouds” which renders some sprites of clouds in the upper part of the screen, then rendering “Buildings” which puts the silhouette of some buildings along the bottom of the screen, and finishing with “Bird” which renders a bird in the sky.

This scene composition technique is used to ensure that components are rendered in the order they need to be, in order to build the scene up from back to front. Otherwise known as ‘painters algorithm’ as this is much the same way a painter constructs a painting, starting at the back and working forwards.

What the hierarchy also does for us, is it offers the opportunity for each component to alter the way it’s children are drawn. For example, if we made the ‘Clear Backdrop’ component in the above scene invisible, it could choose not to render it’s children, making them invisible also. Perhaps the position or rotational properties of the child components could be adjusted also.

In order to create this hierarchical structure, we’re going to start with three classes.

  1. TSceneComponent – This represents any scene component which needs to be rendered. It will have the Render() method which will be called as the scene is being composed, and it’ll be available for owning other scene components, making the hierarchy possible.
  2. TSceneRoot – Derrived from the TSceneComponent, this class will represent the root node of the hierarchy, into which we’ll place the other components which make up the scene.
  3. TSpriteScene – This is a component which we’ll install into the IDE, which has a Root property (TSceneRoot). It therefore hosts the scene we’ll be rendering.

Before we get on with writing these three classes however, there is something else we’ll need. The TSceneComponent is responsible for rendering objects to our screen, and so it’ll need some connection to the context that we’re going to render onto.

Now, I’ve read ahead some and I know that we’ll need the ability to pass more information to the TSceneComponent than just the rendering context. Lets create a context class of our own, which will be responsible for passing all of the required information through our scene components. Create a new unit inside your sprite engine package and add this content…

unit unitViewportContext;

interface
uses
  FMX.Types3D;

type
  TViewportContext = class
  private
    fContext3D: TContext3D;
  public
    property Context3D: TContext3D read fContext3D write fContext3D;
  end;

implementation

end.

This unit provides a wrapper class ‘TViewportContext’ which at present has only one property ‘Context3D’ which is a TContext3D class. This is the very same TContext3D class which we rendered to in the previous article. We’ll see how this is used later, for now, lets create those scene hierarchy classes…

unit unitSpriteScene;

interface
uses
  classes,
  unitViewportContext;

type
  {# TSceneComponent represents any component of the scene to be rendered. }
  TSceneComponent = class( TComponent )
  protected
    procedure Render(Context: TViewportContext); virtual;
  public
    constructor Create( aOwner: TSceneComponent ); reintroduce; virtual; 
  end;

  {# Root node of the scene }
  TSceneRoot = class(TSceneComponent);

  {# TSpriteScene Represents the entire sprite scene to be rendered }
  TSpriteScene = class( TComponent )
  private
    fRoot: TSceneRoot;
  public
    constructor Create( aOwner: TComponent ); override;
    destructor Destroy; override;

    procedure Render( Context: TViewportContext );
  public
    property Root: TSceneRoot read fRoot;
  end;


procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('SpriteEngine', [TSpriteScene]);
end;

{ TSpriteScene }

constructor TSpriteScene.Create(aOwner: TComponent);
begin
  inherited Create(aOwner);
  fRoot := TSceneRoot.Create(nil);
end;

destructor TSpriteScene.Destroy;
begin
  fRoot.Free;
  inherited Destroy;
end;

procedure TSpriteScene.Render(Context: TViewportContext);
begin
  fRoot.Render(Context);
end;

{ TSceneComponent }

constructor TSceneComponent.Create(aOwner: TSceneComponent);
begin
  inherited Create(aOwner);
end;

procedure TSceneComponent.Render(Context: TViewportContext);
var
  idx: int32;
  Ref: TComponent;
begin
  for idx := 0 to pred(ComponentCount) do begin
    Ref := Components[idx];
    if (Ref is TSceneComponent) then begin
      TSceneComponent(Ref).Render(Context);
    end;
  end;
end;

end.

A few things to notice about the unitSpriteScene unit.

  • The TSceneComponent reintroduces the standard TComponent constructor to ensure that a scene component may be owned by another TSceneComponent only.
  • The TSceneComponent.Render() method does nothing other than looping the child components seeking child TSceneComponent classes, and calling their Render() method.
    This means that when we derive from TSceneComponent we can call the inherited Render() method to draw all children.
  • The Render() method accepts our TViewportContext class as a parameter. This means that in order to render our scene, the TViewport component that we developed in the previous post will need to create a TViewportContext and pass it into the Render() method of TSpriteScene.
  • The TSpriteScene component is the one which is registered and placed into the Tool Palette. This will allow us to create a scene which may be attached to the TViewport component by a property reference. We’ll see how this is important later.

Now that we have this hierarchical scene, we need to alter our TViewport to be able to render a scene.  At the same time as making this change, I’m going to remove the temporary ‘RenderSomething()’ method from the viewport unit, as well as removing the texture material property. Lets see the new version of unitViewport.

unit unitViewport;

interface
uses
  unitSpriteScene, // for TSpriteScene
  unitViewportContext, // for TViewportContext
  System.Classes, // for TComponent
  FMX.Forms3D; // for TForm3D

type
  TViewport = class(TComponent)
  private
    fForm: TForm3D;
    fSpriteScene: TSpriteScene;
    fContext: TViewportContext;
  private
    procedure SetForm(const Value: TForm3D);
  public
    constructor Create( aOwner: TComponent ); override;
    destructor Destroy; override;

    procedure Render;

  published
    property Form: TForm3D read fForm write SetForm;
    property Scene: TSpriteScene read fSpriteScene write fSpriteScene;
  end;

procedure Register;

implementation
uses
  FMX.Types3D; // for TVertexBuffer

procedure Register;
begin
  RegisterComponents('SpriteEngine', [TViewport]);
end;

{ TViewport }

constructor TViewport.Create(aOwner: TComponent);
begin
  inherited Create(aOwner);
  // Auto assign the form if it is TForm3D, calls SetForm
  if assigned(aOwner) and (aOwner is TForm3D) then begin
    Form := TForm3D(aOwner);
  end;
  fSpriteScene := nil;
  fContext := TViewportContext.Create;
  fContext.Context3D := nil;
end;

destructor TViewport.Destroy;
begin
  Form := nil; // causes dispose of camera and dummy.
  fSpriteScene := nil;
  fContext.Free;
  inherited Destroy;
end;

procedure TViewport.Render;
begin
  if assigned(Form) then begin
    // Render something as a test.
    if Form.Context.BeginScene then begin
      try
        Form.Context.SetContextState(TContextState.cs2DScene);
        if assigned(Scene) then begin
          fContext.Context3D := Form.Context;
          Scene.Render(fContext);
        end;
      finally
        Form.Context.EndScene;
      end;
    end;
  end;
end;

procedure TViewport.SetForm(const Value: TForm3D);
begin
  if Value<>Form then begin
    fForm := Value;
  end;
end;

end.

In this version of the viewport unit, we’ve added the Scene property, which is a reference to a TSpriteScene. The idea here being, when you drop a TViewport onto your form, you also drop a TSpriteScene component and set the Scene property to point to it.  In this way, you could drop several TSpriteScene’s onto your form and switch between them by altering the Scene property on the viewport.

We’ve also added a private member named fContext, which is the new TViewportContext that we created earlier. In the constructor we create this context and initialize it, and then dispose of it in the destructor.

The Render method for the viewport has been altered also. We no longer call ‘RenderSomething()’ but instead we ensure that the ‘Context3D’ property of fContext is correctly set, and then call the Render() method of the Scene, passing the context as a parameter.

If you were to drop a TViewport component and a TSpriteScene component onto your main form now, and wire them together. When you call Render, nothing will happen. Why? Well because your scene is empty!

In order to put something into the scene, we’ll need to create a new TSceneComponent descendant class to render something.

This post is already getting a little long, so we’ll look at creating our actual sprite class (first draft) in the next post. For right now, lets create a class to clear the backdrop of our scene to a specified color.

unit unitBackdrop;

interface
uses
  classes,
  System.UITypes, // for TAlphaColor
  unitSpriteScene, // for TSceneComponent
  unitViewportContext; // for TViewportContext

type
  TBackdrop = class( TSceneComponent )
  private
    fColor: TAlphaColor;
  protected
    procedure Render( Context: TViewportContext ); override;
  public
    constructor Create( aOwner: TSceneComponent ); override;
  published
    property Color: TAlphaColor read fColor write fColor;
  end;

implementation

{ TBackdrop }

constructor TBackdrop.Create(aOwner: TSceneComponent);
begin
  inherited Create(aOwner);
  Color := TAlphaColorRec.Blue;
end;

procedure TBackdrop.Render(Context: TViewportContext);
begin
  Context.Context3D.Clear(Color);
  inherited Render(Context);
end;

end.

There, we’ve created a scene component which has a color property. In the constructor we initialize the color property, and in the Render() method, we simply clear the context using the preset color. We then call the inherited Render() method to ensure any children will be rendered.

Having added all of the above units to your package, rebuild and re-install the package.

Lets build a simple scene…

  1. Start a new Multidevice Delphi application (Firemonkey/FMX)
  2. Select a 3D application from the wizard.
  3. Drop a TViewport component onto your form.
  4. Drop a TSpriteScene component onto your form.
  5. Drop a TTimer component onto your form.
  6. Ensure Viewport1.Form is set to point at your main form.
  7. Set Viewport1.Scene to point at SpriteScene1
  8. Set the timer interval to 100
  9. In the OnTimer event add “Viewport1.Render;”
  10. On the forms OnCreate add “TBackdrop.Create(SpriteScene1.Root);”

*Note* You could add a custom component editor to the package for the TSpriteScene component, so that you can easily add TBackdrop as a child component in the designer. – That’s an exercise for the reader, I’m still trying to get us to a working sprite engine, far less one that is so convenient to use yet.

Now run the application, you should see the same thing I did….

Blue

 

In this post we set out to create a hierarchical scene composition system, which we have done. At this point however, our application isn’t really doing anything interesting.

In the next post we’ll see the spite engine finally begin to take shape when we add the sprite class. Until then…

Thanks for reading!

Print Friendly, PDF & Email
Facebooktwitterredditpinteresttumblrmail

1 Response

  1. Hi,

    I did this and didn’t work on my PC. Finally I copied it and neither worked. I got no error message, my program runs but the main form background remains the same.

    What do you think can be the problem? or any clue where to start looking, i don’t see anything.

    Thanks in advance.

Leave a Reply