-
Notifications
You must be signed in to change notification settings - Fork 94
Getting Started 3: Adding the Physics System
In this tutorial we will add physics objects to our example application from Getting Started: 1.
The content of this tutorial covers the directories:
- examples/4_adding_physics_objects
Prerequisites: You will need to have compiled kivent_core, kivent_cymunk, and cymunk, in addition to having kivy and all its requirements installed.
Currently we have a renderer that is only good for position data. Our physics objects will rotate so we need to use a renderer with a different vertex shader, and an appropriate shader. We will add 3 GameSystems: RotateSystem2D, CymunkPhysics, and RotateRenderer (replacing the old renderer). Note that the new renderer uses the 'positionrotateshader.glsl' instead of 'positionshader.glsl' we used in the last few examples.
<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
Let's modify our create entity function to look like this:
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': 20,
'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)
A physics component is fairly complicated, and you'd be best of reading Chipmunk2D's documentation to understand every parameter here, however the basics are that cymunk support several types of shapes: 'circle', 'box', 'poly', and 'segment' that correspond to various types of geometric objects Chipmunk2D can calculate the physics between. Each shape has different configuration options: you can find the expected values here.
In addition, every shape shares many other physics related values such as mass, position, velocity, angle, and so on. We add all these arguments for initializing our physics object and we add a rotate component to receive the physics information.
Let's set up a button to draw our entities instead of drawing them in the init_game method. Remove the draw_some_stuff function from our init_game function:
def init_game(self):
self.setup_states()
self.set_state()
and instead in our KV UI, let's add:
<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}
We'll also add a count NumericProperty to our App class and a label to display the count:
class YourAppNameApp(App):
count = NumericProperty(0)
The new draw_some_stuff function looks like:
def draw_some_stuff(self):
size = Window.size
w, h = size[0], size[1]
delete_time = 2.5
create_asteroid = self.create_asteroid
for x in range(100):
pos = (randint(0, w), randint(0, h))
ent_id = create_asteroid(pos)
self.app.count += 100
Finally, we will remove the entities a certain amount of time after we add them using a kivy Clock.schedule_once.
Add a callback function to clockschedule:
def destroy_created_entity(self, ent_id, dt):
self.gameworld.remove_entity(ent_id)
self.app.count -= 1
Modify the draw_some_stuff function to call this function using the entity_id returned by our create_asteroid function:
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
We use partial to create a function that has the entity_id as an arg and will receive the appropriate dt term from the Kivy Clock.
Make sure to update the state for your application to use the new GameSystem.
from kivy.app import App
print('imported kivy')
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 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',],
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)
Clock.schedule_once(partial(destroy_ent, ent_id), delete_time)
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': 20,
'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()
#:kivy 1.9.0
TestGame:
<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
<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 4: Interacting with the Physics System