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()