Source code for visualiser.vehicles
'''Vehicle classes used by the game'''
import sys, random
from os import listdir
from os.path import join, dirname
from config import SimulatorConfig, GUIConfig
from .util import SPRITE_DIR, OBSTACLE_SPRITE_PREFIX, loadSprite, Pos, Vector
guiConf = GUIConfig()
conf = SimulatorConfig()
OBSTACLE_SPRITES = listdir(join(SPRITE_DIR, OBSTACLE_SPRITE_PREFIX))
#: :class:`dict` mapping obstacle sprites onto their respective :class:`float`
#: scales
SPRITE_SCALES = {
'car.png': .4,
'lorry.png': .75,
'police.png': .42,
'taxi.png': .35,
'truck.png': .5,
'van.png': .55,
'viper.png': .37,
}
[docs]class Vehicle:
'''A vehicle in the game
:param game: The parent
:class:`~visualiser.visualiser.SimulatorVisualiser`
'''
def __init__(self, game):
# real 2D velocity vector, applied each tick
self.velocity = Vector()
#: A reference to the vehicle's :class:`~pygame.Rect`
self.rect = None
self._game = game
self._lane = 0
#: (:class:`float`) Size multiplier of the sprite
self._spriteScale = 1
#: :class:`str` name of the sprite file, relative to the
#: sprites/obstacles directory
self._sprite = 'obstacle.png'
self._image = None
self._pos = Pos(self) # real 2D position Vector
self._speed = 0 # virtual speed (m/s)
@property
def pos(self):
'''The position vector of the vehicle
:getter: Get position
:setter: Set the position
:type: :class:`~visualiser.util.Pos`'''
return self._pos
@pos.setter
def pos(self, vec):
self.rect.x = vec.x
self.rect.y = vec.y
self._pos = vec
@property
def speed(self):
'''The virtual forward speed of the vehicle, in metres per second
:getter: Get the vehicle speed
:setter: Set the vehicle speed in m/s
'''
return self._speed
@speed.setter
def speed(self, val):
self._speed = val
@property
def sprite(self):
'''The sprite object. Uses :py:attr:`~_sprite` as the file name of the
image to load
:getter: Return the sprite
:type: :class:`pygame.Surface`
'''
if not self._image is None:
return self._image
self._image = loadSprite(self._sprite, self._spriteScale)
return self._image
@property
def lane(self):
'''The current lane of the vehicle
:getter: Get the current lane
:setter: Set the lane of the vehicle. The vehicle is centered in its
lane. Will only accept lanes available
:type: :class:`int`
'''
return self._lane
@lane.setter
def lane(self, val):
off = conf.OffroadAllowed
if val not in range(0 if off else 1, 5 if off else 4):
return
self._lane = int(val)
# start with just offroad/2, subtract width/2
self.pos.x = -self.rect.size[0] /2
if val < 1:
self.pos.x += conf.OffroadWidth /2
return
# add offroad width, width of each lane to left, plus half
self.pos.x += conf.OffroadWidth +max(0, val -1) *conf.LaneWidth \
+(conf.LaneWidth if val < 4 else conf.OffroadWidth) /2
[docs] def colliding(self, veh):
'''Return whether this vehicle is colliding with Vehicle veh'''
return self.rect.colliderect(veh.rect)
[docs] def draw(self, canvas):
'''Draw the vehicle'''
canvas.blit(self.sprite, self.rect)
[docs] def tick(self, dt):
'''Called regularly to perform update logic.
Updates vehicle position using its velocity'''
self.pos += self.velocity *dt
[docs]class Obstacle(Vehicle):
'''An obstacle vehicle that the main car has to avoid
:param game: The parent :class:`~visualiser.visualiser.SimulatorVisualiser`
'''
def __init__(self, game):
super().__init__(game)
sprite = random.choice(OBSTACLE_SPRITES)
self._sprite = OBSTACLE_SPRITE_PREFIX + sprite
self._spriteScale = SPRITE_SCALES[sprite]
self.rect = self.sprite.get_rect()
#: :class:`bool` indicating whether this vehicle has collided with the
#: user car
self._collided = False
#: :class:`bool` indicating whether this vehicle has collided with the
#: agent car
self._agentCollided = False
self.lane = 0
self.speed = conf.ObstacleSpeed
self.pos.y = -self.rect.size[1] +1 # start just above screen
@property
def hasCollided(self):
'''
:getter: Return whether the vehicle has just collided this tick.
Updates :py:attr:`_collided`
'''
collision = self._game.car.rect.colliderect(self.rect)
ret = collision and not self._collided
self._collided = collision or self._collided
return ret
@property
def hasCollidedAgent(self):
'''
:getter: Returns whether the vehicle has started colliding with agent
car this tick. Updates :py:attr:`_agentCollided`
'''
collision = self._game.agentCar.rect.colliderect(self.rect)
ret = collision and not self._agentCollided
self._agentCollided = collision or self._agentCollided
return ret
@property
def speed(self):
'''The virtual forward speed of the vehicle, in metres per second
:getter: Get the vehicle speed
:setter: Set the vehicle speed in m/s. Also sets the real velocity,
which is relative to the user car'''
return self._speed
@speed.setter
def speed(self, val):
self._speed = val
# set velocity relative to driver
self.velocity.y = -(val -conf.CarSpeed) \
*conf.PixelMetreRatio
[docs] def tick(self, dt):
'''Called regularly to perform update logic.
Return False to indicate vehicle is entirely off screen and should be
removed'''
super().tick(dt)
return self._game.canvas.get_rect().colliderect(self.rect)
[docs]class Car(Vehicle):
'''The controlled car
:param game: The parent :class:`~visualiser.visualiser.SimulatorVisualiser`
'''
def __init__(self, game):
super().__init__(game)
self._spriteScale = conf.CarScale
self._sprite = 'car.png'
self.rect = self.sprite.get_rect()
self.pos.y = guiConf.Height -self.rect.size[1] -25
self.speed = conf.CarSpeed
self.lane = 2
[docs] def tick(self, dt):
'''Car tick method, currently does nothing'''
pass