Near Infinite Wrapping Tile Worlds in 240 lines

Mon 25 January 2016

This blog post takes a look at the solution to a cool problem that has been going around the #kivy irc recently: How to render an infinitely wrapping tilemap where going to one edge brings us back around to the other.

The Approach

In my opinion, the most straightforward way to do this is to actually just draw the world physically over and over. This will require coordinate conversions between tile space, world space, and actual physical location on screen, but it gives us the benefit of being able to build our game without other areas caring about the tiling whatsoever. For instance, our physics world will no longer have to know about the wrap at all since we will just draw a copy of the world at the bounds.

Visually, we would take a map that looks like:

Unwrapped Map

Placed on the slice of an infite grid with tile (0, 0) being the one labeled '1':

On Grid

When a row or column that is larger than the existing map comes into view, we simply redraw the tile that should be there at that new location in grid space. So that tile 4, 0 is the same as tile 0, 0 in our example image.

Expanding

Thus we would actually end up rendering copies of the map over and over, looking something like:

Wrapping

This approach will work until the offset of the camera becomes really large or really small, at which point the floating point calculations might get a little crazy. It would take a really long time to even scroll out that far though. If this really becomes an issue for your game design, you can periodically 'reset' everything back around the origin.

The main advantage of this method is for the purpose of physics simulation, working with our camera, and other types of calculation that typically assume a continous grid. Our game will exist in one continous coordinate system, however the world will appear as if it is repeating to the player.

Setting Up Our Game

We are going to dive right into setting up a KivEnt app to solve this problem, if you want a quick refresher on how this works check out the Getting Started 1: Setting Up an App.

We are going to build a very simple KivEnt demo of this functionality, using 4 GameSystems. The Renderer, the GameView, the PositionSystem2D, and one GameSystem of our own design, the TileSystem.

Our tilemap will be made up of 2 types of entities:

  1. A PositionComponent2D, RenderComponent entity that is the actual sprite drawn to the screen for each tile.

  2. A entity with a single component- that of the TileSystem we are going to build. This system will represent the tiles as they exist in world space, and manage drawing the appropriate tile at the appropriate place to handle the wrapping.

So let's set up our GameWorld in the kv file, adding the 4 systems:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#:kivy 1.9.0
#:import get_asset_path __main__.get_asset_path

TestGame:

<TestGame>:
    gameworld: gameworld
    GameWorld:
        id: gameworld
        gamescreenmanager: gamescreenmanager
        size_of_gameworld: 100*1024
        system_count: 3
        zones: {'general': 10000}
        PositionSystem2D:
            system_id: 'position'
            gameworld: gameworld
            zones: ['general']
        Renderer:
            id: renderer
            gameworld: gameworld
            system_id: 'renderer'
            zones: ['general']
            frame_count: 2
            gameview: 'camera1'
            updateable: True
            shader_source: get_asset_path('positionshader.glsl', 'assets/glsl')
        GameView:
            system_id: 'camera1'
            gameworld: gameworld
            size: root.size
            pos: root.pos
            do_scroll_lock: False
            id: camera1
        TileSystem:
            system_id: 'tiles'
            id: tiles
            gameworld: gameworld
            camera_pos: camera1.camera_pos
            camera_size: camera1.size
    GameScreenManager:
        id: gamescreenmanager

We set system_count to 3 because only 3 of the GameSystems actually track components, GameView does not attach components to entities, instead operating on other GameSystems. Make sure to assign the system_id of your GameView to the Renderer's 'gameview' attribute so that our render takes place in camera space.

Finally, we are going to bind TileSystem to the size and pos of the camera so that it knows what bounds to be drawing the tiles in.

The TileSystem

This GameSystem is going to be responsible for converting between to and from tile world space and on screen position, as well as actually managing the creation of the sprite entities representing our tiles on screen.

First let's talk attributes:

1
2
3
4
5
6
7
class TileSystem(GameSystem):
    tile_width = NumericProperty(64.)
    tile_height = NumericProperty(64.)
    tiles_in_x = NumericProperty(100)
    tiles_in_y = NumericProperty(100)
    camera_pos = ListProperty(None, allownone=True)
    camera_size = ListProperty(None, allownone=True)

We need to set the size of the tile, in width and height, and the size of our map in tiles. We will also need to create properties to bind to the camera's pos and size so we can determine which tiles are on screen. Now let's make sure that we properly respond to events on these properties. First to handle the tile count for the map:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    def __init__(self, **kwargs):
        super(TileSystem, self).__init__(**kwargs)
        self.calc_tiles_trigger = Clock.create_trigger(self.calc_tiles)
        self.calc_tiles_trigger()

    def calc_tiles(self, dt):
        self.tiles = [[None for y in range(self.tiles_in_y)] for x in range(
                     self.tiles_in_x)]

    def on_tiles_in_x(self, instance, value):
        self.calc_tiles_trigger()

    def on_tiles_in_y(self, instance, value):
        self.calc_tiles_trigger()

We initialize an array of arrays tiles_in_x by tiles_in_y of size and make sure that we create a new one whenever either changes. We create a Clock trigger to ensure that the calc_tiles function is only called once a frame, no matter how often tiles_in_x and tiles_in_y change. That way if we are changing both we only trigger 1 update instead of calculating new_x, old_y and then new_x, new_y.

Secondly, for the camera updates, we do almost the same thing:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    def __init__(self, **kwargs):
        super(TileSystem, self).__init__(**kwargs)
        self.calc_tiles_trigger = Clock.create_trigger(self.calc_tiles)
        self.tile_trigger = Clock.create_trigger(self.handle_tile_drawing)
        self.calc_tiles_trigger()

    def on_camera_pos(self, instance, value):
        self.tile_trigger()

    def on_camera_size(self, instance, value):
        self.tile_trigger()

    def handle_tile_drawing(self, dt):
        pass

We create a new function that will do the heavy lifting of determing which tiles are coming onto the screen and need to be drawn, and which tiles are leaving the screen and need to be removed. We'll cover writing this function in detail later. For now let's move on to creating the entities for the TileSystem (upon which handle_tile_drawing will operate).

1
2
3
4
5
6
7
    def init_component(self, component_index, entity_id, zone, args):
        component = self.components[component_index]
        component.entity_id = entity_id
        component.texture = args.get('texture')
        component.tile_pos = tx, ty = args.get('tile_pos')
        component.current_entity = None
        self.tiles[tx][ty] = component_index

We will override the default logic to do the following:

  1. Store the position in tile coordinates (the 0...tile_count integers) that represent which tile this is in our world.

  2. Store the texture to draw for this tile, for now we have 4 lovely choices:

  3. Set the current_entity attribute to None, we will use this attribute to hold the entity_id of the entity that is actually rendering this tile.

  4. Finally, set this entity to be the tile in the tiles list of lists.

Coordinate Conversions

We now need to build up functions to convert to and from the various coordinate systems we have going on. We have three major coordinate systems occuring.

Tile Space: 
This is an integer domain that is 0 to tiles_in_x, 0 to tiles_in_y.

World Space: 
This is a floating point domain that is 0. to tile_width*tiles_in_x, 0. to 
tile_height*tiles_in_y.

Camera Space:
This is the camera coordinate space which is effectively -infinity to infinity
in either direction, floating point.

When we draw a tile to our world, we draw it in camera space. However, our list of lists representing the tile world begins in tile space. This section will detail the conversions necessary to go between these spaces.

First to go from camera space to world space:

1
2
3
4
    def get_world_pos(self, pos):
        tile_max_x = self.tiles_in_x * self.tile_width
        tile_max_y = self.tiles_in_y * self.tile_height
        return (pos[0] % tile_max_x, pos[1] % tile_max_y)

We simply calculate the size of the world and take the mod of the actual camera position by the size of the world. This will return a floating point between 0 and tile_max_x and 0 and tile_max_y.

Secondly, to go from tile space to world space:

1
2
3
    def get_world_pos_from_tile_pos(self, tile_pos):
        return (tile_pos[0] * self.tile_width, 
                tile_pos[1] * self.tile_height)

This is probably the easiest conversion, the location of a tile in world space is simply its tile position mulitplied by the width and height of the tiles. To do the reverse and get tile space from world space:

1
2
3
    def get_tile_at_world_pos(self, world_pos):
        return (int(world_pos[0] // self.tile_width),
                int(world_pos[1] // self.tile_height))

Since tile_width and tile_height are most likely floating point numbers we need to cast back to an int before returning.

Finally, we need to be able to calculate where a tile is at on the screen given its tile position.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    def get_screen_pos_from_tile_pos(self, tile_pos):
        #First we grab the inverse of the position of the camera. 
        cx, cy = -self.camera_pos[0], -self.camera_pos[1]
        #Calculate the full size of our space.
        tile_max_x = self.tiles_in_x * self.tile_width
        tile_max_y = self.tiles_in_y * self.tile_height
        #Get the tile located at the camera's position.
        tcx, tcy = self.get_tile_at_world_pos(
            self.get_world_pos((cx, cy)))
        #Get the position in the world of the tile located at the
        #camera's position.
        wx, wy = self.get_world_pos_from_tile_pos(tile_pos)
        origin_x, origin_y = None, None
        #This section calculates the location of the origin in camera
        #space for the chunk that the tile is occuring in.
        #If the tile position is less than the cameras tile it must appear
        #on the left side of the screen and thus the next 'chunk' of the
        #repeating map. Else it occurs in the same 'chunk' as the camera.
        if tile_pos[0] < tcx:
            origin_x = (cx // tile_max_x + 1) * tile_max_x
        else:
            origin_x = (cx // tile_max_x) * tile_max_x
        if tile_pos[1] < tcy:
            origin_y = (cy // tile_max_y + 1) * tile_max_y
        else:
            origin_y = (cy // tile_max_y) * tile_max_y
        #Now offset the location of the tile's chunk origin by the location
        #of the tile.
        return origin_x + wx, origin_y + wy

Now that we have all the necessary conversions, we can calculate which tiles are actually in view of the camera at any given moment. We do this by finding out how many tiles fit inside the cameras dimensions and which tile is at the position of the camera (the GameView.camera_pos is the bottom left corner of the camera). We will return a list of the tile entities that are on the screen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
    def calculate_tiles_in_view(self):
        #Get the inverse of the camera position again.
        cx, cy = -self.camera_pos[0], -self.camera_pos[1]
        #Get the camera size.
        cw, ch = self.camera_size
        #Get the width and height of the tiles.
        tile_width = self.tile_width
        tile_height = self.tile_height
        #Get the tile count.
        tiles_in_x = self.tiles_in_x
        tiles_in_y = self.tiles_in_y
        #Calculate the position in the world of the camera.
        world_pos = self.get_world_pos((cx, cy))
        #Calculate how many tiles fit in the cameras view, adding 2 for
        #a padding of 1 tile on either side.
        x_count = int(cw // tile_width) + 2
        y_count = int(ch // tile_height) + 2
        #Calculate the tile at the location of the camera.
        starting_x, starting_y = self.get_tile_at_world_pos(world_pos)
        #Calculate how many tiles we will visit.
        end_x = starting_x + x_count
        end_y = starting_y + y_count
        #Create our return list
        tiles_in_view = []
        tiles_a = tiles_in_view.append
        tiles = self.tiles
        #For every tile from starting to end.
        for x in range(starting_x, end_x):
            #Calculate the actual tile coordinate, as we may have wrapped.
            actual_x = x % tiles_in_x
            for y in range(starting_y, end_y):
                actual_y = y % tiles_in_y
                #Find out what tile is there.
                tile = tiles[actual_x][actual_y]
                #if the tile exists at this location, add it to return list
                if tile is not None:
                    tiles_a(tile)
        return tiles_in_view

Now we have all the pieces to create our handle_tile_drawing function from earlier. Let's add one more line to our init function:

1
2
3
    def __init__(self, **kwargs):
        super(TileSystem, self).__init__(**kwargs)
        self.tiles_on_screen_last_frame = set()

We will use tiles_on_screen_last_frame to track which entities we have already added from the screen. Now to complete the job we just need to get the tiles on screen right now, and perform set addition to determine which tiles have been added to the screen and which have been removed.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
    def handle_tile_drawing(self, dt):
        #First, check to make sure we have a valid binding from our camera.
        if self.camera_pos is not None and self.camera_size is not None:
            #Get the tiles on screen last frame.
            last_frame = self.tiles_on_screen_last_frame
            #Get the components for the TileSystem.
            components = self.components
            #Bring our init and remove functions into the namespace.
            init_entity = self.gameworld.init_entity
            remove_entity = self.gameworld.remove_entity
            #Use our function from above to get the tiles on screen.
            tiles_in_view = set(self.calculate_tiles_in_view())
            #Function to convert from tile pos to screen pos
            screen_pos_from_tile_pos = self.get_screen_pos_from_tile_pos
            #Find the tiles that weren't on screen last frame.
            new = tiles_in_view - last_frame
            #Find the tiles that aren't on screen anymore.
            removed = last_frame - tiles_in_view
            #Update the on screen last frame set.
            self.tiles_on_screen_last_frame = tiles_in_view
            #For tiles that have been removed from screen, find their
            #component get the entity that represents the tile and remove
            #it. Set it back to None.
            for component_index in removed:
                tile_comp = self.components[component_index]
                remove_entity(tile_comp.current_entity)
                tile_comp.current_entity = None
            #For tiles that are added, create a new entity and store the id
            #to the entity under current_entity for the tile component.
            for component_index in new:
                tile_comp = self.components[component_index]
                screen_pos = screen_pos_from_tile_pos(
                        tile_comp.tile_pos)
                create_dict = {
                    'position': screen_pos,
                    'renderer': {'texture': tile_comp.texture, 
                                 'model_key': tile_comp.texture},
                    }
                ent = init_entity(create_dict, ['position', 'renderer'])
                tile_comp.current_entity = ent

There we go, we now have a fully operational wrapping tile map display system in the TileMap GameSystem. Keep in mind this approach won't work properly if the total size of the tile map is smaller than the size of the camera's viewing space. In this case we may end up with one tile on the screen twice, in which case we would need to expand current_entity to be able to store multiple tiles and add complexity to our remove case.

Loading a Tile Map

The only step left is to actually load some tiles into our TileSystem and check out the results. We are going to use 4 tiles:

Tile 1 Tile 2 Tile 3 Tile 4

First, let's load in each tile texture:

1
2
3
4
5
    def load_textures(self):
        texture_manager.load_image(get_asset_path('orange-tile.png', 'assets'))
        texture_manager.load_image(get_asset_path('purple-tile.png', 'assets'))
        texture_manager.load_image(get_asset_path('green-tile.png', 'assets'))
        texture_manager.load_image(get_asset_path('blue-tile.png', 'assets'))

Next, let's load a model for each texture. Each texture is 64. x 64. and we will be using the Renderer's 'vertex_format_4f'. We will load each tile with the same name as the texture for that tile for simplicity.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def load_models(self):
    model_manager = self.gameworld.model_manager
    model_manager.load_textured_rectangle('vertex_format_4f', 64., 64., 
                                          'orange-tile', 'orange-tile')
    model_manager.load_textured_rectangle('vertex_format_4f', 64., 64., 
                                          'purple-tile', 'purple-tile')
    model_manager.load_textured_rectangle('vertex_format_4f', 64., 64., 
                                          'green-tile', 'green-tile')
    model_manager.load_textured_rectangle('vertex_format_4f', 64., 64., 
                                          'blue-tile', 'blue-tile')

The final step is to actually load in some entities with TileSystem components to represent our map:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    def setup_tiles(self):
        init_entity = self.gameworld.init_entity
        tile_system = self.ids.tiles
        for x in range(tile_system.tiles_in_x):
            for y in range(tile_system.tiles_in_y):
                #randomly choose a texture for our tile.
                model_key = choice(['orange-tile', 'green-tile', 'purple-tile',
                                    'blue-tile'])
                create_dict = {
                    'tiles': {'texture': model_key, 'tile_pos': (x, y)},
                }
                ent = init_entity(create_dict, ['tiles'])
        #Trigger the first loading of the tile map.
        tile_system.tile_trigger()

There we go, put everything together and call all the loading functions in our init_gameworld callback:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class TestGame(Widget):
    def __init__(self, **kwargs):
        super(TestGame, self).__init__(**kwargs)
        self.gameworld.init_gameworld(
            ['renderer', 'position', 'camera1', 'tiles'],
            callback=self.init_game)

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

You should get something that looks like:

Full Code

Python File:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.clock import Clock
from random import choice
import kivent_core
from kivent_core.gameworld import GameWorld
from kivent_core.managers.resource_managers import texture_manager
from kivy.properties import StringProperty, NumericProperty, ListProperty
from os.path import dirname, join, abspath
from kivent_core.systems.gamesystem import GameSystem
from kivy.factory import Factory

def get_asset_path(asset, asset_loc):
    return join(dirname(dirname(abspath(__file__))), asset_loc, asset)

class TileSystem(GameSystem):
    tile_width = NumericProperty(64.)
    tile_height = NumericProperty(64.)
    tiles_in_x = NumericProperty(100)
    tiles_in_y = NumericProperty(100)
    camera_pos = ListProperty(None, allownone=True)
    camera_size = ListProperty(None, allownone=True)

    def __init__(self, **kwargs):
        super(TileSystem, self).__init__(**kwargs)
        self.tiles_on_screen_last_frame = set()
        self.tile_trigger = Clock.create_trigger(self.handle_tile_drawing)
        self.calc_tiles_trigger = Clock.create_trigger(self.calc_tiles)
        self.calc_tiles_trigger()

    def calc_tiles(self, dt):
        self.tiles = [[None for y in range(self.tiles_in_y)] for x in range(
                     self.tiles_in_x)]

    def on_tiles_in_x(self, instance, value):
        self.calc_tiles_trigger()

    def on_tiles_in_y(self, instance, value):
        self.calc_tiles_trigger()

    def on_camera_pos(self, instance, value):
        self.tile_trigger()

    def handle_tile_drawing(self, dt):
        if self.camera_pos is not None and self.camera_size is not None:
            last_frame = self.tiles_on_screen_last_frame
            components = self.components
            init_entity = self.gameworld.init_entity
            remove_entity = self.gameworld.remove_entity
            tiles_in_view = set(self.calculate_tiles_in_view())
            screen_pos_from_tile_pos = self.get_screen_pos_from_tile_pos
            new = tiles_in_view - last_frame
            removed = last_frame - tiles_in_view
            self.tiles_on_screen_last_frame = tiles_in_view
            for component_index in removed:
                tile_comp = self.components[component_index]
                remove_entity(tile_comp.current_entity)
                tile_comp.current_entity = None
            for component_index in new:
                tile_comp = self.components[component_index]
                screen_pos = screen_pos_from_tile_pos(
                        tile_comp.tile_pos)
                create_dict = {
                    'position': screen_pos,
                    'renderer': {'texture': tile_comp.texture, 
                                 'model_key': tile_comp.texture},
                    }
                ent = init_entity(create_dict, ['position', 'renderer'])
                tile_comp.current_entity = ent

    def on_camera_size(self, instance, value):
        self.tile_trigger()

    def get_world_pos(self, pos):
        tile_max_x = self.tiles_in_x * self.tile_width
        tile_max_y = self.tiles_in_y * self.tile_height
        return (pos[0] % tile_max_x, pos[1] % tile_max_y)

    def get_screen_pos_from_tile_pos(self, tile_pos):
        cx, cy = -self.camera_pos[0], -self.camera_pos[1]
        tile_max_x = self.tiles_in_x * self.tile_width
        tile_max_y = self.tiles_in_y * self.tile_height
        tcx, tcy = self.get_tile_at_world_pos(
            self.get_world_pos((cx, cy)))
        wx, wy = self.get_world_pos_from_tile_pos(tile_pos)
        origin_x, origin_y = None, None
        if tile_pos[0] < tcx:
            origin_x = (cx // tile_max_x + 1) * tile_max_x
        else:
            origin_x = (cx // tile_max_x) * tile_max_x
        if tile_pos[1] < tcy:
            origin_y = (cy // tile_max_y + 1) * tile_max_y
        else:
            origin_y = (cy // tile_max_y) * tile_max_y
        return origin_x + wx, origin_y + wy

    def get_world_pos_from_tile_pos(self, tile_pos):
        return (tile_pos[0] * self.tile_width, 
                tile_pos[1] * self.tile_height)

    def get_tile_at_world_pos(self, world_pos):
        return (int(world_pos[0] // self.tile_width),
                int(world_pos[1] // self.tile_height))

    def calculate_tiles_in_view(self):
        cx, cy = -self.camera_pos[0], -self.camera_pos[1]
        cw, ch = self.camera_size
        tile_width = self.tile_width
        tile_height = self.tile_height
        tiles_in_x = self.tiles_in_x
        tiles_in_y = self.tiles_in_y
        world_pos = self.get_world_pos((cx, cy))
        x_count = int(cw // tile_width) + 2
        y_count = int(ch // tile_height) + 2
        starting_x, starting_y = self.get_tile_at_world_pos(world_pos)
        end_x = starting_x + x_count
        end_y = starting_y + y_count
        tiles_in_view = []
        tiles_a = tiles_in_view.append
        tiles = self.tiles
        for x in range(starting_x, end_x):
            actual_x = x % tiles_in_x
            for y in range(starting_y, end_y):
                actual_y = y % tiles_in_y
                tile = tiles[actual_x][actual_y]
                if tile is not None:
                    tiles_a(tile)
        return tiles_in_view

    def init_component(self, component_index, entity_id, zone, args):
        component = self.components[component_index]
        component.entity_id = entity_id
        component.texture = args.get('texture')
        component.tile_pos = tx, ty = args.get('tile_pos')
        component.current_entity = None
        self.tiles[tx][ty] = component_index


Factory.register('TileSystem', cls=TileSystem)


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

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

    def load_textures(self):
        texture_manager.load_image(get_asset_path('orange-tile.png', 'assets'))
        texture_manager.load_image(get_asset_path('purple-tile.png', 'assets'))
        texture_manager.load_image(get_asset_path('green-tile.png', 'assets'))
        texture_manager.load_image(get_asset_path('blue-tile.png', 'assets'))

    def load_models(self):
        model_manager = self.gameworld.model_manager
        model_manager.load_textured_rectangle('vertex_format_4f', 64., 64., 
                                              'orange-tile', 'orange-tile')
        model_manager.load_textured_rectangle('vertex_format_4f', 64., 64., 
                                              'purple-tile', 'purple-tile')
        model_manager.load_textured_rectangle('vertex_format_4f', 64., 64., 
                                              'green-tile', 'green-tile')
        model_manager.load_textured_rectangle('vertex_format_4f', 64., 64., 
                                              'blue-tile', 'blue-tile')

    def setup_tiles(self):
        init_entity = self.gameworld.init_entity
        tile_system = self.ids.tiles
        for x in range(tile_system.tiles_in_x):
            for y in range(tile_system.tiles_in_y):
                model_key = choice(['orange-tile', 'green-tile', 'purple-tile',
                                    'blue-tile'])
                create_dict = {
                    'tiles': {'texture': model_key, 'tile_pos': (x, y)},
                }
                ent = init_entity(create_dict, ['tiles'])
        tile_system.tile_trigger()

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

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


class YourAppNameApp(App):
    pass


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

kv file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#:kivy 1.9.0
#:import get_asset_path __main__.get_asset_path

TestGame:

<TestGame>:
    gameworld: gameworld
    GameWorld:
        id: gameworld
        gamescreenmanager: gamescreenmanager
        size_of_gameworld: 100*1024
        system_count: 3
        zones: {'general': 10000}
        PositionSystem2D:
            system_id: 'position'
            gameworld: gameworld
            zones: ['general']
        Renderer:
            id: renderer
            gameworld: gameworld
            system_id: 'renderer'
            zones: ['general']
            frame_count: 2
            gameview: 'camera1'
            updateable: True
            shader_source: get_asset_path('positionshader.glsl', 'assets/glsl')
        GameView:
            system_id: 'camera1'
            gameworld: gameworld
            size: root.size
            pos: root.pos
            do_scroll_lock: False
            id: camera1
        TileSystem:
            system_id: 'tiles'
            id: tiles
            gameworld: gameworld
            camera_pos: camera1.camera_pos
            camera_size: camera1.size
    GameScreenManager:
        id: gamescreenmanager

blogroll