Bacon provides functions for loading and drawing images and fonts. It presents a simple 2D API for this, but under the hood is using current generation GPU APIs, which allows you to use sophisticated effects.
While images and fonts can be loaded and queried at any time, the drawing functions can only be called within the scope of your game’s Game.on_tick() method.
The coordinate system is top-down, with (0, 0) in the top-left corner.
At the beginning of each frame (before doing any other rendering in on_tick), you should clear the screen:
Note
TODO clear example
Rectangles can drawn or filled in the current color with draw_rect() and fill_rect():
import bacon
class Game(bacon.Game):
def on_tick(self):
bacon.clear(0, 0, 0, 1)
bacon.set_color(1, 0, 0, 1)
bacon.fill_rect(50, 50, 150, 150)
bacon.set_color(0, 1, 0, 1)
bacon.draw_rect(50, 50, 150, 150)
bacon.run(Game())
Lines can similarly be drawn with draw_line().
Images can be loaded from most standard file types, for example PNG, BMP, TIFF, JPEG, etc. First create an Image, then draw it with draw_image() or draw_image_region():
import bacon
kitten = bacon.Image('res/kitten.png')
print('The kitten image has dimensions %dx%d' % (kitten.width, kitten.height))
class Game(bacon.Game):
def on_tick(self):
bacon.clear(0, 0, 0, 1)
bacon.draw_image(kitten, 0, 0)
bacon.run(Game())
To render text, first load a TrueType or OpenType font at a particular point size by constructing a Font, then render it with draw_string():
import bacon
font = bacon.Font('res/DejaVuSans.ttf', 24)
print('The font has ascent %d and descent %d' % (font.ascent, font.descent))
class Game(bacon.Game):
def on_tick(self):
bacon.clear(0, 0, 0, 1)
bacon.draw_string(font, 'Hello, Bacon!', 50, 50)
bacon.run(Game())
All drawing commands are transformed by the top matrix in a transform stack before being submitted for rendering. By transforming the top matrix of the transform stack, you affect the position of all subsequent drawing commands. This can be convenient for rendering groups of objects with a parent transform, and is necessary to perform rotations, scales and skews on drawing.
This example shows how the rotate() function can be used to rotate an image, using translate() to set move the rotation pivot to the center of the image:
import bacon
import math
kitten = bacon.Image('res/kitten.png')
class Game(bacon.Game):
def on_tick(self):
bacon.clear(0, 0, 0, 1)
bacon.translate(kitten.width / 2, kitten.height / 2)
bacon.rotate(math.pi / 4)
bacon.draw_image(kitten, -kitten.width / 2, -kitten.height / 2)
bacon.run(Game())
The above functions operate only on the top matrix of the transform stack. You can save and restore the current transform with push_transform() and pop_transform(). The following example shows how the rotation transform does not affect the text rendered, only the image.
import bacon
import math
kitten = bacon.Image('res/kitten.png')
font = bacon.Font('res/DejaVuSans.ttf', 24)
class Game(bacon.Game):
def on_tick(self):
bacon.clear(0, 0, 0, 1)
bacon.push_transform()
bacon.translate(kitten.width / 2, kitten.height / 2)
bacon.rotate(math.pi / 4)
bacon.draw_image(kitten, -kitten.width / 2, -kitten.height / 2)
bacon.pop_transform()
bacon.draw_string(font, 'Hello', 20, 30)
bacon.run(Game())
All drawing commands are tinted by the top color in a color stack when rendered. By default the color is white ((1, 1, 1, 1)), so no tinting is applied. This example shows the image tinted red by modifying the color:
import bacon
kitten = bacon.Image('res/kitten.png')
class Game(bacon.Game):
def on_tick(self):
bacon.clear(0, 0, 0, 1)
bacon.set_color(1, 0, 0, 1)
bacon.draw_image(kitten, 0, 0)
bacon.run(Game())
By changing the alpha (the fourth color component), you can make images and text translucent.
Just like the transform stack, the color stack allows you to save and restore the current color, using push_color() and pop_color().
By default all rendering is done to the main window. You can render to an image instead using push_target(). This is useful for compositing elements in a single layer for blending, and for certain special effects. Use pop_target() to resume rendering to the previous target (e.g. the window).
Shaders are small programs that run on the GPU to modify the drawing commands. The shader is written in OpenGL-ES Shading Language 2.0, and is supplied as two strings: one for the vertex program and one for the fragment program. The following example demonstrates a shader that adjusts the brightness and contrast of the image depending on the mouse position within the window:
import bacon
shader = bacon.Shader(vertex_source=
"""
precision highp float;
attribute vec3 a_Position;
attribute vec2 a_TexCoord0;
attribute vec4 a_Color;
varying vec2 v_TexCoord0;
varying vec4 v_Color;
uniform mat4 g_Projection;
void main()
{
gl_Position = g_Projection * vec4(a_Position, 1.0);
v_TexCoord0 = a_TexCoord0;
v_Color = a_Color;
}
""",
fragment_source=
"""
precision highp float;
uniform sampler2D g_Texture0;
uniform float brightness;
uniform float contrast;
varying vec2 v_TexCoord0;
varying vec4 v_Color;
void main()
{
// Standard vertex color and texture
vec4 color = v_Color * texture2D(g_Texture0, v_TexCoord0);
// Brightness / contrast
color = vec4(brightness + 0.5) + (color - vec4(0.5)) * vec4(contrast);
gl_FragColor = color;
}
""")
brightness = shader.uniforms['brightness']
contrast = shader.uniforms['contrast']
kitten = bacon.Image('res/kitten.png')
class Game(bacon.Game):
def on_tick(self):
bacon.clear(0, 0, 0, 1)
bacon.set_shader(shader)
brightness.value = bacon.mouse.y / float(bacon.window.height)
contrast.value = bacon.mouse.x / float(bacon.window.width)
bacon.draw_image(kitten, 0, 0)
bacon.run(Game())
Bacon requires that certain shader uniforms and attributes are named and typed appropriately for it to be able to submit drawing commands:
These uniforms may not be set directly, however others you define in the shader can be manipulated through the Shader.uniforms map as shown in the example above. Note that the naming convention of shader uniforms is important: uniforms with names that begin with g_ share their value across all shaders (for example, g_Projection and g_Texture0 above). Other uniforms have values that must be set per-shader.
The current blend state dictates how new elements are composited into the target. The default blend mode is suitable for compositing images and glyphs with premultiplied alpha; changing the blend mode with set_blending() can be used for special effects. In the following example, the same text is rendered over itself in different colors with an additive blend; where the colors intersect, the color adds to white:
import bacon
font = bacon.Font('res/DejaVuSans.ttf', 48)
class Game(bacon.Game):
def on_tick(self):
bacon.clear(0, 0, 0, 1)
bacon.set_blending(bacon.BlendFlags.one, bacon.BlendFlags.one)
bacon.set_color(1, 0, 0, 1)
bacon.draw_string(font, 'Bacon', 0, 50)
bacon.set_color(0, 1, 0, 1)
bacon.draw_string(font, 'Bacon', 10, 50)
bacon.set_color(0, 0, 1, 1)
bacon.draw_string(font, 'Bacon', 20, 50)
bacon.run(Game())