Previous posts for this project here:
I have the following functionality:
- Load up images in a specific structure
- Load up sounds in a specific structure
- Animate eyes, with a realistic yet random blinking method
- Animate the mouth to make it appear to be talking
- The eye can be poked (demonstrated on last post) which results in a specific action.
To Do:
Play sounds
Finish Physical Pumpkin.
Make "scare" technique using PIR sensor.
The eye Poke
The poking Sequence for the left eye, the right eye is the same with variables swapped:
def getPoking(self):
if(self.leftPoking):
if(self.pokeStart == 0):
self.leftEyeBlit = display.blit(self.leftEyeBlink[-1],(self.leftEyeX,self.leftEyeY))
self.lastPoke = time.time();
self.pokeStart = time.time();
else:
if(time.time() - self.pokeStart > 10):
self.leftPoking = 0
self.pokeStart = 0
self.leftEyeBlit = display.blit(self.leftEye,(self.leftEyeX,self.leftEyeY))
In this code, the eye poke is the same image as when the eye is nearly fully closed in a blink. I will change this to blit the squint image that is given in the file structure.
There are a couple variables here that come into play.
pokeStart = the time the poke sequence started, if it has started, else it is 0
lastPoke = the time the last poke occurred
You can see on line 8 above:
if(time.time() - self.pokeStart > 10):
We can only start the poke sequence every 10 seconds. I plan to put this into a configurable variable.
Detecting the Eye Poke
Whenever an images is blitted to the screen, it returns a rectangle object that gives the coordinates of the rectangle the image is on.
In my main game loop I have this code:
for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit() elif event.type == KEYDOWN: if event.key == K_ESCAPE: pygame.quit() sys.exit() elif event.type == pygame.MOUSEBUTTONDOWN: pos = pygame.mouse.get_pos() if(faces[0].eyes.leftEyeBlit.collidepoint(pos)): faces[0].eyes.leftPoking = 1 elif(faces[0].eyes.rightEyeBlit.collidepoint(pos)): faces[0].eyes.rightPoking = 1;
The event MOUTBUTTONDOWN gives us the x,y coordinates of the button press, or in this case a screen touch. Given a rect object, there is a function called collidepoint, which given a x,y point will return true if that coordinate is within the rectangle. If it is, we run the poke sequence for the eye that is poked.
Playing Sounds an the Talking Mouth
Playing sounds in pygame is fairly trivial. I will be using the Music object to play sounds. This is a multi-threaded approach as the sound played in the background of your code, and releases your code pointer after it is started.
The way this will work is I will have several sound bytes, some of which play together in a specific order the same way the images are grouped together. Once a sound file is loaded,it can be played then continuously polled to see if it is done.
The sound will begin playing, and I will tell the mouth to start talking. In the main game loop, the sound will be polled and if it is completed, the mouth will be told to stop talking. The next sound in the sequence will then be played with a small pause between. If the sequence is completed, a new sequence will be picked at random.
There are 2 special sequences. One of which is the eye poke. The eye can only be poked every 10 seconds, and the sound and animation sequence will play. This will override the currently playing sequence.
The other special sequence is the scare sequence. This is chosen at random in between randomly played sequences.
The Talking Mouth
def talk(self):
if(self.talking == 0): #determine when to talk again
self.mouthBlit = display.blit(self.mouth,(self.mouthX,self.mouthY))
if (time.time() - self.lastTalk >= self.talkDelay):
print("talk!" + "(" + str(self.talkDelay) + ")")
self.talking = 1
self.talkIndex = 0
self.talkDirection = 1
lastTalkSwap = time.time()
self.lastTalk = time.time()
else: #animate talking
if(time.time() - self.lastTalkSwap >= 0):
self.talkIndex = self.talkIndex + self.talkDirection
if(self.talkIndex >= len(self.mouthTalk)):
self.talkIndex = len(self.mouthTalk)-1
self.talkDirection = -1
if(self.talkIndex == 0 and self.talkDirection == -1):
## print("mouth reset")
self.talking = 0
self.talkDirection = 1
self.lasttalk = time.time()
## print("blink index:" + str(self.blinkIndex))
self.talkBlit = display.blit(self.mouthTalk[self.talkIndex],(self.mouthX,self.mouthY))
self.lastTalkSwap = time.time()
This is run the same way as the eyes. An array of images is sequenced in order with a small delay between the blitting. The array is played forwards, then backwards as one complete animation sequence. Unlike the eye however, it does not stop there. It continues to play the array back and forth until told to stop otherwise.
The variables in play here are:
talkDelay = The delay between talk blits, this is left small but can be configured. I Emerpically determined a value that looked good for my images.
talkIndex = The current index of the image being blitted. This cycles forwards and backwards through the mouth talking image array.
talkDirection = This is direction the talkIndex is moving. it is 1 when moving forward, and -1 when moving backwards. You can see on line 16 how this is set.
talkSwap - This last time the images were swapped, the time between swaps must be greater than or equal to talkDelay
The complete code thus far:
import os
import pygame,sys, time, random
from pygame.locals import *
#a generic function for loading a set of images from a given directory
def LoadImages(imagesPath):
imageList = list()
for dirname, dirnames, filenames in os.walk(imagesPath):
for filename in sorted(filenames):
try:
imageList.append( pygame.image.load(os.path.join(dirname, filename)))
except:
pass
return imageList
#a generic function for loading a set of sounds from a given directory
def LoadSounds(imagesPath):
soundList = list()
for dirname, dirnames, filenames in os.walk(imagesPath):
for filename in sorted(filenames):
try:
soundList.append( pygame.mixer.Sound(os.path.join(dirname, filename)))
except:
pass
return soundList
#define the face and sub classes, which is just used to keep all the images together that go together
class Mouth:
def __init__(self,path):
self.mouthTalk = LoadImages(os.path.join(path, 'mouth/talk/'))
print("Mouth images:" + str(len(self.mouthTalk)))
self.mouth = pygame.image.load(os.path.join(path, 'mouth/mouth.png'))
self.mouthX = 0
self.marginX = 20
self.marginY = 0;
self.mouthW = 480/2
self.mouthH = 350
self.mouthY = (480-350) / 2
self.lastTalk = time.time()
self.lastTalkSwap = time.time()
self.talkMin = 1
self.talkMax = 6
self.talkDelay =.03
self.talking = 0
self.talkIndex = 0
self.talkDirection = 1
self.leftPoking = 0
self.rightPoking = 0
self.lastPoke = 0
self.pokeStart = 0
self.mouthBlit = pygame.Rect(0,0,1,1)
def talk(self):
if(self.talking == 0): #determine when to talk again
self.mouthBlit = display.blit(self.mouth,(self.mouthX,self.mouthY))
if (time.time() - self.lastTalk >= self.talkDelay):
print("talk!" + "(" + str(self.talkDelay) + ")")
self.talking = 1
self.talkIndex = 0
self.talkDirection = 1
lastTalkSwap = time.time()
self.lastTalk = time.time()
else: #animate talking
if(time.time() - self.lastTalkSwap >= 0):
self.talkIndex = self.talkIndex + self.talkDirection
if(self.talkIndex >= len(self.mouthTalk)):
self.talkIndex = len(self.mouthTalk)-1
self.talkDirection = -1
if(self.talkIndex == 0 and self.talkDirection == -1):
## print("mouth reset")
self.talking = 0
self.talkDirection = 1
self.lasttalk = time.time()
## print("blink index:" + str(self.blinkIndex))
self.talkBlit = display.blit(self.mouthTalk[self.talkIndex],(self.mouthX,self.mouthY))
self.lastTalkSwap = time.time()
class Eyes:
def __init__(self,path):
self.leftEyeSquint = LoadImages(os.path.join(path, 'eye/left/squint/'))
self.leftEye = pygame.image.load(os.path.join(path, 'eye/left/eye.png'))
self.leftEyeBlink = LoadImages(os.path.join(path , 'eye/left/blink/'))
## self.rightEyeSquint = LoadImages(os.path.join(path , 'eye/right/squint/'))
## self.rightEyeBlink = LoadImages(os.path.join(path , 'eye/right/blink/'))
self.rightEyeSquint = list()
self.rightEyeBlink = list()
self.leftEyeX = 800/2-480/4;
self.leftEyeY = 0;
self.marginX = 20
self.marginY = 0;
self.leftEyeW = 480/2
self.leftEyeH = 480 / 4
self.rightEyeX = 800/2-480/4
self.rightEyeY = 480/2
self.rightEyeW = 20
self.rightEyeH = 20
self.lastBlink = time.time()
self.lastBlinkSwap = time.time()
self.blinkMin = 1
self.blinkMax = 6
self.blinkDelay = random.randint(self.blinkMin,self.blinkMax)
self.blinking = 0
self.blinkIndex = 0
self.blinkDirection = 1
self.leftPoking = 0
self.rightPoking = 0
self.lastPoke = time.time()
self.pokeStart = 0
self.leftEyeBlit = pygame.Rect(0,0,1,1)
self.rightEyeBlit = pygame.Rect(0,0,1,1)
def getBlink(self):
if(self.blinking == 0): #determine when to blink again
if(not self.leftPoking):
self.leftEyeBlit = display.blit(self.leftEye,(self.leftEyeX,self.leftEyeY))
if(not self.rightPoking):
self.rightEyeBlit = display.blit(self.rightEye,(self.rightEyeX,self.rightEyeY+self.marginX))
if (time.time() - self.lastBlink >= self.blinkDelay):
print("Blink!" + "(" + str(self.blinkDelay) + ")")
self.blinking = 1
self.blinkIndex = 0
self.blinkDirection = 1
lastBlinkSwap = time.time()
if(self.blinkDelay <= 2):
self.blinkMin = 4
else:
self.blinkMin = 1
if(self.blinkDelay >= 4):
self.blinkMax = 3
else:
self.blinkMax = 6
self.blinkDelay = random.randint(self.blinkMin,self.blinkMax)
self.lastBlink = time.time()
else: #animate blinking
if(time.time() - self.lastBlinkSwap >= .05):
self.blinkIndex = self.blinkIndex + self.blinkDirection
if(self.blinkIndex >= len(self.leftEyeBlink)):
self.blinkIndex = len(self.leftEyeBlink)-1
self.blinkDirection = -1
if(self.blinkIndex == 0 and self.blinkDirection == -1):
## print("reset")
self.blinking = 0
self.blinkDirection = 1
self.lastBlink = time.time()
## print("blink index:" + str(self.blinkIndex))
if(not self.leftPoking):
self.leftEyeBlit = display.blit(self.leftEyeBlink[self.blinkIndex],(self.leftEyeX,self.leftEyeY))
if(not self.rightPoking):
self.rightEyeBlit= display.blit(self.rightEyeBlink[self.blinkIndex],(self.rightEyeX,self.rightEyeY+self.marginX))
self.lastBlinkSwap = time.time()
def getPoking(self):
if(self.leftPoking):
if(self.pokeStart == 0):
self.leftEyeBlit = display.blit(self.leftEyeBlink[-1],(self.leftEyeX,self.leftEyeY))
self.lastPoke = time.time();
self.pokeStart = time.time();
else:
if(time.time() - self.pokeStart > 10):
self.leftPoking = 0
self.pokeStart = 0
self.leftEyeBlit = display.blit(self.leftEye,(self.leftEyeX,self.leftEyeY))
# elif(self.rightPoking):
# if(not pokeStart)
# self.rightEyeBlit = display.blit(self.rightEyeBlink[-1],(self.rightEyeX,self.rightEyeY))
# self.lastPoke = time.time();
# self.pokeStart = time.time();
class Face:
def __init__(self,path):
#load each component of the eyes using the generic image loading function
self.talkSounds = LoadSounds(os.path.join(path , 'sounds/talk'))
self.singSounds = LoadSounds(os.path.join(path , 'sounds/sing'))
self.scareSounds = LoadSounds(os.path.join(path , 'sounds/scare'))
#create the eyes class
self.eyes = Eyes(path)
self.mouth = Mouth(path)
#define vars for face
#emperically determined coordinates
self.mouthX = 20
self.mouthY = 150
self.mouthW = 200
self.mouthH = 200
def PrintInfo(self):
print str(len(self.eyes.leftEyeSquint)) + ' left squint images loaded'
print str(len(self.eyes.leftEyeBlink )) + ' left blink images loaded'
print str(len(self.eyes.rightEyeSquint )) + ' right squint images loaded'
print str(len(self.eyes.rightEyeBlink )) + ' right blink images loaded'
print str(len(self.mouthTalk )) + ' talk images loaded'
print str(len(self.talkSounds )) + ' talk sounds loaded'
print str(len(self.singSounds )) + ' sing sounds loaded'
print str(len(self.scareSounds )) + ' scare sounds loaded'
#main code here
pygame.init()
display = pygame.display.set_mode((800, 480),pygame.FULLSCREEN | pygame.HWSURFACE | pygame.DOUBLEBUF )
pygame.display.set_caption('Funny Pumpkin')
#Create a list of faces classes, this example only has 1 face, but multiple faces can be used
faces = list()
#load the default face
faces.append(Face('./faces/default/'))
#test the class
##faces[0].PrintInfo()
for i in range(len(faces[0].eyes.leftEyeBlink)):
faces[0].eyes.leftEyeBlink[i] = pygame.transform.scale(faces[0].eyes.leftEyeBlink[i], (faces[0].eyes.leftEyeW-faces[0].eyes.marginX,faces[0].eyes.leftEyeH-faces[0].eyes.marginY))
faces[0].eyes.leftEyeBlink[i] = pygame.transform.rotate(faces[0].eyes.leftEyeBlink[i],270)
faces[0].eyes.rightEyeBlink.append(pygame.transform.flip(faces[0].eyes.leftEyeBlink[i],False,True))
faces[0].eyes.leftEye = pygame.transform.scale(faces[0].eyes.leftEye, (faces[0].eyes.leftEyeW-faces[0].eyes.marginX,faces[0].eyes.leftEyeH-faces[0].eyes.marginY))
faces[0].eyes.leftEye = pygame.transform.rotate(faces[0].eyes.leftEye,270)
faces[0].eyes.rightEye = pygame.transform.flip(faces[0].eyes.leftEye,False,True)
for i in range(len(faces[0].mouth.mouthTalk)):
faces[0].mouth.mouthTalk[i] = pygame.transform.rotate(faces[0].mouth.mouthTalk[i],270)
faces[0].mouth.mouthTalk[i] = pygame.transform.scale(faces[0].mouth.mouthTalk[i], (faces[0].mouth.mouthW-faces[0].mouth.marginX,faces[0].mouth.mouthH-faces[0].mouth.marginY))
faces[0].mouth.mouth = pygame.transform.rotate(faces[0].mouth.mouth,270)
faces[0].mouth.mouth = pygame.transform.scale(faces[0].mouth.mouth, (faces[0].mouth.mouthW-faces[0].mouth.marginX,faces[0].mouth.mouthH-faces[0].mouth.marginY))
#global vars
FPS = 30
r = list()
#main game loop
i = 0
faces[0].eyes.getBlink()
while(1):
faces[0].eyes.getBlink()
faces[0].eyes.getPoking()
faces[0].mouth.talk()
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
pos = pygame.mouse.get_pos()
if(faces[0].eyes.leftEyeBlit.collidepoint(pos)):
faces[0].eyes.leftPoking = 1
elif(faces[0].eyes.rightEyeBlit.collidepoint(pos)):
faces[0].eyes.rightPoking = 1;
pygame.display.update()
