In CosPlay, class CPCanvas defines a 2D rendering pane. To display something on the terminal - it needs to be rendered on the canvas.
For each game frame, the game engine creates a new empty canvas for all scene objects to draw on. Note that in most cases, the canvas dimension matches the scene dimension. This canvas is available to scene objects via CPSceneObjectContext.getCanvas method. Game engine then compares previous canvas and this one to determine which areas of the terminal need redrawing. You can also create the canvas object outside game loop using companion object methods.
Canvas (0,0)
coordinate point is located in the top left corner. X-axis goes to the right and Y-axis points down. Every drawn pixel on the canvas also has a Z-index or depth. Pixel with larger or equal Z-index visually overrides the pixel with the smaller Z-index, i.e. Z-axis is pointing away from the screen towards the observer:
,*-- -- -- -- -- X / | (0,0) .' | / | .' | / Z | Y
ASCII-based graphics are vastly different from the traditional raster-based graphics. Since ASCII-based drawing uses printable characters from ASCII character set that are displayed on basic text terminal most differences are obvious but some are more subtle and unexpected. For example, while straight vertical and horizontal lines are easy to implement many curved lines are much trickier to draw. Specifically, circles and ellipses, for example, are hard to do properly in automated way. Moreover, many complex curves need to be done manually, especially if they are dynamically redrawn on each frame update.
For example, consider the following subtle curvatures (borrowed from the excellent ASCII art tutorial by Richard Crawford):
/ / / / / / ____/ _.-~ _,-~ _.-' _.-' _.-' / ,^ ,^ ," ,` ,' / / / / / / / / / / / / ____/ __.-~ __,-~ __.-" __.-' __.-`
First line is a default naive drawing. Second and third curves differ only in use of ','
vs. '.'
(line 2 and line 6). This is just one of several stylistic modifications one could make - in this particular case, use of ','
make the curvature appear more angular at the connection point. Later options make the curve even smoother by utilising '''
, '"'
and '`'
characters. Last line looks particular smooth.
Class CPCanvas provides many methods for basic line and rectangular drawing, circle and polylines, anti-aliasing, "color" fill in, and much more. Most functions have multiple overridden variants with different parameters so that they can be easily used in different contexts. Note also that this class deals primarily with line ASCII art and has only few functions like antialiasing for the solid ASCII art.
Here's the code snippet from the built-in Canvas Example that demonstrates some of the CPCanvas API:
// Color shortcuts. val c1 = C_STEEL_BLUE1 val c2 = C_INDIAN_RED val c3 = C_LIGHT_STEEL_BLUE val spr = new CPCanvasSprite(): override def render(ctx: CPSceneObjectContext): Unit = val canv = ctx.getCanvas // Draw image. canv.drawImage(alienImg, 2, 2, 0) canv.drawString(5, alienImg.getHeight + 4, 0, "Image", c1) // Draw a basic polyline shape. canv.drawPolyline(Seq( 15 -> 2, 25 -> 3, 30 -> 15, 22 -> 17, 18 -> 5, 15 -> 2, ), 100, '*'&c3) // Fill this shape with random color on each frame. canv.fill(17, 3, _.char == '*', (_, _) => CPRand.rand(pxs)) canv.drawString(18, 19, 0, "Filled shape", c1) // Draw ASCII-art style polyline shape. canv.drawArtPolyline(Seq( 32 -> 2, 42 -> 3, 47 -> 15, 39 -> 17, 35 -> 5, 32 -> 2, ), 100, _.px.withFg(c3), ART_SMOOTH) // Fill this ASCII-art shape with dark grey 'x'. canv.fill(35, 4, _.px != bgPx, (_, _) => 'x'&C_GRAY3) canv.drawString(34, 19, 0, "Filled smooth shape", c1) // Draw antialias circle. val circRect = canv.drawCircle(75, 12, 10, z = 0, 2f, 1f, 0.5f, true, 'x'&c2) canv.antialias(circRect, _.char != 'x') // Draw a panel with titled border. canv.fillRect(3, 27, 40, 37, 0, Seq(' '&C_BLACK)) canv.drawSimpleBorder( 2, 26, 41, 38, 0, '|'&c3, '-'&c3, '+'&c1, styleStr("/ ", c3) ++ styleStr("Titled Panel", C_INDIAN_RED) ++ styleStr(" /", c3), 5, 26 )
NOTES:
Note that you can implement simple animation by re-drawing the canvas differently on each frame update. In the above example we draw a polyline shape on the line 15 and then fill it in with a random color on each frame (line 25). This results in a nice flickering effect - the sort of animation that is ideally suited for this type of implementation, a literally one-line animation implementation.
Here is the result animation from the built-in Canvas Example:
One of the useful techniques of obtaining a complex "drawn-on-canvas" images is to create a new canvas object at the scene's start (see CPLifecycle.onStart() method), draw all necessary ASCII art on it and then capture the image using one of the capture()
methods in CPCanvas class. Once image is obtained you can use it with any image-based sprites to display it.
There are many online tutorials for ASCII art that are highly recommended, including from:
See also the following resources for general ASCII art collections: