#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# player.py
#
# Copyright 2016 notna <notna@apparat.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., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
#
#
__all__ = ["BasicPlayer","FirstPersonPlayer","FourDirectionalMoveController","EgoMouseRotationalController","BasicFlightController"]
import math
try:
import pyglet
except ImportError:
pass
from . import Actor,RotatableActor,Controller
[docs]class FourDirectionalMoveController(Controller):
"""
Controller allowing the user to control the actor with the keyboard.
You can configure the used keybinds with the :confval:`controls.controls.forward` etc.
The keybinds can also be changed with their ``keybindname``\ , e.g. ``peng3d:actor.<actor uuid>.player.controls.forward`` for forward.
The movement speed may also be changed via the :py:attr:`movespeed` instance attribute, which defaults to :confval:`controls.controls.movespeed`\ .
You may also access the currently held keys via :py:attr:`move`\ , which is a list with 2 items, forwards/backwards and left/right.
"""
def __init__(self,*args,**kwargs):
super(FourDirectionalMoveController,self).__init__(*args,**kwargs)
self.move = [0,0]
self.movespeed = self.peng.cfg["controls.controls.movespeed"]
[docs] def registerEventHandlers(self):
"""
Registers needed keybinds and schedules the :py:meth:`update` Method.
You can control what keybinds are used via the :confval:`controls.controls.forward` etc. Configuration Values.
"""
# Forward
self.peng.keybinds.add(self.peng.cfg["controls.controls.forward"],"peng3d:actor.%s.player.controls.forward"%self.actor.uuid,self.on_fwd_down,False)
# Backward
self.peng.keybinds.add(self.peng.cfg["controls.controls.backward"],"peng3d:actor.%s.player.controls.backward"%self.actor.uuid,self.on_bwd_down,False)
# Strafe Left
self.peng.keybinds.add(self.peng.cfg["controls.controls.strafeleft"],"peng3d:actor.%s.player.controls.strafeleft"%self.actor.uuid,self.on_left_down,False)
# Strafe Right
self.peng.keybinds.add(self.peng.cfg["controls.controls.straferight"],"peng3d:actor.%s.player.controls.straferight"%self.actor.uuid,self.on_right_down,False)
pyglet.clock.schedule_interval(self.update,1.0/60)
[docs] def update(self,dt):
"""
Should be called regularly to move the actor.
This method does nothing if the :py:attr:`enabled` property is set to false.
Note that this method is called automatically and should not be manually called.
"""
if not self.enabled:
return
speed = self.movespeed
d = dt * speed # distance covered this tick.
dx, dy, dz = self.get_motion_vector()
# New position in space, before accounting for gravity.
dx, dy, dz = dx * d, dy * d, dz * d
#dy+=self.vert*VERT_SPEED
x,y,z = self.actor._pos
newpos = dx+x, dy+y, dz+z
self.actor.pos = newpos
[docs] def get_motion_vector(self):
"""
Returns the movement vector according to held buttons and the rotation.
:return: 3-Tuple of ``(dx,dy,dz)``
:rtype: tuple
"""
if any(self.move):
x, y = self.actor._rot
strafe = math.degrees(math.atan2(*self.move))
y_angle = math.radians(y)
x_angle = math.radians(x + strafe)
dy = 0.0
dx = math.cos(x_angle)
dz = math.sin(x_angle)
else:
dy = 0.0
dx = 0.0
dz = 0.0
return (dx, dy, dz)
# Event Handlers
def on_fwd_down(self,symbol,modifiers,release):
self.move[0]-=1 if not release else -1
def on_bwd_down(self,symbol,modifiers,release):
self.move[0]+=1 if not release else -1
def on_left_down(self,symbol,modifiers,release):
self.move[1]-=1 if not release else -1
def on_right_down(self,symbol,modifiers,release):
self.move[1]+=1 if not release else -1
[docs]class EgoMouseRotationalController(Controller):
"""
Controller allowing the user to rotate the actor with the mouse.
"""
def __init__(self,*args,**kwargs):
super(EgoMouseRotationalController,self).__init__(*args,**kwargs)
[docs] def registerEventHandlers(self):
"""
Registers the motion and drag handlers.
Note that because of the way pyglet treats mouse dragging, there is also an handler registered to the on_mouse_drag event.
"""
self.world.registerEventHandler("on_mouse_motion",self.on_mouse_motion)
self.world.registerEventHandler("on_mouse_drag",self.on_mouse_drag)
def on_mouse_motion(self, x, y, dx, dy):
if self.enabled:
m = self.peng.cfg["controls.mouse.sensitivity"]
x, y = self.actor._rot
x, y = x + dx * m, y + dy * m
y = max(-90, min(90, y))
x %= 360
newrot = (x,y)
self.actor.rot = newrot
def on_mouse_drag(self,x,y,dx,dy,buttons,modifiers):
self.on_mouse_motion(x,y,dx,dy)
[docs]class BasicFlightController(Controller):
"""
Controller allowing the user to move up and down with the jump and crouch controls.
The used keybinds may be configured via :confval:`controls.controls.crouch` and :confval:`controls.controls.jump`\ .
The vertical speed used when flying may be configured via :confval:`controls.controls.verticalspeed` or the :py:attr:`speed` attribute.
"""
def __init__(self,*args,**kwargs):
super(BasicFlightController,self).__init__(*args,**kwargs)
self.speed = self.peng.cfg["controls.controls.verticalspeed"]
self.move = 0
[docs] def registerEventHandlers(self):
"""
Registers the up and down handlers.
Also registers a scheduled function every 60th of a second, causing pyglet to redraw your window with 60fps.
"""
# Crouch/fly down
self.peng.keybinds.add(self.peng.cfg["controls.controls.crouch"],"peng3d:actor.%s.player.controls.crouch"%self.actor.uuid,self.on_crouch_down,False)
# Jump/fly up
self.peng.keybinds.add(self.peng.cfg["controls.controls.jump"],"peng3d:actor.%s.player.controls.jump"%self.actor.uuid,self.on_jump_down,False)
pyglet.clock.schedule_interval(self.update,1.0/60)
[docs] def update(self,dt):
"""
Should be called regularly to move the actor.
This method does nothing if the :py:attr:`enabled` property is set to False.
This method is called automatically and should not be called manually.
"""
if not self.enabled:
return
dy = self.speed * dt * self.move
x,y,z = self.actor._pos
newpos = x,dy+y,z
self.actor.pos = newpos
def on_crouch_down(self,symbol,modifiers,release):
self.move -= 1 if not release else -1
def on_jump_down(self,symbol,modifiers,release):
self.move += 1 if not release else -1
[docs]class BasicPlayer(RotatableActor):
"""
Basic Player class, subclass of :py:class:`RotatableActor()`\ .
This class adds no features currently, it can be used to identify player actors via :py:func:`isinstance()`\ .
"""
pass
[docs]class FirstPersonPlayer(BasicPlayer):
"""
Old class allowing to create standard first-person players easily.
:deprecated: See :py:class:`EgoMouseRotationalController()` and :py:class:`FourDirectionalMoveController()` instead
"""
def __init__(self,peng,world,uuid=None,pos=[0,0,0],rot=[0,0]):
super(FirstPersonPlayer,self).__init__(peng,world,uuid,pos,rot)
self.move = [0,0]
self.movespeed = self.peng.cfg["controls.controls.movespeed"]
self.active = True
# Event handler registration
# Forward
self.peng.keybinds.add(self.peng.cfg["controls.controls.forward"],"peng3d:actor.player.controls.forward",self.on_fwd_down)
self.peng.keybinds.add("release-"+self.peng.cfg["controls.controls.forward"],"peng3d:actor.player.controls.forward.release",self.on_fwd_up)
# Backward
self.peng.keybinds.add(self.peng.cfg["controls.controls.backward"],"peng3d:actor.player.controls.backward",self.on_bwd_down)
self.peng.keybinds.add("release-"+self.peng.cfg["controls.controls.backward"],"peng3d:actor.player.controls.backward.release",self.on_bwd_up)
# Strafe Left
self.peng.keybinds.add(self.peng.cfg["controls.controls.strafeleft"],"peng3d:actor.player.controls.strafeleft",self.on_left_down)
self.peng.keybinds.add("release-"+self.peng.cfg["controls.controls.strafeleft"],"peng3d:actor.player.controls.strafeleft.release",self.on_left_up)
# Strafe Right
self.peng.keybinds.add(self.peng.cfg["controls.controls.straferight"],"peng3d:actor.player.controls.straferight",self.on_right_down)
self.peng.keybinds.add("release-"+self.peng.cfg["controls.controls.straferight"],"peng3d:actor.player.controls.straferight.release",self.on_right_up)
# Mouse
self.world.registerEventHandler("on_mouse_motion",self.on_mouse_motion)
self.world.registerEventHandler("on_mouse_drag",self.on_mouse_drag)
pyglet.clock.schedule_interval(self.update,1.0/60)
[docs] def update(self,dt):
"""
Internal method used for moving the player.
:param float dt: Time delta since the last call to this method
"""
speed = self.movespeed
d = dt * speed # distance covered this tick.
dx, dy, dz = self.get_motion_vector()
# New position in space, before accounting for gravity.
dx, dy, dz = dx * d, dy * d, dz * d
#dy+=self.vert*VERT_SPEED
x,y,z = self._pos
newpos = dx+x, dy+y, dz+z
self.pos = newpos
[docs] def get_motion_vector(self):
"""
Returns the movement vector according to held buttons and the rotation.
:return: 3-Tuple of ``(dx,dy,dz)``
:rtype: tuple
"""
if any(self.move):
x, y = self._rot
strafe = math.degrees(math.atan2(*self.move))
y_angle = math.radians(y)
x_angle = math.radians(x + strafe)
dy = 0.0
dx = math.cos(x_angle)
dz = math.sin(x_angle)
else:
dy = 0.0
dx = 0.0
dz = 0.0
return (dx, dy, dz)
# Event Handlers
def on_fwd_down(self,symbol,modifiers):
self.move[0]-=1
def on_fwd_up(self,symbol,modifiers):
self.move[0]+=1
def on_bwd_down(self,symbol,modifiers):
self.move[0]+=1
def on_bwd_up(self,symbol,modifiers):
self.move[0]-=1
def on_left_down(self,symbol,modifiers):
self.move[1]-=1
def on_left_up(self,symbol,modifiers):
self.move[1]+=1
def on_right_down(self,symbol,modifiers):
self.move[1]+=1
def on_right_up(self,symbol,modifiers):
self.move[1]-=1
def on_mouse_motion(self, x, y, dx, dy):
if self.active:
m = 0.15
x, y = self._rot
x, y = x + dx * m, y + dy * m
y = max(-90, min(90, y))
x %= 360
newrot = (x,y)
self.rot = newrot
def on_mouse_drag(self,x,y,dx,dy,buttons,modifiers):
self.on_mouse_motion(x,y,dx,dy)