Skip to content

Getting Started 5: Controlling the Viewing Area

SleepProgger edited this page Apr 25, 2017 · 8 revisions

The GameView System

The GameView is a GameSystem that does not hold its own entities but operates on other systems. GameView has properties for controlling the viewing area, and every GameSystem have a gameview StringProperty. When the system_id of a GameView is set for a GameSystem.gameview, that GameSystem will be added to the GameView widget instead of directly to the GameWorld. This allows you to control the viewing area of some parts of your game but not others, and also handle transforming the touch input.

In this tutorial, we will begin with the code from Getting Started 4.

The content of this tutorial covers the directories:

examples/6_controlling_the_viewing_area

Prerequisites: You will need to have compiled kivent_core, kivent_cymunk, and cymunk, in addition to having kivy and all its requirements installed.

Adding a GameView

Into our GameWorld definition in kv we will add the GameView, let's call it 'camera1':

<TestGame>:
    gameworld: gameworld
    app: app
    GameWorld:
        id: gameworld
        gamescreenmanager: gamescreenmanager
        size_of_gameworld: 100*1024
        zones: {'general': 20000, 'touch': 100}
        PositionSystem2D:
            system_id: 'position'
            gameworld: gameworld
            zones: ['general', 'touch']
        RotateSystem2D:
            system_id: 'rotate'
            gameworld: gameworld
            zones: ['general']
        RotateRenderer:
            gameworld: gameworld
            zones: ['general']
            shader_source: 'assets/glsl/positionrotateshader.glsl'
            gameview: 'camera1'
        CymunkPhysics:
            gameworld: root.gameworld
            zones: ['general']
        CymunkTouchSystem:
            gameworld: root.gameworld
            zones: ['touch']
            zone_to_use: 'touch'
            physics_system: 'cymunk_physics'
            touch_radius: 30
            gameview: 'camera1'
        GameView:
            system_id: 'camera1'
            gameworld: gameworld
            size: root.size
            window_size: root.size
            pos: root.pos
            do_scroll: True

We need to tell the GameView how big it is and where we want it, here we want it to take up the whole screen so we set size to the root size and pos to the root pos.

do_scroll is a BooleanProperty that control whether or not the user can touch/mouse drag to pan the viewing area. We will set it to True so we can look around as the asteroids drift off screen.

Notice that we have set the gameview StringProperty for both RotateRenderer and CymunkTouchSystem. This is because RotateRenderer will draw tot he gameview's viewing area and we must transform the touch on the screen into camera space in order to accurately detect which object we are touching.

Python Changes

First let us add the new GameView to our init_game function:

    def __init__(self, **kwargs):
        super(TestGame, self).__init__(**kwargs)
        self.gameworld.init_gameworld(
            ['cymunk_physics', 'rotate_renderer', 'rotate', 'position',
            'cymunk_touch', 'camera1'],
            callback=self.init_game)

Secondly, let's adjust the draw_some_stuff function to now draw within the bounds of the GameView, instead of just 0 and the Window.size.

    def draw_some_stuff(self):
        delete_time = 2.5
        gameview = self.gameworld.system_manager['camera1']
        x, y = int(-gameview.camera_pos[0]), int(-gameview.camera_pos[1])
        w, h =  int(gameview.size[0] + x), int(gameview.size[1] + y)
        create_asteroid = self.create_asteroid
        for i in range(100):
            pos = (randint(x, w), randint(y, h))
            ent_id = create_asteroid(pos)
        self.app.count += 100

We must take the negative of the camera_pos as it moves with the inverse of the viewing field (so that entity pos + camera_pos = pos of entity in screen space). Finally, we add the position of the camera to the size of the GameView to get the right and top of the camera's area.

The GameView has other properties to allow it to follow a specific entity, scale in and out, and more related functionality: see the documentation for details.

Full Code

main.py

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
from math import radians, pi, sin, cos
import kivent_core
import kivent_cymunk
from kivent_core.gameworld import GameWorld
from kivent_core.managers.resource_managers import texture_manager
from kivent_core.systems.renderers import RotateRenderer
from kivent_core.systems.position_systems import PositionSystem2D
from kivent_core.systems.rotate_systems import RotateSystem2D
from kivent_cymunk.interaction import CymunkTouchSystem
from kivy.properties import StringProperty, NumericProperty
from functools import partial


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


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

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

    def destroy_created_entity(self, ent_id, dt):
        self.gameworld.remove_entity(ent_id)
        self.app.count -= 1

    def draw_some_stuff(self):
        delete_time = 2.5
        gameview = self.gameworld.system_manager['camera1']
        x, y = int(-gameview.camera_pos[0]), int(-gameview.camera_pos[1])
        w, h =  int(gameview.size[0] + x), int(gameview.size[1] + y)
        create_asteroid = self.create_asteroid
        for i in range(100):
            pos = (randint(x, w), randint(y, h))
            ent_id = create_asteroid(pos)
        self.app.count += 100

    def create_asteroid(self, pos):
        x_vel = randint(-500, 500)
        y_vel = randint(-500, 500)
        angle = radians(randint(-360, 360))
        angular_velocity = radians(randint(-150, -150))
        shape_dict = {'inner_radius': 0, 'outer_radius': 22, 
            'mass': 50, 'offset': (0, 0)}
        col_shape = {'shape_type': 'circle', 'elasticity': .5, 
            'collision_type': 1, 'shape_info': shape_dict, 'friction': 1.0}
        col_shapes = [col_shape]
        physics_component = {'main_shape': 'circle', 
            'velocity': (x_vel, y_vel), 
            'position': pos, 'angle': angle, 
            'angular_velocity': angular_velocity, 
            'vel_limit': 250, 
            'ang_vel_limit': radians(200), 
            'mass': 50, 'col_shapes': col_shapes}
        create_component_dict = {'cymunk_physics': physics_component, 
            'rotate_renderer': {'texture': 'asteroid1', 
            'size': (45, 45),
            'render': True}, 
            'position': pos, 'rotate': 0, }
        component_order = ['position', 'rotate', 'rotate_renderer', 
            'cymunk_physics',]
        return self.gameworld.init_entity(
            create_component_dict, component_order)

    def update(self, dt):
        self.gameworld.update(dt)

    def setup_states(self):
        self.gameworld.add_state(state_name='main', 
            systems_added=['rotate_renderer'],
            systems_removed=[], systems_paused=[],
            systems_unpaused=['rotate_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):
    count = NumericProperty(0)


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

yourappname.kv

#:kivy 1.9.0

TestGame:

<TestGame>:
    gameworld: gameworld
    app: app
    GameWorld:
        id: gameworld
        gamescreenmanager: gamescreenmanager
        size_of_gameworld: 100*1024
        zones: {'general': 20000, 'touch': 100}
        PositionSystem2D:
            system_id: 'position'
            gameworld: gameworld
            zones: ['general', 'touch']
        RotateSystem2D:
            system_id: 'rotate'
            gameworld: gameworld
            zones: ['general']
        RotateRenderer:
            gameworld: gameworld
            zones: ['general']
            shader_source: 'assets/glsl/positionrotateshader.glsl'
            gameview: 'camera1'
        CymunkPhysics:
            gameworld: root.gameworld
            zones: ['general']
        CymunkTouchSystem:
            gameworld: root.gameworld
            zones: ['touch']
            zone_to_use: 'touch'
            physics_system: 'cymunk_physics'
            touch_radius: 30
            gameview: 'camera1'
        GameView:
            system_id: 'camera1'
            gameworld: gameworld
            size: root.size
            window_size: root.size
            pos: root.pos
            do_scroll: True
    GameScreenManager:
        id: gamescreenmanager
        size: root.size
        pos: root.pos
        gameworld: gameworld

<GameScreenManager>:
    MainScreen:
        id: main_screen

<MainScreen@GameScreen>:
    name: 'main'
    FloatLayout:
        Button:
            text: 'Draw Some Stuff'
            size_hint: (.2, .1)
            pos_hint: {'x': .025, 'y': .025}
            on_release: app.root.draw_some_stuff()
        DebugPanel:
            size_hint: (.2, .1)
            pos_hint: {'x': .225, 'y': .025}
        Label:
            text: str(app.count)
            size_hint: (.2, .1)
            font_size: 24
            pos_hint: {'x': .425, '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 6: Creating a Renderer