Retro Graphics Engine¶
This part of the documentation will give some insights of the implementation of the various aspects of the framework. It will help you to extend the framework in order to implement new functions etc.
Lifecycle¶
The sequence flow of the engine architecture is depicted in the following figure. It shows, in terms of the life cycle, which components are started and when:
The procedure can be described as follows:
Activity#onCreate
of activity is called- find the custom
DrawView
component of the activity - instantiate the
RenderThread
class and pass theDrawView
as argument (optional)- needs the SurfaceHolder from the
DrawView
component
- needs the SurfaceHolder from the
- find the custom
DrawView
is created within the layoutRetroEngine.init(context)
is calledStateManager
instance is initialized- attach touch and key listeners (all kinds of key and touch events)
- canvas dimension is assigned to the
RetroEngine
class RenderThread
will be created if not manually passed before to the componentDrawView
knows about theRenderThread
to control it
RenderThread
is started (or will be instantiated if not available)- knows about the SurfaceHolder from the
DrawView
component - initializes the next
State
to update and draw
- knows about the SurfaceHolder from the
- the loop in
RenderThread
is calling the update logic and the render method for the currently activeState
- the currently active
State
class will be retrieved from theStateManager
class within theRenderThread
- the state delegates this to its sprite objects
- the currently active
Render loop¶
The implemented render loop is framerate independent. The render and the update part of the sprites is separated. Therefore it consists of a update and a render cycle. The render loop runs in a separate thread.
In short, the render loop is a mixture of the implementation from JGO - Game loops!, dewitters-gameloop and ANDROID SPEEDING UP CANVAS.DRAWBITMAP. It can be considered as a loop with fixed timestep so that a constant speed with maximum FPS can be achieved. This is a very common render loop which can be found in many examples.
The distinct parts of this loop are described in short below.
Update cycle¶
RenderThread
will call the update method of the currently activeState
classState
class is in charge to call the update methodupdateLogic
of every sprite
Implementation Hint
It`s convenient to use the State#updateSprites()
method
if you add your sprites to the root group with State#addSprite()
Version Notice
This is the documentation for Version 0.9.5-beta1 of the engine
The update is limited to get a framerate independent application. The application runs at a constant speed with a maximum FPS.
Its based on a fixed fps and should practically never be over the set fps of TICKS_PER_SECOND = 50
in this default
configuration. The updates will be done at steady 50 times per second.
You can experiment with those variables defined in the class RetroEngine
and change them
to your personal needs.
Render cycle¶
The canvas of the DrawView
component is locked to call the render method of the currently
active State
which delegates the canvas object as argument to the render function of the sprites
defined in this State
. The sprite objects using the canvas to draw bitmap graphics on it.
Rendering is done as many times as the CPU of your smartphone is capable. There is no limitation.
Points to remember
- Canvas is used for displaying graphics
- the special implementation DrawView
(a custom Android view) provides the canvas
- The RenderThread
will get the current state from the StateManager
and will
pass the canvas from the DrawView
component to the State
class' render method
Controlling the loop¶
You can call those method of RetroEngine
anywhere to control
the render loop:
- Pause:
RetroEngine.pauseRenderThread();
- don't process the update and render loop
- Resume:
RetroEngine.resumeRenderThread();
- resume processing update and render loop
- Exit the loop:
RetroEngine.changeRunningState(false);
Engine Initialization¶
Each DrawView
component in the layout will have its own render thread.
This allows multiple independent graphics rendering.
You have two options to get the engine initialized.
This architecture allows you to define your own render thread
or use the one provided by the DrawView
component. In both approaches
you can use the default implementation RenderThread
provided by Retro Graphics Engine.
Create the RenderThread¶
Create the RenderThread
class by yourself:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | //inside an activity public RenderThread renderThread; public DrawView drawView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_layout); drawView = findViewById(R.id.graphics); // use the default render thread from the engine renderThread = new RenderThread(drawView); } |
We're using the default RenderThread
class provided by Retro Graphics Engine.
You can extend this class and provide your own implementation with that approach.
With that approach both components get to know each other automatically.
Acquire the RenderThread¶
Let the DrawView
component create the default RenderThread
and get
it back when the surface is created:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public class MyActivity extends AppCompatActivity implements DrawView.DrawViewInteraction { public static RenderThread renderThread; private DrawView drawView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_touch_interaction); drawView = findViewById(R.id.graphics); // subscribe this activity to listened when the DrawView is // creating the surface drawView.addListener(this); } @Override public void onCreated(DrawView drawView) { // get the RenderThread created by the DrawView renderThread = drawView.getRenderThread(); // now add your custom states here renderThread.addState(new MyBasicState()); } } |
The viewpoint origin¶
This point represents the origin of the canvas, the drawing surface. Normally it has the value (0,0) and depicts the top-left corner of the canvas. This may change in case of the background layer you use. Because when the canvas is translated then the coordinates of every sprite on it will change.
This important point can then be used to set the absolute position of sprites on the canvas or to check for collision between sprites. In those cases you need the absolute position.
You can call State#getViewportOrigin()
in a State
class at every time.
Each sprite also has this method available. If the sprite has a parent then
the parents value will be used.
States¶
- a state defines a scene where sprites can be arranged and the logic for a scene can be implemented
- scene objects are nodes, respectively sprites
- every State has a so called root node which is a
SpriteGroup
. It`s convenient to use for automatically drawing every other child sprite
Base methods for sprite handling:
Method | Description |
---|---|
setInitAsync() / isInitAsync() |
should state initialised asynchronously by DrawView? A standard progess dialog is shown |
addSprite / addSprites() |
add one or more sprites to the rootGroup of the state |
updateLogic() |
all the update logic as to be implemented here. call updateSprites() to delegate all updates to the sprites added to rootGroup |
init() |
initialisation routines, for instance create all sprites, load resources etc. |
render() |
place your render sprite routines here. use drawSprites() to delegate all render stuff to the sprites added to rootGroup |
Methods for background handling:
TODO
StateManager¶
- a class which holds of every
State
- capability to switch between
State
classes or to start anActivity
- Interact with Activity: within a
State
you can call theStateManager
and get access to theActivity
1 | manager.getParentActivity() |
Keep in mind
Each activity and class has access to the same states that were added before -
the StateManager
is a singleton. Throughout the app every class has access
to the same objects of this class.
In contrast, each RenderThread
has its own individual added states. The
StateManager
acts as a global resource holder of all states. Each DrawView
with its associated render thread can have different states.
If the render thread exits all added states will be cleared automatically.
But they share all the same instance of each state.
See more examples in the section Handle different flows.
Change to another State¶
To change the state within a State
to another one, the state has to be added
to the RenderThread
before. Then you can simply call:
1 | StateManager.getInstance().changeGameState(MyNewState.class); |
The class of the state to change to has to be passed as argument to the method.
Helper classes ¶
- MathUtils
- ColorTools
- InterpolationHelper
- BitmapHelper