-
Notifications
You must be signed in to change notification settings - Fork 94
Getting Started 4: Interacting with the Physics System
This GameSystem provides input handling to convert touch or mouse movement into a dragging effect on physics objects. It does this by creating a rogue physics body (unattached to the cymunk.Space) and constrains it to the touched entity with a cymunk.PivotJoint. In this tutorial, we will begin with the code from Getting Started 3.
The content of this tutorial covers the directories:
- examples/5_interacting_with_physics
Prerequisites: You will need to have compiled kivent_core, kivent_cymunk, and cymunk, in addition to having kivy and all its requirements installed.
In example/4 our kv file looks like:
<TestGame>:
gameworld: gameworld
app: app
GameWorld:
id: gameworld
gamescreenmanager: gamescreenmanager
size_of_gameworld: 100*1024
zones: {'general': 20000}
PositionSystem2D:
system_id: 'position'
gameworld: gameworld
zones: ['general']
RotateSystem2D:
system_id: 'rotate'
gameworld: gameworld
zones: ['general']
RotateRenderer:
gameworld: gameworld
zones: ['general']
shader_source: 'assets/glsl/positionrotateshader.glsl'
CymunkPhysics:
gameworld: root.gameworld
zones: ['general']
GameScreenManager:
id: gamescreenmanager
size: root.size
pos: root.pos
gameworld: gameworld
We need to introduce the CymunkTouchSystem, telling it which zone to create its entities in, the system_id of the physics system, and add a new zone to our GameWorld.zones dict.
First add a new zone called 'touch' to the GameWorld. We can use a fairly low number of entities here as we will only ever have one per touch on screen at a time.
<TestGame>:
gameworld: gameworld
app: app
GameWorld:
id: gameworld
gamescreenmanager: gamescreenmanager
size_of_gameworld: 100*1024
zones: {'general': 20000, 'touch': 100}
This reserves 100 entities worth of components for any StaticMemGameSystem that uses the new 'touch' zone. Don't forget to add the new zone to the Positionsystem2D zones list as our new CymunkTouchSystem entities will also need a component from this system. Adding the new system, our kv looks like:
<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'
CymunkPhysics:
gameworld: root.gameworld
zones: ['general']
CymunkTouchSystem:
gameworld: root.gameworld
zones: ['touch']
zone_to_use: 'touch'
physics_system: 'cymunk_physics'
touch_radius: 30
In addition to declaring its zones and binding to the gameworld, we need to tell the system which zone it will create its entities in. CymunkTouchSystem automatically creates its entities for you in on_touch_down, we just need to tell it which zone to make these entities in with the zone_to_use property. In addition we must provide the system_id of the CymunkPhysics GameSystem, the default name is 'cymunk_physics' so we set the physics_system attribute to this. There are several other properties that will help you control the behaviour of this system, such as touch_radius which controls how big of an area is queried for collision around the touch. See module documentation for a full list.
The CymunkTouchSystem is fairly uninvolved, the only code we need to change is to tell the init_gameworld function to also check for our new GameSystem, the default name is 'cymunk_touch' but perhaps you have chosen a different name.
def __init__(self, **kwargs):
super(TestGame, self).__init__(**kwargs)
self.gameworld.init_gameworld(
['cymunk_physics', 'rotate_renderer', 'rotate', 'position',
'cymunk_touch'],
callback=self.init_game)
Let's also remove the automatic timed removal of entities we implemented in the last tutorial.
def draw_some_stuff(self):
size = Window.size
w, h = size[0], size[1]
delete_time = 2.5
create_asteroid = self.create_asteroid
destroy_ent = self.destroy_created_entity
for x in range(100):
pos = (randint(0, w), randint(0, h))
ent_id = create_asteroid(pos)
Clock.schedule_once(partial(destroy_ent, ent_id), delete_time)
self.app.count += 100
to
def draw_some_stuff(self):
size = Window.size
w, h = size[0], size[1]
delete_time = 2.5
create_asteroid = self.create_asteroid
destroy_ent = self.destroy_created_entity
for x in range(100):
pos = (randint(0, w), randint(0, h))
ent_id = create_asteroid(pos)
self.app.count += 100
You should now be able to run your example and interact with the asteroids you create.
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'],
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):
size = Window.size
w, h = size[0], size[1]
delete_time = 2.5
create_asteroid = self.create_asteroid
destroy_ent = self.destroy_created_entity
for x in range(100):
pos = (randint(0, w), randint(0, 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'
CymunkPhysics:
gameworld: root.gameworld
zones: ['general']
CymunkTouchSystem:
gameworld: root.gameworld
zones: ['touch']
zone_to_use: 'touch'
physics_system: 'cymunk_physics'
touch_radius: 30
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:'