import sys
import os
sys.path.append(os.path.dirname(os.path.realpath(__file__))+"/../CommonFunctions")
from setCalico import *
from utilityFunctions import *
from baseActor import BaseActor
from makeShapeFunctions import makeColRec, makePic, makeDebugPicture, makeDebugColor
[docs]class SpriteActor(BaseActor):
"""
Actor class for Sprite on collision shape objects.
Since I was unable to get the calico Sprite class to work properly,
this is my own, very simple, implementation of a Sprite like class.
"""
def __init__(self,
position=(0,0),
base=None,
shape=None,
files=None,
obstructs=False,
bodyType=None,
collision=None,
separation=None,
visible=None,
scale=1.0,
debug=False,
rotation=0,
**kwargs):
"""
Constructs a SpriteActor.
Args:
position: Tuple (x, y) indicating the starting position of the shape.
base: Main argument. Can send pictures, list of pictures, or a shape.
shape: Used if you want a separate collision object for this actor.
files: A list of file names, each pointing to an image. Each image will
form one 'costume' for this 'Sprite'. Not really needed. Can be supplied
to base.
obstructs: Boolean indicating whether this object obstructs the movement
of other objects. If 'obstructs' is set to True with a 'shape' that
does not have a body, a warning will be printed and the object will
not obstruct anything.
bodyType: String indicating the type of body that should be added to the
physics simulator. Possible values are: 'static' and 'dynamic'.
collision: Function of the form func(myfixture, otherfixture, contact),
called when the 'shape' collides with anything.
separation: Function of the form func(myfixture, otherfixture),
called when the 'shape' separates from anything.
visible: Boolean indicating whether this Sprite Actor should be visible.
scale: A float, indicating by what factor images should be scaled.
debug: Boolean indicating whether to paint this object in debug mode.
rotation: Float indicating by how many radians the sprite actor should
be rotated.
Keyword Args:
interact: Function of the form func(self, items), called when
this object is interacted with.
use: Function of the form func(self) called when this object
is used.
pickup: Anything, generally will be the object that is returned to the
player upon pickup. If set to True, will return the Actor object
itself instead. If set to True, and a value for returned is povided,
will return whatever value returned was set to.
(deprecated) If pickup is the string "self", a reference to
this instance will be returned instead, just as if pickup was set
to True.
returned: Anything, will be the object that is returned to the player
if pickup is set to True.
onPickup: Function of the form func(self) called when this object
is picked up by the player. Note: passing a value for onPickup
is NOT enough to enable the player to pickup this object.
description: A description of this object, used when this object is printed.
If left blank, the class and memory address of this object is printed.
tag: The tag of the actor, usually a string helpful for identifying what
kind of actor you are dealing with.
"""
#: The main shape of the sprite actor
self.shape = None
#: Boolean indicating whether or not this Sprite Actor should obstruct movement
#: Change it before adding the Actor to the world.
self.obstructs = None
#: The collision function that will be called when this Actor collides with
#: anything in the world. Change it before adding the Actor to the world.
self.collision = None
#: The separation function that will be called when this Actor separates from
#: anything in the world. Change it before adding the Actor to the world.
self.separation = None
#: The radius from where this actor will respond to the talk() command.
#: Not used by default.
self.talkRadius = None
#: The list of customes available for this Actor.
self.costumes = None
#: The current costume of this Actor.
self.currentCostume = None
#: The index of the current costume of this Actor.
self.currentCostumeIndex = None
#: The list of customes used for the current animation cycle.
#: Note that this is a list of indices, not costumes. Based on these indices,
#: the actual costumes will be retrieved from the costumes list.
self.animationCostumes = None
#: The index of the current animation costume.
self.animationIndex = None
#: Booleans indicating whether the current animation should cycle or not.
self.cycle = None
#: Callback function excuted at the end of a full animation cycle.
self.onEndOfAnimation
# Synonyms for keyword arguments
if "vis" in kwargs:
visible = kwargs["vis"]
del kwargs["vis"]
if "desc" in kwargs:
description = kwargs["desc"]
del kwargs["desc"]
# Initialize the base class
BaseActor.__init__(self, **kwargs)
# If the user did not provide a base, but did provide a shape, the shape should be the base
if (not base) and shape:
base = shape
shape = None
# If the base is an instance of a shape, but not a Picture, use it as the shape for this image
if isinstance(base, Shape) and (not isinstance(base, Picture)):
shape = base
# If the base is a picture, make it the avatar of this sprite
if isinstance(base, Picture):
files = [base]
# If the base is an instance of string, assume it is a filename
if isinstance(base, str):
files = [base]
# If the base is iterable and not a string, assume it is a list of filenames
elif hasattr(base, "__iter__"):
files = base
# Set costumes (if any)
self.costumes = []
if files:
for fileName in files:
costume = makePic(fileName, scale)
costume.visible = False
self.costumes.append(costume)
# If the shape is not defined, create one
if not shape:
if len(self.costumes) > 0:
w=getWidth(self.costumes[0])
h=getHeight(self.costumes[0])
shape = makeColRec(w, h)
else:
shape = makeColRec(50, 50)
# Set class specific attributes
self.shape = shape
self.obstructs = obstructs
self.collision = collision
self.separation = separation
if bodyType is not None: self.shape.bodyType = bodyType
if visible is not None: self.shape.visible = visible
# Move shape into position
self.moveTo(position[0], position[1])
# Rotate shape
self.rotateTo(rotation)
# Max distance away to talk to the actor
self.talkRadius=100
#Set current costume
if len(self.costumes) > 0:
self.currentCostume = self.costumes[0]
else:
self.currentCostume = self.shape
self.currentCostumeIndex=0
self.currentCostume.visible = True
for costume in self.costumes:
costume.draw(self.shape)
self.animationCostumes = [0]
self.animationIndex = 0
self.cycle = True
self.onEndOfAnimation = None
#Enable debug
if debug:
self.debugMode()
[docs] def draw(self, levelWindow):
"""
Adds this object to the level window, and thus the world.
Args:
levelWindow: A LevelWindow object.
"""
#Set level window
self.levelWindow = levelWindow
#Draw shape
if self.shape: self._drawShape(self.shape, self.levelWindow)
#Install collision callbacks
if self.collision: self.installCollision(self.collision, self.shape)
if self.separation:
self.installSeparation(self.separation, self.shape)
if self.canInteract:
self.installCollision(self.levelWindow.interactCollide, self.shape)
self.installSeparation(self.levelWindow.interactSeparate, self.shape)
if self.canPickup:
self.installCollision(self.levelWindow.pickupCollide, self.shape)
self.installSeparation(self.levelWindow.pickupSeparate, self.shape)
[docs] def undraw(self):
"""
Removes the shape and speech bubble of this actor from the world.
Note: be carefull where you remove objects from the world, since
removing objects while they are being used can lead to unstable
behavior. The level thread is the recommended place to do so.
"""
self.shape.undraw()
[docs] def getAvatar(self):
"""
Returns the first costume in the costumes list or, if the costumes
list is empty, returns the shape of this Actor instead.
Return:
A Calico shape object, usually a Figure.
"""
if len(self.costumes) > 0:
return self.costumes[0]
else:
return self.shape
[docs] def getX(self):
"""
Returns the x coordinate of this object.
Return:
A float indicating the x coordinate of this object.
"""
return self.shape.center.x
[docs] def getY(self):
"""
Returns the y coordinate of this object.
Return:
A float indicating the y coordinate of this object.
"""
return self.shape.center.y
[docs] def move(self, x, y):
"""
Moves this object by (x, y).
Args:
x: Float indicating horizontal displacement.
Negative values move left, positive values move right.
y: Float indicating vertical displacement.
Negative values move up, positive values move down.
"""
self.moveTo(self.getX() + x, self.getY() + y)
[docs] def moveTo(self, x, y):
"""
Moves this object to the coordinate (x, y).
Args:
x: Float for the x coordinate.
y: Float for the y coordinate.
"""
self.shape.moveTo(x, y)
[docs] def getRotation(self):
"""
Returns the rotation of this object.
Returns:
rotation of this object.
"""
return self.shape.rotation
[docs] def rotate(self, angle):
"""
Rotates this actor by a certain angle.
Args:
angle: Float indicating the angle to rotate.
"""
self.shape.rotate(angle)
[docs] def rotateTo(self, angle):
"""
Rotates this actor to a certain angle.
Args:
angle: Float indicating the angle to rotate to.
"""
self.shape.rotateTo(angle)
[docs] def hit(self, x, y):
"""
Returns whether the supplied coordinate hits the shape.
Args:
x: A float indicating an x coordinate.
y: A float indicating a y coordinate.
Returns:
True if the coordinate (x, y) falls onto the shape.
"""
return self.shape.hit(x, y)
[docs] def visible(self):
"""
Returns whether this object is visible.
Returns:
True if this object is visible, False otherwise.
"""
return self.shape.visible
[docs] def hide(self):
"""
Hides this object.
"""
self.shape.visible = False
[docs] def show(self):
"""
Shows this object.
"""
self.shape.visible = True
def _drawShape(self, shape, window):
"""
Adds a shape, or, if the shape is a group, all its children to the world.
"""
if isinstance(shape, Group):
for item in shape.items:
self._drawShape(item, window)
else:
shape.draw(window.worldFrame)
shape.addToPhysics()
if shape.body and not self.obstructs:
shape.body.IsSensor = True
elif not shape.body and self.obstructs:
warnings.warn("Obstructs is True, but shape has no body: shape cannot obstruct.")
[docs] def inRange(self, x, y):
"""
Checks whether the provided coordinate is within talkRadius of this Actor.
Args:
x: Float indicating the horizontal aspect of the coordinate.
y: Float indicating the horizontal aspect of the coordinate.
Return:
Boolean indicating whether the provided coordinate was in talkRadius of
this Actor.
"""
print(abs(x - self.shape.x))
print(abs(y - self.shape.y))
if abs(x - self.shape.x) > self.talkRadius: return False
if abs(y - self.shape.y) > self.talkRadius: return False
return True
[docs] def debugMode(self):
"""
Enables or disables debug mode.
In debug mode, many objects that are invisible are now visible and transparant.
Args:
debug: Boolean indicating whether to enable (True) or disable (False) debug mode.
"""
#if self.debug == debug: return
debug = True
self.debug = debug
for custome in self.costumes:
custome = makeDebugPicture(custome, debug)
self.shape.fill = makeDebugColor(255,0,0, 150,debug)
[docs] def speak(self, text = None, action = None, portrait = None):
"""
Makes this object speak.
User initiated 'talk()' callbacks will go directly to the speech bubble,
meaning this function is only for if you want to manually make this
object say something.
Args:
text: A string containing the text to be displayed.
action: A function executed at the end of this function.
"""
if self.levelWindow:
if not portrait:
portrait = Picture(self.currentCostume)
portrait.border = 0
self.levelWindow.printToSpeechBox(text, portrait)
if action: action()
[docs] def changeCostume(self, index):
"""
Makes a different shape visible.
Args:
index: The index of the shape.
"""
self.currentCostumeIndex=index
self.currentCostume.visible = False
self.currentCostume = self.costumes[index]
self.currentCostume.visible = True
[docs] def flipCostumeHoriz(self,index):
"""
Flips the indicated costume horizontal.
Args:
index: The index of the costume to flip.
"""
self.costumes[index].flipHorizontal()
[docs] def flipCostumeVert(self,index):
"""
Flips the indicated costume vertically.
Args:
index: The index of the costume to flip.
"""
self.costumes[index].flipVertical()
[docs] def animate(self):
"""
Performs one frame of the animation of this Sprite Actor.
One frame of animation means that the current costume is changed to the
costume at the index at the animationIndex of the animationCostumes list.
The animationIndex is then incremented by one. If the animationIndex reaches
the end of the animationCostumes list, the animationIndex will either be
reset (if cycle is True), or the onEndOfAnimation callback is called.
Return:
True if the animation is still ongoing, or False if this was the last
frame of the animation and cycle is False.
"""
self.changeCostume(self.animationCostumes[self.animationIndex])
self.animationIndex += 1
if self.animationIndex >= len(self.animationCostumes):
if self.cycle:
self.animationIndex = 0
else:
self.animationIndex -= 1
if self.onEndOfAnimation is not None: self.onEndOfAnimation()
return False
return True