Kivent Tutorial

Part 1: Setting up and App

https://github.com/kivy/kivent/wiki/Getting-Started-1:-Setting-Up-an-App

Start with the boilerplate debug code.


In [ ]:
%%file debug.kv
#:import random random
    
<Widget>:
    canvas.after:
        Color:
            rgba: 1,1,1,.5
        Line:
            rectangle: self.x, self.y, self.width, self.height
            width: 2
                
<DebugLabel@Button>:
    size: self.parent.size
    pos: self.parent.pos
    background_color: random.random(), random.random(), random.random(), 0.6
    text: 'debuglabel'

Now make a default kivy app consisting of main.py and yourappname.kv. (Note the reference to debug.kv in yourappname.kv and the debug label).


In [ ]:
%%file main.py
from kivy.app import App
from kivy.uix.widget import Widget

class TestGame(Widget):
    pass

class YourAppNameApp(App):
    def build(self):
        pass
    
if __name__ == '__main__':
    YourAppNameApp().run()

In [ ]:
%%file yourappname.kv
#:include debug.kv
#:kivy 1.9.0

TestGame:

<TestGame>:
    DebugLabel:
        text: "testgame"

In [ ]:
# run your app
!python main.py

Now check out two different ways to do the exact same thing with different instantiation code.


In [ ]:
%%file main.py
from kivy.app import App
from kivy.uix.widget import Widget

class TestGame(Widget):
    pass

class YourAppNameApp(App):
    def build(self):
        return TestGame() ## change this from pass
    
if __name__ == '__main__':
    YourAppNameApp().run()

In [ ]:
%%file yourappname.kv
#:include debug.kv
#:kivy 1.9.0

## Deleted the refernce to TestGame as the root widget

<TestGame>:
    DebugLabel:
        text: "testgame"

In [ ]:
# run your app. you should get the same thing. 
!python main.py

In [ ]:
%%file main.py
from kivy.app import App
from kivy.uix.widget import Widget

# The TestGame class is now completely defined in the .kv file

class YourAppNameApp(App):
    def build(self):
        pass
    
if __name__ == '__main__':
    YourAppNameApp().run()

In [ ]:
%%file yourappname.kv
#:include debug.kv
#:kivy 1.9.0

TestGame:
    
<TestGame@Widget>: ## adding makes TestGame a Widget class
    DebugLabel:
        text: "testgame"

In [ ]:
# run it again
!python main.py

Now in pure python with no .kv file...hmmmm...later.

Time to add some kivent

We'll go back to the first version of default app.


In [ ]:
%%file main.py
from kivy.app import App
from kivy.uix.widget import Widget
import kivent_core ## add this import statement to kivent

class TestGame(Widget):
    pass

class YourAppNameApp(App):
    def build(self):
        pass
    
if __name__ == '__main__':
    YourAppNameApp().run()

First add the GameWorld Widget that will manage the game data. More specifically, it keeps track of the components and their updates.

  • Thought: abstraction of entity-component as a rows vs. columns and the update methods of the GameSystem act on the columns.

In [ ]:
%%file yourappname.kv
#:include debug.kv
#:kivy 1.9.0

TestGame:

<TestGame@Widget>: ## adding @Widget makes this the root widget
    gameworld: _gameworld  ##reference to GameWorld id
    DebugLabel:
        text: "testgame"
    GameWorld:
        id: _gameworld
        size_of_gameworld: 100*1024 ## required see explanation
        size_of_entity_block: 128 ## required see explanation
        system_count: 8 ## max number of GameSystems with components
        zones: {'general':10000}
        DebugLabel:
            text: "gameworld"

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 http://kivent.org/docs/memory_handlers.html.

Now add the GameScreenManager which will handle its Screens interactions with the GameWorld's state management.


In [ ]:
%%file yourappname.kv
#:include debug.kv
#:kivy 1.9.0

TestGame:

<TestGame>: 
    gameworld: _gameworld  
    DebugLabel:
        text: "testgame"
    GameWorld:
        id: _gameworld
        gamescreenmanager: _gamescreenmanager ## reference to GameScreenManager id
        size_of_gameworld: 100*1024 ## number of Kibibytes allocated for the GameWorld to hold component data
        size_of_entity_block: 128 ## kibibytes set aside for each entity
        system_count: 8 ## max number of components
        zones: {'general':10000} # regions to reserve in memory and how large to make the regions
        DebugLabel:
            text: "gameworld"
    GameScreenManager:
        id: _gamescreenmanager
        size: root.size
        pos: root.pos
        gameworld: _gameworld
        GameScreen:
            name:'main'

Now for the python part.

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:


In [ ]:
%%file main.py
from kivy.app import App
from kivy.uix.widget import Widget
import kivent_core ## add this import statement to kivent

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

To check if things are working correctly, go ahead and run your code. Your screen output should still look the same, but now now you will get kivent logs with information telling you how much space has been allocated for your game:

[INFO              ] [KivEnt      ] Vertex Format: vertex_format_4f registered. Size per vertex is: 16. Format is [('pos', 2, 'float', 0, False), ('uvs', 2, 'float', 8, False)].
[INFO              ] [KivEnt      ] Vertex Format: vertex_format_7f registered. Size per vertex is: 28. Format is [('pos', 2, 'float', 0, False), ('uvs', 2, 'float', 8, False), ('rot', 1, 'float', 16, False), ('center', 2, 'float', 20, False)].
[INFO              ] [KivEnt      ] Vertex Format: vertex_format_4f4ub registered. Size per vertex is: 20. Format is [('pos', 2, 'float', 0, False), ('uvs', 2, 'float', 8, False), ('vColor', 4, 'ubyte', 16, True)].
[INFO              ] [KivEnt      ] Model Manager reserved space for vertex format: vertex_format_4f. 75 KiB was reserved for vertices, fitting a total of 4800. 25 KiB was reserved for indices fitting a total of 12800.
[INFO              ] [KivEnt      ] Model Manager reserved space for vertex format: vertex_format_7f. 75 KiB was reserved for vertices, fitting a total of 2742. 25 KiB was reserved for indices fitting a total of 12800.
[INFO              ] [KivEnt      ] Model Manager reserved space for vertex format: vertex_format_4f4ub. 75 KiB was reserved for vertices, fitting a total of 3840. 25 KiB was reserved for indices fitting a total of 12800.
[INFO              ] [KivEnt      ] We will need 684 KiB for game, we have 102400 KiB

In particular:

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

gives 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.

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

In [ ]:
# insert screen output here

In [ ]:
#!python main.py

Actually adding GameSystems

http://www.kivent.org/docs/gamesystems.html

Position System

http://www.kivent.org/docs/gamesystems.html#position-systems We'll start by adding the position system component. We'll use the class PositionSystem2D. PositionSystem2D abstracts 2 dimensional position data out into its own system so that all other GameSystem can interact with the position of an Entity without having to know specifically about anything under the hood used to determine the actual position. This GameSystem does no processing of its own, it just holds data.

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.


In [ ]:
%%file yourappname.kv
#:include debug.kv
#:kivy 1.9.0

TestGame:

<TestGame>:
    gameworld: _gameworld
    DebugLabel:
        text: "testgame"
    GameWorld:
        id: _gameworld
        gamescreenmanager: _gamescreenmanager ## reference to GameScreenManager id
        size_of_gameworld: 100*1024 ## number of Kibibytes allocated for the GameWorld to hold component data
        size_of_entity_block: 128 ## kibibytes set aside for each entity
        system_count: 8 ## max number of components
        zones: {'general':10000} # regions to reserve in memory and how large to make the regions
        DebugLabel:
            text: "gameworld"
        PositionSystem2D:
            system_id: 'position'
            gameworld: _gameworld
            zones: ['general']
            size_of_component_block: 128

    GameScreenManager:
        id: _gamescreenmanager
        size: root.size
        pos: root.pos
        gameworld: _gameworld
        GameScreen:
            name:'main'

And add the corresponding initialization of the position system in main.py.


In [ ]:
%%file main.py
from kivy.app import App
from kivy.uix.widget import Widget
import kivent_core ## add this import statement to kivent

class TestGame(Widget):
    def __init__(self, **kwargs):
        super(TestGame, self).__init__(**kwargs)
        ## add the position system to the gameworld initialization
        self.gameworld.init_gameworld(['position'], 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()

Renderer System

Setting up a Renderer

See http://www.kivent.org/docs/gamesystems.html#rendering-systems

class kivent_core.systems.renderers.Renderer(**kwargs)

We will make this renderer static as we will use 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.


In [ ]:
%%file yourappname.kv
#:include debug.kv
#:kivy 1.9.0

TestGame:

<TestGame>:
    gameworld: _gameworld
    DebugLabel:
        text: "testgame"
    GameWorld:
        id: _gameworld
        gamescreenmanager: _gamescreenmanager
        size_of_gameworld: 100*1024
        size_of_entity_block: 128
        system_count: 8
        zones: {'general':10000}
        DebugLabel:
            text: "gameworld"
        PositionSystem2D:
            system_id: 'position'
            gameworld: _gameworld
            zones: ['general']
            size_of_component_block: 128
        Renderer: ### add a renderer
            id: _renderer
            gameworld: _gameworld
            system_id: 'renderer'
            zones: ['general']
            frame_count: 1
            updateable: False ## updateable is as attribute of the GameSystem class
            force_update: True
            size_of_batches: 128
            size_of_component_block: 128
            ### there's an error in the online tutorial here...it should be
            shader_source: 'assets/glsl/positionshader.glsl'
    GameScreenManager:
        id: _gamescreenmanager
        size: root.size
        pos: root.pos
        gameworld: _gameworld
        GameScreen:
            name:'main'

And now add the renderer to the gameworld initialization.


In [ ]:
%%file main.py
from kivy.app import App
from kivy.uix.widget import Widget
import kivent_core ## add this import statement to kivent

class TestGame(Widget):
    def __init__(self, **kwargs):
        super(TestGame, self).__init__(**kwargs)
        ## add the renderer system to the gameworld initialization
        self.gameworld.init_gameworld(['position', 'renderer'], 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()

Run your code again to make sure it works. Nothing in the appearance will have changed.


In [ ]:
!python main.py

Adding Assets

Now, we have GameSystems set up, but they don't do anything yet. We need some assets to display. First off, load the sprites that we will want to display. To do this, use the GameWorld model_manager. The model manager handles the loading of VertexModels. You should only load model data using this ModelManager. Do not instantiate your own.

See http://www.kivent.org/docs/rendering.html?highlight=vertexformat#models for more on VertexModels.

Note the new import statements.


In [ ]:
%%file main.py
from kivy.app import App
from kivy.uix.widget import Widget
import kivent_core
from kivent_core.managers.resource_managers import texture_manager

## load the atlas with our sprites
texture_manager.load_atlas('assets/background_objects.atlas')

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

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

    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'

    def load_models(self):
        ### load a model of the 'star1' texture with 2 different sizes (7., 7.) and (10., 10.).
        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')


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

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

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

from random import randint import Window


In [ ]:
%%file main.py
from kivy.app import App
from kivy.uix.widget import Widget
import kivent_core
from kivent_core.managers.resource_managers import texture_manager
from random import randint, choice
from kivy.core.window import Window


## load the atlas with our sprites
texture_manager.load_atlas('assets/background_objects.atlas')

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

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

    def setup_states(self):
        ## add 'renderer' here to make sure the canvas knows about it and is unpaused
        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'

    def load_models(self):
        ### load a model of the 'star1' texture with 2 different sizes (7., 7.) and (10., 10.).
        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(1000):
            pos = randint(0, Window.width), randint(0, Window.height)
            model_key = choice(['star1-4', 'star1-4-2'])
            ## create_dict will dictate the order to create components
            create_dict = {
                'position':pos,
                'renderer': {'texture': 'star1', 'model_key': model_key}}
            ent = init_entity(create_dict, ['position', 'renderer'])


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

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

If you run things now, you should be able to see stuff!!! Yay!!

  • Add a screenshot here

In [ ]:

Add a debugging widget

We'll add a Widget to the GameScreenManager. It will display the current frame-per-second (FPS) performance of our application. Note the new import statments in the python part.


In [ ]:
%%file yourappname.kv
#:include debug.kv
#:kivy 1.9.0

TestGame:

<TestGame>:
    gameworld: _gameworld
    DebugLabel:
        text: "testgame"
    GameWorld:
        id: _gameworld
        gamescreenmanager: _gamescreenmanager
        size_of_gameworld: 100*1024
        size_of_entity_block: 128
        system_count: 8
        zones: {'general':10000}
        DebugLabel:
            text: "gameworld"
        PositionSystem2D:
            system_id: 'position'
            gameworld: _gameworld
            zones: ['general']
            size_of_component_block: 128
        Renderer: ### add a renderer
            id: _renderer
            gameworld: _gameworld
            system_id: 'renderer'
            zones: ['general']
            frame_count: 1
            updateable: False ## updateable is as attribute of the GameSystem class
            force_update: True
            size_of_batches: 128
            size_of_component_block: 128
            ### there's an error in the online tutorial here...it should be
            shader_source: 'assets/glsl/positionshader.glsl'
    GameScreenManager:
        id: _gamescreenmanager
        size: root.size
        pos: root.pos
        gameworld: _gameworld
        GameScreen:
            name:'main'

<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:'

In [ ]:
%%file main.py
from kivy.app import App
from kivy.uix.widget import Widget
import kivent_core
from kivent_core.managers.resource_managers import texture_manager
from random import randint, choice
from kivy.core.window import Window
from kivy.clock import Clock
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(['position', 'renderer'], callback = self.init_game)

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

    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'

    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(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}}
            ent = init_entity(create_dict, ['position', 'renderer'])

## the debug panel
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):
        pass

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

In [ ]:
#!python main.py

Finally, the end of section 1.


In [ ]: