Showing posts with label pygame. Show all posts
Showing posts with label pygame. Show all posts

Pygame animation sprite base class

.
Here's a base class for animated sprites which includes code to ensure that the images are only loaded once. It uses a dictionary of image frames keyed against the classname to achieve this.

import pygame, math, random, sys, os
from pygame.locals import *

IMAGE_PATH = '../res/images/'

class AnimationSprite(pygame.sprite.Sprite):
'''Base class for animated sprites'''

# This will hold a dict of frame lists keyed on the sprite's class name
# The images are only ever loaded once
animationFrames = {}

def __init__(self, spriteGroup):
pygame.sprite.Sprite.__init__(self)
self.frameCount = 0
self.animFrameCount = 1
self.loadImages()
frames = AnimationSprite.animationFrames[self.__class__.__name__]
self.image = frames[self.frameCount]
self.rect = self.image.get_rect()
self.spriteGroup = spriteGroup
self.spriteGroup.add(self)

def update(self, events):
'''Called by the SpriteGroup every tick'''
self.doMove(events)
self.frameCount += 1
if not self.frameSkip == 0 and (self.frameCount % self.frameSkip) == 0:
self.animFrameCount += 1
index = self.animFrameCount % self.numberOfFrames
frames = AnimationSprite.animationFrames[self.__class__.__name__]
self.image = frames[index]

def loadImages(self):
'''Load the animation frames from the composite ship image'''

# See if we've already loaded the image for this sprite
if not AnimationSprite.animationFrames.has_key(self.__class__.__name__):
print 'Loading graphics for: ', self.__class__.__name__
compositeImage = pygame.image.load(os.path.join(IMAGE_PATH, self.imageName)).convert_alpha()
rowOffsetY = 0
frames = []
for i in range(0, self.numberOfFrames):
img = compositeImage.subsurface((self.width * i, 0), (self.width, self.height))
frames.append(img)
AnimationSprite.animationFrames[self.__class__.__name__] = frames



Pygame - creating a game background

.
Here's how the background for SubHunter is created with Pygame.


def createBackgroundImage(self):
'''Generate a background of sky, sea and sand'''

backgroundSurface = pygame.Surface((main.GAME_WIDTH, main.GAME_HEIGHT)).convert()

# Sky
sky = pygame.Surface((main.GAME_WIDTH, main.SKY_HEIGHT)).convert()
for y in range(0, int(main.SKY_HEIGHT)):
lineRect = Rect((0, y), (main.GAME_WIDTH, 1))
greenValue = (y / main.SKY_HEIGHT) * 255
sky.fill((53, greenValue, 207), lineRect)
#sky.fill((greenValue, 0, 07), lineRect)
backgroundSurface.blit(sky, (0, 0))

# Sea
sea = pygame.Surface((main.GAME_WIDTH, main.SEA_HEIGHT)).convert()
for i in range(0, int(main.SEA_HEIGHT)):
lineRect = Rect((0, i), (main.GAME_WIDTH, 1))
blueValue = 255 - ((i / main.SEA_HEIGHT) * 255);
sea.fill((0, 20, blueValue), lineRect)
backgroundSurface.blit(sea, (0, main.SKY_HEIGHT))

# Sand
sand = pygame.Surface((main.GAME_WIDTH, main.SAND_HEIGHT)).convert()
for i in range(0, int(main.SAND_HEIGHT)):
lineRect = Rect((0, main.SAND_HEIGHT - i), (main.GAME_WIDTH, 1))
colourValue = ((main.SAND_HEIGHT - i) / (main.GAME_HEIGHT - main.SAND_HEIGHT)) * 255
sand.fill((colourValue * 4, colourValue * 4, colourValue * 0.1), lineRect)
backgroundSurface.blit(sand, (0, main.GAME_HEIGHT - main.SAND_HEIGHT))

SubHunter Python/Pygame port


Version 0.2
Ok now we have subs and depth charges.



Version 0.1

Started porting an old Java game that I wrote in 1999 to Python / Pygame. Very early days. So far just an animation sprite implemented by one sprite, the ship which the player controls.

This game is a port of an old 1980's arcade game called Battle Cruiser


Pythentic Asteroids v0.1

Click Me!











Here's a video of the original.

Detecting Intersecting Lines in Python Pygame

Here's some functions that I wrote to help with collision detection in a retro arcade remake of Asteroids using Python and Pygame. The code is GPL license and includes some simple assertion tests at the bottom.

Features:
  • Calculate the point of intersection of two line segments
  • Handles lines segments rather than just infinitely long lines
  • Handles vertical lines (infinite gradient)
  • Handles horizontal lines (zero gradient)
  • Handles parallel lines (never intersect)
  • Calculate line gradients
  • Calculate Y axis intersect point
The entry point for an intersect test is the getIntersectPoint function.
#    geometry.py
#
#    Geometry functions to find intersecting lines.
#    Thes calc's use this formula for a straight line:-
#        y = mx + b where m is the gradient and b is the y value when x=0
#
#    See here for background http://www.mathopenref.com/coordintersection.html
#    
#    Throughout the code the variable p is a point tuple representing (x,y)
#
#    Copyright (C) 2008  Nick Redshaw
#
#    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 3 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, see .

from __future__ import division
from pygame import Rect

# Calc the gradient 'm' of a line between p1 and p2
def calculateGradient(p1, p2):

# Ensure that the line is not vertical
  if (p1[0] != p2[0]):
    m = (p1[1] - p2[1]) / (p1[0] - p2[0])
    return m
  else:
    return None

# Calc the point 'b' where line crosses the Y axis
def calculateYAxisIntersect(p, m):
  return  p[1] - (m * p[0])

# Calc the point where two infinitely long lines (p1 to p2 and p3 to p4) intersect.
# Handle parallel lines and vertical lines (the later has infinate 'm').
# Returns a point tuple of points like this ((x,y),...)  or None
# In non parallel cases the tuple will contain just one point.
# For parallel lines that lay on top of one another the tuple will contain
# all four points of the two lines
def getIntersectPoint(p1, p2, p3, p4):
  m1 = calculateGradient(p1, p2)
  m2 = calculateGradient(p3, p4)

# See if the the lines are parallel
  if (m1 != m2):
  # Not parallel

  # See if either line is vertical
  if (m1 is not None and m2 is not None):
    # Neither line vertical           
    b1 = calculateYAxisIntersect(p1, m1)
    b2 = calculateYAxisIntersect(p3, m2)   
    x = (b2 - b1) / (m1 - m2)       
    y = (m1 * x) + b1           
  else:
    # Line 1 is vertical so use line 2's values
    if (m1 is None):
      b2 = calculateYAxisIntersect(p3, m2)   
      x = p1[0]
      y = (m2 * x) + b2
      # Line 2 is vertical so use line 1's values               
  elif (m2 is None):
    b1 = calculateYAxisIntersect(p1, m1)
    x = p3[0]
    y = (m1 * x) + b1           
  else:
    assert false

  return ((x,y),)
  else:
    # Parallel lines with same 'b' value must be the same line so they intersect
    # everywhere in this case we return the start and end points of both lines
    # the calculateIntersectPoint method will sort out which of these points
    # lays on both line segments
    b1, b2 = None, None # vertical lines have no b value
    if m1 is not None:
    b1 = calculateYAxisIntersect(p1, m1)

    if m2 is not None:   
      b2 = calculateYAxisIntersect(p3, m2)

    # If these parallel lines lay on one another   
    if b1 == b2:
      return p1,p2,p3,p4
    else:
      return None

# For line segments (ie not infinitely long lines) the intersect point
# may not lay on both lines.
#   
# If the point where two lines intersect is inside both line's bounding
# rectangles then the lines intersect. Returns intersect point if the line
# intesect o None if not
def calculateIntersectPoint(p1, p2, p3, p4):

  p = getIntersectPoint(p1, p2, p3, p4)

  if p is not None:               
    width = p2[0] - p1[0]
    height = p2[1] - p1[1]       
    r1 = Rect(p1, (width , height))
    r1.normalize()

    width = p4[0] - p3[0]
    height = p4[1] - p3[1]
    r2 = Rect(p3, (width, height))
    r2.normalize()              

    # Ensure both rects have a width and height of at least 'tolerance' else the
    # collidepoint check of the Rect class will fail as it doesn't include the bottom
    # and right hand side 'pixels' of the rectangle
    tolerance = 1
    if r1.width < tolerance:
    r1.width = tolerance

    if r1.height < tolerance:
    r1.height = tolerance

    if r2.width < tolerance:
    r2.width = tolerance

    if r2.height < tolerance:
    r2.height = tolerance

    for point in p:                 
      try:    
        res1 = r1.collidepoint(point)
        res2 = r2.collidepoint(point)
        if res1 and res2:
          point = [int(pp) for pp in point]                                
          return point
      except:
        # sometimes the value in a point are too large for PyGame's Rect class
        str = "point was invalid  ", point                
        print str

    # This is the case where the infinately long lines crossed but 
    # the line segments didn't
    return None            

  else:
    return None


# Test script below...
if __name__ == "__main__":

# line 1 and 2 cross, 1 and 3 don't but would if extended, 2 and 3 are parallel
# line 5 is horizontal, line 4 is vertical
p1 = (1,5)
p2 = (4,7)

p3 = (4,5)
p4 = (3,7)

p5 = (4,1)
p6 = (3,3)

p7 = (3,1)
p8 = (3,10)

p9 =  (0,6)
p10 = (5,6)

p11 = (472.0, 116.0)
p12 = (542.0, 116.0)  

assert None != calculateIntersectPoint(p1, p2, p3, p4), "line 1 line 2 should intersect"
assert None != calculateIntersectPoint(p3, p4, p1, p2), "line 2 line 1 should intersect"
assert None == calculateIntersectPoint(p1, p2, p5, p6), "line 1 line 3 shouldn't intersect"
assert None == calculateIntersectPoint(p3, p4, p5, p6), "line 2 line 3 shouldn't intersect"
assert None != calculateIntersectPoint(p1, p2, p7, p8), "line 1 line 4 should intersect"
assert None != calculateIntersectPoint(p7, p8, p1, p2), "line 4 line 1 should intersect"
assert None != calculateIntersectPoint(p1, p2, p9, p10), "line 1 line 5 should intersect"
assert None != calculateIntersectPoint(p9, p10, p1, p2), "line 5 line 1 should intersect"
assert None != calculateIntersectPoint(p7, p8, p9, p10), "line 4 line 5 should intersect"
assert None != calculateIntersectPoint(p9, p10, p7, p8), "line 5 line 4 should intersect"

print "\nSUCCESS! All asserts passed for doLinesIntersect"


Pythentic Asteroids screencast

A short screencast of Pythentic Asteroids v0.1 game in action. My laptop struggled to capture the video and run the game at the same time hence the jerkiness.

Blogger seems to reduce the size of the video making it very hard to see what's going on here.

Vector sprite and game stage class (python)


As part of a learning exercise for Python I've been porting an old Asteroids clone from Java that I wrote 10 years ago for a digital TV set-top-box.

Here's the first cut of the VectorSprite class and the Stage class. The later sets up the PyGame environment. The VectorSprite class is the basis for all the spites in the game, it handles all the trig functions and currently supports transformation, rotation and scaling.

I'll post the whole game when it's complete as well as posting it on the PyGame site.
#    Pythentic Asteroids - vectorsprites.py
#
#    Copyright (C) 2008  Nick Redshaw
#
#    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 3 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, see .

import pygame, sys, os, math
from pygame.locals import *
from math import *

class VectorSprite: 

def __init__(self, x, y, vx, vy, pointlist, stage):
self.x = x
self.y = y
self.vx = vx
self.vy = vy
self.angle = 0
self.pointlist = pointlist # raw pointlist    
self.rotateAndTransform()
self.stage = stage
self.color = (255,255,255)
self.draw()

# roatate each x,y coord by the angle, then translate it to the x,y position
def rotateAndTransform(self):
newPointList = [self.rotatePoint(point) for point in self.pointlist]
self.transformedPointlist = [self.translatePoint(point) for point in newPointList]

# draw the sprite         
def draw(self):
self.boundingRect = pygame.draw.aalines(self.stage.screen, self.color, True, self.transformedPointlist)              

# comment in to see bounding rects
#pygame.draw.rect(self.stage.screen, self.color, self.boundingRect, 1)

# translate each point to the current x, y position  
def translatePoint(self, point):             
newPoint = []
newPoint.append(point[0] + self.x)
newPoint.append(point[1] + self.y)

return newPoint

# Move the sprite by the velocity 
def move(self):     
if self.x <>
self.x = self.stage.dimensions[0]

if self.x > self.stage.dimensions[0]:
self.x = 0

if self.y <>
self.y = self.stage.dimensions[1]

if self.y > self.stage.dimensions[1]:
self.y = 0

self.x = self.x + self.vx
self.y = self.y + self.vy
self.rotateAndTransform()

# Rotate a point by the given angle
def rotatePoint(self, point):
newPoint = []
cosVal = math.cos(radians(self.angle))
sinVal = math.sin(radians(self.angle))
newPoint.append(point[0] * cosVal + point[1] * sinVal)
newPoint.append(point[1] * cosVal - point[0] * sinVal)
return newPoint

# Scale a point
def scale(self, point, scale):
newPoint = []
newPoint.append(point[0] * scale)
newPoint.append(point[1] * scale)
return newPoint

def collidesWith(self, target):
if self.boundingRect.colliderect(target.boundingRect):
return True
else:
return False


#    Pythentic Asteroids - stage.py
#    Copyright (C) 2008  Nick Redshaw
#
#    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 3 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, see .

import pygame, sys, os

class Stage:

def __init__(self, dimensions, caption):
pygame.init()
pygame.display.set_mode(dimensions)
pygame.display.set_caption(caption)
self.screen = pygame.display.get_surface()
self.spriteList = []
self.dimensions = dimensions

def addSprite(self, sprite):
self.spriteList.append(sprite)

def removeSprite(self, sprite):     
self.spriteList.remove(sprite)

def drawSprites(self):
for sprite in self.spriteList:
sprite.draw()

def moveSprites(self):
for sprite in self.spriteList:
sprite.move()