In CosPlay, a scene is defined by CPScene class:
A scene can be adaptive or static in terms of its dimensions. When a scene dimension is set it becomes static for the lifetime of the scene and its scene objects can rely on this fact. However, if a scene dimension is not set, it will adapt to the terminal's window dimension on each frame update. Make sure that all scene objects in the adaptive scene take this into account in their rendering routines.
Lifecycle
Both CPScene and CPSceneObject extend CPLifecycle trait. You can extend these classes and override lifecycle callbacks when you need to react on lifecycle events.
It is important to note that unlike many other game engines the scene objects in CosPlay do not form any hierarchy withing the scene - scene objects are just a flat collection. Any relationship between scene objects in the same scene can be resolved in any supported Scala way - CosPlay does not place any restrictions on that.
A scene has a strong encapsulation contract in relation to its scene objects:
In other words, once a scene is created its constituent scene objects can only be changed in the game loop by one of its own scene objects. This contract is necessary to ensure proper state management of the scene objects. Note that scene objects can add new and remove existing, not active, scenes as well.
When starting a game via one of the CPEngine.startGame(...) methods you need to pass at least one scene:
val scene = new CPScene(...) try CPEngine.startGame(scene) // At least one scene is required. finally CPEngine.dispose() sys.exit(0)
When creating a scene at least one scene object is required. There are two main ways to create a new scene in CosPlay:
val bgPx = CPPixel('.', C_GREY1, C_GREY2) val dim = CPDim(100, 50) val spr1 = CPAnimationSprite(...) val spr2 = CPAnimationSprite(...) val myScene = new CPScene("id", Option(dim), bgPx, spr1, spr2)
val bgPx = CPPixel('.', C_GREY1, C_GREY2) val dim = CPDim(100, 50) object MyScene extends CPScene("id", Option(dim), bgPx): private val spr1 = CPAnimationSprite(...) private val spr2 = CPAnimationSprite(...) addObjects(spr1, spr2)Note that in this case you need to use protected
addObjects(...)
method to add scene objects to the scene.A scene object is defined by CPSceneObject class and is the main CosPlay type that a game developer interacts with while building a game:
Scene Object vs. Sprite
By convention all subtypes of CPSceneObject are called sprites.
There are two main ways to create a scene object:
Hello CosPlay!
at the top left corner of the scene in black using system font:object HelloSprite extends CPSceneObject("id"): override def getX: Int = 0 override def getY: Int = 0 override def getZ: Int = 0 override def getDim: CPDim = CPDim(15, 1) override def render(ctx: CPSceneObjectContext): Unit = ctx.getCanvas.drawString( 0, 0, 0, "Hello CosPlay!", CPColor.C_BLACK )
You would typically implement CPSceneObject class directly when none of the existing sprites fit your needs.
The same Hello CosPlay!
sprite can be implemented quicker using existing CPLabelSprite:
val spr = CPLabelSprite(0, 0, 0, "Hello CosPlay!", C_BLACK)
Scene objects get updated roughly 30 times per second (CosPlay maintains 30 FPS as its target frame rate). There is no need to update ASCII graphics on ANSI terminal with a higher frame rate which leaves more CPU time to the game logic.
Here is what happens inside the game loop. On each frame update game engine creates a new instance ctx
of CPSceneObjectContext trait that defines all in-game services available to scene objects and performs four passes through the list of scene objects in the current scene:
Update Pass
CPSceneObject.update(ctx) callback.
At the start of each frame, this callback is called on all scene objects in the scene regardless of whether they are visible or in camera frame. On this callback the scene object may read the keyboard input, update its internal state like position, dimension, or Z-index, send and receive messages, perform network sync, etc. Scene object should not do any rendering in this callback.
Monitor Pass
CPSceneObject.monitor(ctx) callback.
Called after all scene objects have been updated but before any of them were rendered. It allows, for example, to rearrange UI sprites on the screen after all of them had a chance to update their dimensions but before they are actually rendered on the screen. Essentially, it provides for "post-update, pre-render" notification. The order in which scene objects are called is undefined.
monitor(ctx)
callback
Note that in most cases one should not override this callback. It is only meant for the use cases when one needs to be notified when all scene objects to be updated but before any of them are rendered. Default implementation is no-op. No rendering should be done in this callback.
Render Pass
CPSceneObject.render(ctx) callback.
This callback is called only after all scene objects received the update(ctx)
and monitor(ctx)
callbacks and is called only for visible and in camera frame scene objects. Scene objects should only render in this callback using canvas object obtained via ctx.getCanvas()
method and should not do any state updates. Note that objects don't have to perform any clipping or worry about partially visible object - if this callback is called the object should render itself fully on a given canvas. Game engine will take care of proper clipping of the partially visible objects.
Shader Pass
CPShader.render(ctx, ...) callback.
After all CPSceneObject.render(ctx)
callbacks are called - this callback is called for each shader for scene objects that have one or more shaders attached to them regardless of whether they are visible or in camera frame.
After all four passes are complete, the game engine will render the difference between the last frame canvas and this frame canvas on the target terminal. And the game loop will begin again.