#! /usr/bin/python
# -*- indent-tabs-mode: t -*-
# -*- coding: utf-8 -*-
# Souvarine souvarine@aliasrobotique.org
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# This is the same than the game_skel-5.py tutorial except that is use a
# BSP Level instead of a terrain.
#
# Choose first person or third person view
THIRD_PERSON = False
# Choose to scale the level or not
SCALE = True
from struct import *
from math import *
import sys, os, os.path
import soya
import soya.widget as widget
import soya.sdlconst as sdlconst
class Action:
def __init__(self, action):
self.action = action
ACTION_WAIT = 0
ACTION_ADVANCE = 1
ACTION_ADVANCE_LEFT = 2
ACTION_ADVANCE_RIGHT = 3
ACTION_TURN_LEFT = 4
ACTION_TURN_RIGHT = 5
ACTION_GO_BACK = 6
ACTION_GO_BACK_LEFT = 7
ACTION_GO_BACK_RIGHT = 8
ACTION_JUMP = 9
class KeyboardController:
def __init__(self):
self.left_key_down = self.right_key_down = self.up_key_down = self.down_key_down = 0
def next(self):
jump = 0
for event in soya.MAIN_LOOP.events:
if event[0] == sdlconst.KEYDOWN:
if (event[1] == sdlconst.K_q) or (event[1] == sdlconst.K_ESCAPE):
sys.exit() # Quit the game
elif event[1] == sdlconst.K_LSHIFT:
jump = 1
elif event[1] == sdlconst.K_LEFT: self.left_key_down = 1
elif event[1] == sdlconst.K_RIGHT: self.right_key_down = 1
elif event[1] == sdlconst.K_UP: self.up_key_down = 1
elif event[1] == sdlconst.K_DOWN: self.down_key_down = 1
elif event[0] == sdlconst.KEYUP:
if event[1] == sdlconst.K_LEFT: self.left_key_down = 0
elif event[1] == sdlconst.K_RIGHT: self.right_key_down = 0
elif event[1] == sdlconst.K_UP: self.up_key_down = 0
elif event[1] == sdlconst.K_DOWN: self.down_key_down = 0
if jump: return Action(ACTION_JUMP)
return Action({
(0, 0, 1, 0) : ACTION_ADVANCE,
(1, 0, 1, 0) : ACTION_ADVANCE_LEFT,
(0, 1, 1, 0) : ACTION_ADVANCE_RIGHT,
(1, 0, 0, 0) : ACTION_TURN_LEFT,
(0, 1, 0, 0) : ACTION_TURN_RIGHT,
(0, 0, 0, 1) : ACTION_GO_BACK,
(1, 0, 0, 1) : ACTION_GO_BACK_LEFT,
(0, 1, 0, 1) : ACTION_GO_BACK_RIGHT,
}.get((self.left_key_down, self.right_key_down, self.up_key_down, self.down_key_down), ACTION_WAIT))
class ThirdPersonCharacter(soya.World):
def __init__(self, parent, controller):
soya.World.__init__(self, parent)
balazar = soya.AnimatedModel.get("balazar")
self.perso = soya.Body(self, balazar)
self.perso.animate_blend_cycle("attente")
self.current_animation = "attente"
self.solid = 0
self.controller = controller
self.speed = soya.Vector(self)
self.rotation_speed = 0.0
self.radius = 0.5
self.radius_y = 1.0
self.center = soya.Point(self, 0.0, self.radius_y, 0.0)
self.left = soya.Vector(self, -1.0, 0.0, 0.0)
self.right = soya.Vector(self, 1.0, 0.0, 0.0)
self.down = soya.Vector(self, 0.0, -1.0, 0.0)
self.up = soya.Vector(self, 0.0, 1.0, 0.0)
self.front = soya.Vector(self, 0.0, 0.0, -1.0)
self.back = soya.Vector(self, 0.0, 0.0, 1.0)
self.jumping = 0
self.camera = soya.TravelingCamera(parent)
traveling = soya.ThirdPersonTraveling(self)
traveling.distance = 5.
self.camera.add_traveling(traveling)
self.camera.zap()
def play_animation(self, animation):
if self.current_animation != animation:
self.perso.animate_clear_cycle(self.current_animation, 0.2)
self.perso.animate_blend_cycle(animation, 1.0, 0.2)
self.current_animation = animation
def begin_round(self):
self.begin_action(self.controller.next())
soya.World.begin_round(self)
def begin_action(self, action):
# Reset
self.speed.x = self.speed.z = self.rotation_speed = 0.0
# If the character is jumping, we don't want to reset speed.y to 0.0 !!!
if (not self.jumping) and self.speed.y > 0.0: self.speed.y = 0.0
animation = "attente"
# Determine the character rotation
if action.action in (ACTION_TURN_LEFT, ACTION_ADVANCE_LEFT, ACTION_GO_BACK_LEFT):
self.rotation_speed = 4.0
animation = "tourneG"
elif action.action in (ACTION_TURN_RIGHT, ACTION_ADVANCE_RIGHT, ACTION_GO_BACK_RIGHT):
self.rotation_speed = -4.0
animation = "tourneD"
# Determine the character speed
if action.action in (ACTION_ADVANCE, ACTION_ADVANCE_LEFT, ACTION_ADVANCE_RIGHT):
self.speed.z = -0.25
animation = "marche"
elif action.action in (ACTION_GO_BACK, ACTION_GO_BACK_LEFT, ACTION_GO_BACK_RIGHT):
self.speed.z = 0.06
animation = "recule"
new_center = self.center + self.speed
context = scene.RaypickContext(new_center, max(self.radius, 0.1 + self.radius_y))
r = context.raypick(new_center, self.down, 0.1 + self.radius_y, 1, 1)
if r and not self.jumping:
ground, ground_normal = r
ground.convert_to(self)
self.speed.y = ground.y
if action.action == ACTION_JUMP:
self.jumping = 1
self.speed.y = 0.5
else:
self.speed.y = max(self.speed.y - 0.02, -0.25)
animation = "chute"
if self.speed.y < 0.0:
self.jumping = 0
new_center = self.center + self.speed
for vec in (self.left, self.right, self.front, self.back, self.up):
r = context.raypick(new_center, vec, self.radius, 1, 1)
if r:
collision, wall_normal = r
# Necessary because of scaling...
wall_normal %= scene
wall_normal.normalize()
hypo = vec.length() * self.radius - (new_center >> collision).length()
correction = (wall_normal * hypo)
self.speed.add_vector(correction)
new_center.add_vector(correction)
self.play_animation(animation)
def advance_time(self, proportion):
soya.World.advance_time(self, proportion)
self.add_mul_vector(proportion, self.speed)
self.rotate_y(proportion * self.rotation_speed)
class FirstPersonCharacter(soya.World):
def __init__(self, parent, controller):
soya.World.__init__(self, parent)
self.solid = 0
self.controller = controller
self.speed = soya.Vector(self)
self.rotation_speed = 0.0
self.radius = 0.5
self.radius_y = 1.0
self.center = soya.Point(self, 0.0, self.radius_y, 0.0)
self.left = soya.Vector(self, -1.0, 0.0, 0.0)
self.right = soya.Vector(self, 1.0, 0.0, 0.0)
self.down = soya.Vector(self, 0.0, -1.0, 0.0)
self.up = soya.Vector(self, 0.0, 1.0, 0.0)
self.front = soya.Vector(self, 0.0, 0.0, -1.0)
self.back = soya.Vector(self, 0.0, 0.0, 1.0)
self.jumping = 0
self.camera = soya.Camera(self)
self.camera.set_xyz(0.0, self.radius_y*2., 0.0)
def begin_round(self):
self.begin_action(self.controller.next())
soya.World.begin_round(self)
def begin_action(self, action):
# Reset
self.speed.x = self.speed.z = self.rotation_speed = 0.0
# If the character is jumping, we don't want to reset speed.y to 0.0 !!!
if (not self.jumping) and self.speed.y > 0.0: self.speed.y = 0.0
# Determine the character rotation
if action.action in (ACTION_TURN_LEFT, ACTION_ADVANCE_LEFT, ACTION_GO_BACK_LEFT):
self.rotation_speed = 4.0
elif action.action in (ACTION_TURN_RIGHT, ACTION_ADVANCE_RIGHT, ACTION_GO_BACK_RIGHT):
self.rotation_speed = -4.0
# Determine the character speed
if action.action in (ACTION_ADVANCE, ACTION_ADVANCE_LEFT, ACTION_ADVANCE_RIGHT):
self.speed.z = -0.25
elif action.action in (ACTION_GO_BACK, ACTION_GO_BACK_LEFT, ACTION_GO_BACK_RIGHT):
self.speed.z = 0.06
new_center = self.center + self.speed
context = scene.RaypickContext(new_center, max(self.radius, 0.1 + self.radius_y))
r = context.raypick(new_center, self.down, 0.1 + self.radius_y, 1, 1)
if r and not self.jumping:
ground, ground_normal = r
ground.convert_to(self)
self.speed.y = ground.y
if action.action == ACTION_JUMP:
self.jumping = 1
self.speed.y = 0.5
else:
self.speed.y = max(self.speed.y - 0.02, -0.25)
if self.speed.y < 0.0:
self.jumping = 0
new_center = self.center + self.speed
for vec in (self.left, self.right, self.front, self.back, self.up):
r = context.raypick(new_center, vec, self.radius, 1, 1)
if r:
collision, wall_normal = r
# Necessary because of scaling...
wall_normal %= scene
wall_normal.normalize()
hypo = vec.length() * self.radius - (new_center >> collision).length()
correction = (wall_normal * hypo)
self.speed.add_vector(correction)
new_center.add_vector(correction)
def advance_time(self, proportion):
soya.World.advance_time(self, proportion)
self.add_mul_vector(proportion, self.speed)
self.rotate_y(proportion * self.rotation_speed)
soya.init()
soya.path.append(os.path.join(os.path.dirname(sys.argv[0]), "../tutorial/data"))
level = soya.BSPWorld.get("bsp_demo")
# We need to scale the level because it's really too big compared to Balazar.
if SCALE:
level.scale(0.05, 0.05, 0.05)
# Try to comment this line and go to the small square room to see what it does
level.enable_area_visibility(0, 1)
scene = soya.World()
atmosphere = soya.SkyAtmosphere()
atmosphere.ambient = (0.9, 0.9, 0.9, 1.0)
scene.atmosphere = atmosphere
scene.add(level)
if THIRD_PERSON:
print "Third person mode !"
character = ThirdPersonCharacter(scene, KeyboardController())
else:
print "First person mode !"
character = FirstPersonCharacter(scene, KeyboardController())
print "Use arrow keys to move and shift to jump."
if SCALE:
character.set_xyz(-128.0/20., 40.0/20., 8.0/20.)
else:
character.set_xyz(-128.0, 40.0, 8.0)
if SCALE:
character.camera.back = 80.0
else:
character.camera.back = 1500.0
#scene.add(character.camera)
# Creates a widget group, containing the camera and a label showing the FPS.
soya.set_root_widget(widget.Group())
soya.root_widget.add(character.camera)
soya.root_widget.add(widget.FPSLabel())
soya.MainLoop(scene).main_loop()
Tutorials : basic-1 - basic-2 - basic-3 - basic-4 - basic-5 - basic-6 - basic-loadingfile-1 - basic-savingfile-cerealizer-1 - basic-savingfile-pickle-1 - blender-auto-exporter-1 - bsp-game_skel - character-animation-1 - character-animation-2 - character-animation-shadow-cellshading-1 - deform-1 - facecutter-1 - fullscreen-1 - game_skel-1 - game_skel-2 - game_skel-3 - game_skel-4 - game_skel-5 - gui-1 - gui-2 - gui-background-1 - label3D-1 - modeling-1 - modeling-2 - modeling-3 - modeling-cellshading-1 - modeling-env-mapping-1 - modeling-material-1 - modeling-material-2 - modeling-shadow-1 - modeling-smoothlit-1 - modeling-solid-model-1 - modeling-transparency-1 - mouse-1 - multiple-cameras-1 - nested-world-1 - ode-collision-1-base - ode-collision-2-base - ode-collision-3-mass_influence - ode-collision-4-pushable - ode-collision-5-hit_func - ode-collision-6-hit_func-2-other - ode-collision-7-hit_func-3-contacts - ode-collision-8-terrain - ode-collision-9-box - ode-gravity - ode-join - ode-mass - ode-test - particle-1 - portal-1 - pudding-1 - pudding-2 - pudding-buttonbar-1 - pudding-buttonbar-2 - pudding-console-1 - pudding-console-2 - pudding-game_skel-5 - pudding-image-1 - pudding-image-slicing-1 - pudding-input-1 - pudding-listbox-1 - pudding-menu-1 - pudding-meter-1 - pudding-svgelements-1 - pudding-z-index-1 - ray-1 - raypicking-1 - raypicking-2 - raypicking-3 - shader-1 - shader-2 - shader-3 - shader-4 - sound-1 - soya-with-tk-1 - speed-1 - state-1 - static-shadows-1 - terrain-1 - terrain-2 - test - traveling-camera-1 - widget-1 - widget-background-1