Table Of Contents

Previous topic

An example game

Next topic

Events

This Page

Graphics

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

Basic shapes

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

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())

Fonts

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())

Transform stack

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())

Color stack

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().

Rendering to an image

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

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:

  • attribute vec3 a_Position: transformed unprojected vertex position (i.e., in screen space with the transform stack applied)
  • attribute vec2 a_TexCoord0: texture coordinate for the image drawn with draw_image()
  • attribute vec4 a_Color: vertex color, as provided by set_color()
  • uniform mat4 g_Projection: projection matrix, typically mapping screen space to NDC
  • uniform sampler2D g_Texture0: texture for the image drawn with draw_image()
  • uniform float g_Time: number of seconds since the game started

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.

Blend modes

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())