Skip to content

Getting Started 1: Setting Up an App

SleepProgger edited this page Apr 20, 2017 · 32 revisions

Setting Up Your Application

This tutorial will teach you the basics of setting up a KivEnt game app. By the end this tutorial series you will be able to render an animated starfield background to the screen using a series of sprites taken from a sprite sheet.

Part One of this tutorial will use the following examples from the repository:

Prerequisites: You will need to have compiled kivent_core as well as kivy and all its requirements installed. Additionally you will need the assets folder from the examples directory.

An Empty Application

We will start with an basic kivy application. In addition to the standard kivy items, we will import KivEnt (kivent_core).

Our main.py should look as follows:

import kivy
from kivy.app import App
from kivy.uix.widget import Widget
import kivent_core

class TestGame(Widget):
    pass

class YourAppNameApp(App):
    def build(self):
        pass

if __name__ == '__main__':
    YourAppNameApp().run()

In addition to our python script, we will create the .kv file that corresponds to our simple kivy application. The corresponding .kv, yourappname.kv, is shown below:

#:kivy 1.9.0

TestGame:

<TestGame>:

Adding the GameWorld

We will introduce 2 widgets, a GameWorld which will manage your game data, and a GameScreenManager which will allow us to sync our Kivy UI to the state of the GameWorld. Screens added to the GameScreenManager will work with GameWorld's state management.

<TestGame>:
    gameworld: gameworld
    GameWorld:
        id: gameworld
        gamescreenmanager: gamescreenmanager
        size_of_gameworld: 100*1024
        size_of_entity_block: 128
        system_count: 8 
        zones: {'general': 10000}
    GameScreenManager:
        id: gamescreenmanager
        size: root.size
        pos: root.pos
        gameworld: gameworld
        GameScreen:
            name: 'main'

We must configure a few options on the GameWorld. size_of_gameworld is the number of Kibibytes that will be allocated to hold static component data (the non-python components that have been cythonized extensively). size_of_entity_block is the size of the individual blocks (in kibibytes) of the memory pools for the GameWorld's entity memory. system_count is the maximum number of GameSystem with components. The size of the entities memory will be sum of the count for all zones * system_count + 1. The zones dict specifies which regions to reserve in memory and how large to make the regions. This allows you to organize your efficiencies by processing pattern so that our GameSystem can process a more contiguous arrangement of data. To learn more, read the memory handler documentation.

When creating a GameWorld, we need to first call init_gameworld to finalize memory allocation and help us avoid kv creation races within our GameWorld widget tree. The first argument in this function is a list of the system_ids for GameSystems we need to ensure have finished their initialization before commencing the init_gameworld function. The callback argument allows you to set a function to be called after the initialization has finished. We will make use of that callback here:

import kivy
from kivy.app import App
from kivy.uix.widget import Widget
import kivent_core

class TestGame(Widget):
    def __init__(self, **kwargs):
        super(TestGame, self).__init__(**kwargs)
        self.gameworld.init_gameworld([], callback=self.init_game)

    def init_game(self):
        self.setup_states()
        self.set_state()

    def setup_states(self):
        self.gameworld.add_state(state_name='main', 
            systems_added=[],
            systems_removed=[], systems_paused=[],
            systems_unpaused=[],
            screenmanager_screen='main')

    def set_state(self):
        self.gameworld.state = 'main'

class YourAppNameApp(App):
    def build(self):
        pass

if __name__ == '__main__':
    YourAppNameApp().run()

Setting up the gameworld state.

The add_state function allows us to define states which will control which GameSystem have been added or removed from the widget tree, and are paused or unpaused, controlling the flow of your update loop. In addition you can specify the screen in the GameScreenManager to associate with this state. We must then set our GameWorld to one of the states we create to start the flow of our game.

On starting up your application you should receive a black screen (as we haven't added anything yet) and see some log output that looks like:

[INFO   ] [KivEnt      ] We will need 384 KiB for game, we have 102400 KiB

This is the total space we have reserved for our game, and the amount that we are actually using between all initialized GameSystems and the entity array itself. While not a count of the total memory your app will consume, as any python logic and the interpreter itself will take up a significant amount of memory as well, but it will help you reason about the size of the data your game logic is introducing and processing. This will ensure we do a minimum amount of memory allocation as a result of the game logic we will be introducing. Here we see only the amount of memory that will be required to hold 10,000 entities with 8 components each in the entities array.

Adding GameSystems

We will start by adding a static renderer, and rendering some stars in the background. This will require adding 2 GameSystems: Renderer and PositionSystem2D. Let's change the TestGame rule to:

<TestGame>:
    gameworld: gameworld
    GameWorld:
        id: gameworld
        gamescreenmanager: gamescreenmanager
        size_of_gameworld: 100*1024
        size_of_entity_block: 128
        system_count: 2
        size: root.size
        pos: root.pos
        zones: {'general': 10000}
        PositionSystem2D:
            system_id: 'position'
            gameworld: gameworld
            zones: ['general']
            size_of_component_block: 128
        Renderer:
            id: renderer
            gameworld: gameworld
            system_id: 'renderer'
            zones: ['general']
            frame_count: 1
            updateable: False
            force_update: True
            size_of_batches: 128
            size_of_component_block: 128
            shader_source: 'assets/glsl/positionshader.glsl'
    GameScreenManager:
        id: gamescreenmanager
        size: root.size
        pos: root.pos
        gameworld: gameworld
        GameScreen:
            name: 'main'

Setting up the PositionSystem2D

When we declare the PositionSystem2D, we give it a system_id, which is the 'name' of its component, and a zones list, which tells the GameWorld how to allocate the memory for this GameSystem, the count for each zone name in the list will be reserved for this system, making the maximum number of entities that can be supported by this GameSystem the sum of the counts for each zone name.

Setting up the Renderer

We will make this renderer static as we are using it to draw immobile background sprites. This is accomplished by setting the frame_count to 1 and updateable to False. size_of_batches controls the size of each batch submission to the GPU, Unity3d guys have recommended 1 Mib to 4 Mib sizes, here I have set it to 128 Kib. Finally we link our shader using shader_source.

Loading our Assets:

texture_manager.load_atlas('assets/background_objects.atlas')

First we load the atlas containing our textures, and then inside TestGame we load a model of the 'star1' texture with 2 different sizes (7., 7.) and (10., 10.).

class TestGame(Widget):
    def __init__(self, **kwargs):
        super(TestGame, self).__init__(**kwargs)
        #Add the GameSystem we are initializing to the args in init_gameworld
        self.gameworld.init_gameworld(
            ['renderer', 'position'],
            callback=self.init_game)

    def init_game(self):
        self.setup_states()
        self.set_state()
        self.load_models()

    def load_models(self):
        model_manager = self.gameworld.model_manager
        model_manager.load_textured_rectangle('vertex_format_4f', 7., 7., 
            'star1', 'star1-4')
        model_manager.load_textured_rectangle('vertex_format_4f', 10., 10., 
            'star1', 'star1-4-2')

All of our sprites will share the same model unless we create a copy, as we do when creating the 'star1-4-2' model, this way you can modify some entities model without affecting all entities.

Creating Entities

Finally, let us add a new function to our init_game method that draws some things.

class TestGame(Widget):
    def __init__(self, **kwargs):
        super(TestGame, self).__init__(**kwargs)
        #Add the GameSystem we are initializing to the args in init_gameworld
        self.gameworld.init_gameworld(
            ['renderer', 'position'],
            callback=self.init_game)

    def init_game(self):
        self.setup_states()
        self.load_models()
        self.set_state()
        self.draw_some_stuff()

    def draw_some_stuff(self):
        init_entity = self.gameworld.init_entity
        for x in range(1000):
            pos = randint(0, Window.width), randint(0, Window.height)
            model_key = choice(['star1-4', 'star1-4-2'])
            create_dict = {
                'position': pos,
                'renderer': {'texture': 'star1', 
                    'model_key': model_key},
            }
            #in addition to the args dict we pass in a list dictating 
            #the order to create the components in.
            ent = init_entity(create_dict, ['position', 'renderer'])
        #If you do not set Renderer.force_update to True, call update_trigger
        #self.ids.renderer.update_trigger()

    def setup_states(self):
        #we add 'renderer' to ensure it is on the canvas and unpaused.
        self.gameworld.add_state(state_name='main', 
            systems_added=['renderer'],
            systems_removed=[], systems_paused=[],
            systems_unpaused=['renderer'],
            screenmanager_screen='main')

Adding a UI Widget

Now let us add a simple widget to our GameScreenManager to integrate a UI. It will display the current frame-per-second (FPS) performance of our application.

class DebugPanel(Widget):
    fps = StringProperty(None)

    def __init__(self, **kwargs):
        super(DebugPanel, self).__init__(**kwargs)
        Clock.schedule_once(self.update_fps)

    def update_fps(self,dt):
        self.fps = str(int(Clock.get_fps()))
        Clock.schedule_once(self.update_fps, .05)

In the kv file we will define:

<GameScreenManager>:
    MainScreen:
        id: main_screen

<MainScreen@GameScreen>:
    name: 'main'
    FloatLayout:
        DebugPanel:
            size_hint: (.2, .1)
            pos_hint: {'x': .225, 'y': .025}

<DebugPanel>:
    Label:
        pos: root.pos
        size: root.size
        font_size: root.size[1]*.5
        halign: 'center'
        valign: 'middle'
        color: (1,1,1,1)
        text: 'FPS: ' + root.fps if root.fps != None else 'FPS:'

Memory Usage

Now if we run our application we should get some more output about KivEnt: Here we can see how much memory each of our systems will take, and some feedback about how many entities KivEnt estimates you will be able to render per batch.

[INFO   ] [Kivent      ] position allocated 1280 KiB
[INFO   ] [KivEnt      ] Batches for canvas: <kivy.graphics.instructions.RenderContext object at 0x7f80958f4650> will have 32768 verts and VBO will be 512 in KiB per frame with 20 total vbos, an estimated 8192 enities fit in each batch with 4 verts per entity
[INFO   ] [Kivent      ] renderer allocated 26260 KiB
[INFO   ] [KivEnt      ] We will need 28820 KiB for game, we have 102400 KiB

Let's draw more objects than we reserved, change the draw_some_stuff function to draw 12,000 entities.

    def draw_some_stuff(self):
        init_entity = self.gameworld.init_entity
        for x in range(12000):

We will get a traceback:

   File "main.py", line 41, in draw_some_stuff
     ent = init_entity(create_dict, ['position', 'renderer'])
   File "kivent_core/gameworld.pyx", line 357, in kivent_core.gameworld.GameWorld.init_entity (kivent_core/gameworld.c:6724)
   File "kivent_core/gameworld.pyx", line 338, in kivent_core.gameworld.GameWorld.get_entity (kivent_core/gameworld.c:6530)
   File "kivent_core/managers/entity_manager.pyx", line 139, in kivent_core.managers.entity_manager.EntityManager.generate_entity (kivent_core/managers/entity_manager.c:2084)
   File "kivent_core/memory_handlers/zone.pyx", line 271, in kivent_core.memory_handlers.zone.MemoryZone.get_free_slot (kivent_core/memory_handlers/zone.c:2527)
   File "kivent_core/memory_handlers/pool.pyx", line 170, in kivent_core.memory_handlers.pool.MemoryPool.get_free_slot (kivent_core/memory_handlers/pool.c:1627)
 MemoryError

If you are receiving a memory error you either need to increase the total memory available for your game or you need to raise the count for how many entities are in the zone you are creating your entity in.

Full code:

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.clock import Clock
from kivy.core.window import Window
from random import randint, choice
import kivent_core
from kivent_core.gameworld import GameWorld
from kivent_core.systems.position_systems import PositionSystem2D
from kivent_core.systems.renderers import Renderer
from kivent_core.managers.resource_managers import texture_manager
from kivy.properties import StringProperty

texture_manager.load_atlas('assets/background_objects.atlas')

class TestGame(Widget):
    def __init__(self, **kwargs):
        super(TestGame, self).__init__(**kwargs)
        self.gameworld.init_gameworld(
            ['renderer', 'position'],
            callback=self.init_game)

    def init_game(self):
        self.setup_states()
        self.load_models()
        self.set_state()
        self.draw_some_stuff()

    def load_models(self):
        model_manager = self.gameworld.model_manager
        model_manager.load_textured_rectangle('vertex_format_4f', 7., 7., 
            'star1', 'star1-4')
        model_manager.load_textured_rectangle('vertex_format_4f', 10., 10., 
            'star1', 'star1-4-2')

    def draw_some_stuff(self):
        init_entity = self.gameworld.init_entity
        for x in range(5000):
            pos = randint(0, Window.width), randint(0, Window.height)
            model_key = choice(['star1-4', 'star1-4-2'])
            create_dict = {
                'position': pos,
                'renderer': {'texture': 'star1', 
                    'model_key': model_key},
            }
            ent = init_entity(create_dict, ['position', 'renderer'])
        #If you do not set Renderer.force_update to True, call update_trigger
        #self.ids.renderer.update_trigger()

    def setup_states(self):
        self.gameworld.add_state(state_name='main', 
            systems_added=['renderer'],
            systems_removed=[], systems_paused=[],
            systems_unpaused=['renderer'],
            screenmanager_screen='main')

    def set_state(self):
        self.gameworld.state = 'main'

class DebugPanel(Widget):
    fps = StringProperty(None)

    def __init__(self, **kwargs):
        super(DebugPanel, self).__init__(**kwargs)
        Clock.schedule_once(self.update_fps)

    def update_fps(self,dt):
        self.fps = str(int(Clock.get_fps()))
        Clock.schedule_once(self.update_fps, .05)

class YourAppNameApp(App):
    def build(self):
        Window.clearcolor = (0, 0, 0, 1.)

if __name__ == '__main__':
    YourAppNameApp().run()
#:kivy 1.9.0

TestGame:

<TestGame>:
    gameworld: gameworld
    GameWorld:
        id: gameworld
        gamescreenmanager: gamescreenmanager
        size_of_gameworld: 100*1024
        size_of_entity_block: 128
        system_count: 2
        zones: {'general': 10000}
        PositionSystem2D:
            system_id: 'position'
            gameworld: gameworld
            zones: ['general']
            size_of_component_block: 128
        Renderer:
            id: renderer
            gameworld: gameworld
            system_id: 'renderer'
            zones: ['general']
            frame_count: 1
            updateable: False
            force_update: True
            size_of_batches: 512
            size_of_component_block: 128
            shader_source: 'assets/glsl/positionshader.glsl'
    GameScreenManager:
        id: gamescreenmanager
        size: root.size
        pos: root.pos
        gameworld: gameworld

<GameScreenManager>:
    MainScreen:
        id: main_screen

<MainScreen@GameScreen>:
    name: 'main'
    FloatLayout:
        DebugPanel:
            size_hint: (.2, .1)
            pos_hint: {'x': .225, 'y': .025}

<DebugPanel>:
    Label:
        pos: root.pos
        size: root.size
        font_size: root.size[1]*.5
        halign: 'center'
        valign: 'middle'
        color: (1,1,1,1)
        text: 'FPS: ' + root.fps if root.fps != None else 'FPS:'

Continue to Getting Started 2: Creating a GameSystem