Jump to content
  • Advertisement
Sign in to follow this  
PaladinJohn

Python - Issue regarding custom Game Engine

This topic is 2599 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm trying to familiarize myself with writing code utilizing an engine, so I thought I would recreate a simple game I made before with a very basic game engine. It all works well except for one flaw... this version does not take gravity into account like my original version does! I've even pretty much copy/pasted the block of code that controls that part of the program, leading me to believe there is something that I am sorely missing. I've spent 2 hours so far trying to jury rig the code in every possible way I can think of, but none of it will work. If any one can look over the following code and let me know where I may have gone wrong, I would appreciate it. (Also, not sure why indentation isn't working in some areas when it is clearly indented in the box I'm typing this in.)

Original Code (Works as intended):
[spoiler]class Turret(pygame.sprite.Sprite):
def __init__(self, shell):
pygame.sprite.Sprite.__init__(self)
self.imageMaster = pygame.image.load("turret.gif")
self.imageMaster = self.imageMaster.convert()
self.rect = self.imageMaster.get_rect()
self.rect.center = (320, 240)
self.turnRate = 10
self.dir = 0
self.shell = shell
self.power = 5

def update(self):
self.checkKeys()
self.rotate()

def checkKeys(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.dir += self.turnRate
if self.dir > 360:
self.dir = self.turnRate
if keys[pygame.K_RIGHT]:
self.dir -= self.turnRate
if self.dir < 0:
self.dir = 360 - self.turnRate

if keys[pygame.K_UP]:
self.power += 1
if self.power > 20:
self.power = 20
if keys[pygame.K_DOWN]:
self.power -= 1
if self.power < 0:
self.power = 0

if keys[pygame.K_SPACE]:
self.shell.x = self.rect.centerx
self.shell.y = self.rect.centery
self.shell.speed = self.power
self.shell.dir = self.dir
self.shell.calcVector()

def rotate(self):
oldCenter = self.rect.center
self.image = pygame.transform.rotate(self.imageMaster, self.dir)
self.rect = self.image.get_rect()
self.rect.center = oldCenter

class Shell(pygame.sprite.Sprite):
def __init__(self, screen):
pygame.sprite.Sprite.__init__(self)
self.screen = screen
self.image = pygame.Surface((10, 10))
self.image.fill((0xff, 0xff, 0xff))
self.image.set_colorkey((0xff, 0xff, 0xff))
pygame.draw.circle(self.image, (0, 0, 0), (5, 5), 5)
self.image = pygame.transform.scale(self.image, (5, 5))
self.rect = self.image.get_rect()
self.rect.center = (-100, -100)
self.speed = 0
self.dir = 0
self.gravity = .5
self.dx = 0
self.dy = 0
self.reset()

def update(self):
self.calcPos()
self.checkBounds()
self.rect.center = (self.x, self.y)

def calcVector(self):
radians = self.dir * math.pi / 180

self.dx = self.speed * math.cos(radians)
self.dy = self.speed * math.sin(radians)
self.dy *= -1

def calcPos(self):
self.x += self.dx
self.y += self.dy
self.dy += self.gravity

def checkBounds(self):
screen = self.screen
if self.x > screen.get_width():
self.reset()
if self.x < 0:
self.reset()
if self.y > screen.get_height():
self.reset()
if self.y < 0:
self.reset()

def reset(self):
self.x = -100
self.y = -100
self.speed = 0
[/spoiler]

With Game Engine (No effect of gravity on the shell):

[spoiler]class Turret(gameEngine.SuperSprite):
def __init__(self, scene):
gameEngine.SuperSprite.__init__(self, scene)
self.setImage("turret.gif")
self.setPosition((320, 240))
self.power = 5

def checkEvents(self):
self.checkKeys()

def checkKeys(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.turnBy(10)
if keys[pygame.K_RIGHT]:
self.turnBy(-10)

if keys[pygame.K_UP]:
self.power += 1
if self.power > 20:
self.power = 20
if keys[pygame.K_DOWN]:
self.power -= 1
if self.power < 0:
self.power = 0

if keys[pygame.K_SPACE]:
self.scene.shell.fire()

class Shell(gameEngine.SuperSprite):
def __init__(self, scene):
gameEngine.SuperSprite.__init__(self, scene)
self.setImage("bullet.gif")
self.imageMaster = pygame.transform.scale(self.imageMaster, (5, 5))
self.setBoundAction(self.HIDE)
self.setSpeedLimits(20, 0)
self.setGravity(.5)
self.reset()

def fire(self):
self.setPosition((self.scene.turret.x, self.scene.turret.y))
self.setAngle(self.scene.turret.rotation)
self.setSpeed(self.scene.turret.power)

def reset(self):
self.setPosition((-100, -100))
self.setSpeed(0)
[/spoiler]

Game Engine Code for SuperSprites:

[spoiler]class BasicSprite(pygame.sprite.Sprite):
""" use this sprite when you want to
directly control the sprite with dx and dy
or want to extend in another direction than DirSprite
"""
def __init__(self, scene):
pygame.sprite.Sprite.__init__(self)
self.screen = scene.screen
self.image = pygame.Surface((25, 25))
self.image.fill((255, 0, 0))
self.rect = self.image.get_rect()
self.x = 100
self.y = 100
self.dx = 0
self.dy = 0

def update(self):
self.x += self.dx
self.y += self.dy
self.checkBounds()
self.rect.center = (self.x, self.y)

def checkBounds(self):
scrWidth = self.screen.get_width()
scrHeight = self.screen.get_height()

if self.x > scrWidth:
self.x = 0
if self.x < 0:
self.x = scrWidth
if self.y > scrHeight:
self.y = 0
if self.y < 0:
self.y = scrHeight

class SuperSprite(pygame.sprite.Sprite):
""" An enhanced Sprite class
expects a gameEngine.Scene class as its one parameter
Use methods to change image, direction, speed
Will automatically travel in direction and speed indicated
Automatically rotates to point in indicated direction
Five kinds of boundary collision
"""

def __init__(self, scene):
pygame.sprite.Sprite.__init__(self)
self.scene = scene
self.screen = scene.screen

#create constants
self.WRAP = 0
self.BOUNCE = 1
self.STOP = 2
self.HIDE = 3
self.CONTINUE = 4

#create a default text image as a placeholder
#This will usually be changed by a setImage call
self.font = pygame.font.Font("freesansbold.ttf", 30)
self.imageMaster = self.font.render(">sprite>", True, (0, 0,0), (0xFF, 0xFF, 0xFF))
self.image = self.imageMaster
self.rect = self.image.get_rect()

#create properties
#most will be changed through method calls
self.x = 200
self.y = 200
self.dx = 0
self.dy = 0
self.dir = 0
self.rotation = 0
self.speed = 0
self.gravity = 0
self.maxSpeed = 10
self.minSpeed = -3
self.boundAction = self.WRAP
self.pressed = False
self.oldCenter = (100, 100)

def update(self):
self.oldCenter = self.rect.center
self.checkEvents()
self.__rotate()
self.__calcVector()
self.__calcPosition()
self.checkBounds()
self.rect.center = (self.x, self.y)

def checkEvents(self):
""" overwrite this method to add your own event code """
pass

def __rotate(self):
""" PRIVATE METHOD
change visual orientation based on
rotation property.
automatically called in update.
change rotation property directly or with
rotateBy(), setAngle() methods
"""
oldCenter = self.rect.center
self.oldCenter = oldCenter
self.image = pygame.transform.rotate(self.imageMaster, self.rotation)
self.rect = self.image.get_rect()
self.rect.center = oldCenter

def __calcVector(self):
""" calculates dx and dy based on speed, dir
automatically called in update()
"""
theta = self.dir / 180.0 * math.pi
self.dx = math.cos(theta) * self.speed
self.dy = math.sin(theta) * self.speed
self.dy *= -1

def __calcPosition(self):
""" calculates the sprites position adding
dx and dy to x and y.
automatically called in update()
"""
self.x += self.dx
self.y += self.dy
self.dy += self.gravity

def checkBounds(self):
""" checks boundary and acts based on
self.BoundAction.
WRAP: wrap around screen (default)
BOUNCE: bounce off screen
STOP: stop at edge of screen
HIDE: move off stage and wait
CONTINUE: keep going at present course and speed

automatically called by update()
"""

scrWidth = self.screen.get_width()
scrHeight = self.screen.get_height()

#create variables to simplify checking
offRight = offLeft = offTop = offBottom = offScreen = False

if self.x > scrWidth:
offRight = True
if self.x < 0:
offLeft = True
if self.y > scrHeight:
offBottom = True
if self.y < 0:
offTop = True

if offRight or offLeft or offTop or offBottom:
offScreen = True

if self.boundAction == self.WRAP:
if offRight:
self.x = 0
if offLeft:
self.x = scrWidth
if offBottom:
self.y = 0
if offTop:
self.y = scrHeight

elif self.boundAction == self.BOUNCE:
if offLeft or offRight:
self.dx *= -1
if offTop or offBottom:
self.dy *= -1

self.updateVector()
self.rotation = self.dir

elif self.boundAction == self.STOP:
if offScreen:
self.speed = 0

elif self.boundAction == self.HIDE:
if offScreen:
self.speed = 0
self.setPosition((-1000, -1000))

elif self.boundAction == self.CONTINUE:
pass

else:
# assume it's CONTINUE - keep going forever
pass

def setSpeed(self, speed):
""" immediately sets the objects speed to the
given value.
"""
self.speed = speed

def setGravity(self, amount):
self.gravity = amount

def speedUp(self, amount):
""" changes speed by the given amount
Use a negative value to slow down
"""
self.speed += amount
if self.speed < self.minSpeed:
self.speed = self.minSpeed
if self.speed > self.maxSpeed:
self.speed = self.maxSpeed

def setAngle(self, dir):
""" sets both the direction of motion
and visual rotation to the given angle
If you want to set one or the other,
set them directly. Angle measured in degrees
"""
self.dir = dir
self.rotation = dir

def turnBy (self, amt):
""" turn by given number of degrees. Changes
both motion and visual rotation. Positive is
counter-clockwise, negative is clockwise
"""
self.dir += amt
if self.dir > 360:
self.dir = amt
if self.dir < 0:
self.dir = 360 - amt
self.rotation = self.dir

def rotateBy(self, amt):
""" change visual orientation by given
number of degrees. Does not change direction
of travel.
"""
self.rotation += amt
if self.rotation > 360:
self.rotation = amt
if self.rotation < 0:
self.rotation = 360 - amt

def setImage (self, image):
""" loads the given file name as the master image
default setting should be facing east. Image
will be rotated automatically """
self.imageMaster = pygame.image.load(image)
self.imageMaster = self.imageMaster.convert()

def setDX(self, dx):
""" changes dx value and updates vector """
self.dx = dx
self.updateVector()

def addDX(self, amt):
""" adds amt to dx, updates vector """
self.dx += amt
self.updateVector()

def setDY(self, dy):
""" changes dy value and updates vector """
self.dy = dy
self.updateVector()

def addDY(self, amt):
""" adds amt to dy and updates vector """
self.dy += amt
self.updateVector()

def setComponents(self, components):
""" expects (dx, dy) for components
change speed and angle according to dx, dy values """

(self.dx, self.dy) = components
self.updateVector()

def setBoundAction (self, action):
""" sets action for boundary. Values are
self.WRAP (wrap around edge - default)
self.BOUNCE (bounce off screen changing direction)
self.STOP (stop at edge of screen)
self.HIDE (move off-stage and stop)
self.CONTINUE (move on forever)
Any other value allows the sprite to move on forever
"""
self.boundAction = action

def setPosition (self, position):
""" place the sprite directly at the given position
expects an (x, y) tuple
"""
(self.x, self.y) = position

def moveBy (self, vector):
""" move the sprite by the (dx, dy) values in vector
automatically calls checkBounds. Doesn't change
speed or angle settings.
"""
(dx, dy) = vector
self.x += dx
self.y += dy
self.__checkBounds()

def forward(self, amt):
""" move amt pixels in the current direction
of travel
"""

#calculate dx dy based on current direction
radians = self.dir * math.pi / 180
dx = amt * math.cos(radians)
dy = amt * math.sin(radians) * -1

self.x += dx
self.y += dy

def addForce(self, amt, angle):
""" apply amt of thrust in angle.
change speed and dir accordingly
add a force straight down to simulate gravity
in rotation direction to simulate spacecraft thrust
in dir direction to accelerate forward
at an angle for retro-rockets, etc.
"""

#calculate dx dy based on angle
radians = angle * math.pi / 180
dx = amt * math.cos(radians)
dy = amt * math.sin(radians) * -1

self.dx += dx
self.dy += dy
self.updateVector()

def updateVector(self):
#calculate new speed and angle based on dx, dy
#call this any time you change dx or dy

self.speed = math.sqrt((self.dx * self.dx) + (self.dy * self.dy))

dy = self.dy * -1
dx = self.dx

radians = math.atan2(dy, dx)
self.dir = radians / math.pi * 180

def setSpeedLimits(self, max, min):
""" determines maximum and minimum
speeds you will allow through
speedUp() method. You can still
directly set any speed you want
with setSpeed() Default values:
max: 10
min: -3
"""
self.maxSpeed = max
self.minSpeed = min

def dataTrace(self):
""" utility method for debugging
print major properties
extend to add your own properties
"""
print "x: %d, y: %d, speed: %.2f, dir: %.f, dx: %.2f, dy: %.2f" % \
(self.x, self.y, self.speed, self.dir, self.dx, self.dy)

def mouseDown(self):
""" boolean function. Returns True if the mouse is
clicked over the sprite, False otherwise
"""
self.pressed = False
if pygame.mouse.get_pressed() == (1, 0, 0):
if self.rect.collidepoint(pygame.mouse.get_pos()):
self.pressed = True
return self.pressed

def clicked(self):
""" Boolean function. Returns True only if mouse
is pressed and released over sprite

"""
released = False
if self.pressed:
if pygame.mouse.get_pressed() == (0, 0, 0):
if self.rect.collidepoint(pygame.mouse.get_pos()):
released = True
return released

def collidesWith(self, target):
""" boolean function. Returns True if the sprite
is currently colliding with the target sprite,
False otherwise
"""
collision = False
if self.rect.colliderect(target.rect):
collision = True
return collision

def collidesGroup(self, target):
""" wrapper for pygame.sprite.spritecollideany() function
simplifies checking sprite - group collisions
returns result of collision check (sprite from group
that was hit or None)
"""
collision = pygame.sprite.spritecollideany(self, target)
return collision

def distanceTo(self, point):
""" returns distance to any point in pixels
can be used in circular collision detection
"""
(pointx, pointy) = point
dx = self.x - pointx
dy = self.y - pointy

dist = math.sqrt((dx * dx) + (dy * dy))
return dist

def dirTo(self, point):
""" returns direction (in degrees) to
a point """

(pointx, pointy) = point
dx = self.x - pointx
dy = self.y - pointy
dy *= -1

radians = math.atan2(dy, dx)
dir = radians * 180 / math.pi
dir += 180
return dir

def drawTrace(self, color=(0x00, 0x00, 0x00)):
""" traces a line between previous position
and current position of object
"""
pygame.draw.line(self.scene.background, color, self.oldCenter,
self.rect.center, 3)
self.screen.blit(self.scene.background, (0, 0))
[/spoiler]

Just in case the error is not in any of that code provided, here is the entire source code for all three programs.

Old Program (Complete):

[spoiler]import pygame, math, random
pygame.init()

class Turret(pygame.sprite.Sprite):
def __init__(self, shell):
pygame.sprite.Sprite.__init__(self)
self.imageMaster = pygame.image.load("turret.gif")
self.imageMaster = self.imageMaster.convert()
self.rect = self.imageMaster.get_rect()
self.rect.center = (320, 240)
self.turnRate = 10
self.dir = 0
self.shell = shell
self.power = 5

def update(self):
self.checkKeys()
self.rotate()

def checkKeys(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.dir += self.turnRate
if self.dir > 360:
self.dir = self.turnRate
if keys[pygame.K_RIGHT]:
self.dir -= self.turnRate
if self.dir < 0:
self.dir = 360 - self.turnRate

if keys[pygame.K_UP]:
self.power += 1
if self.power > 20:
self.power = 20
if keys[pygame.K_DOWN]:
self.power -= 1
if self.power < 0:
self.power = 0

if keys[pygame.K_SPACE]:
self.shell.x = self.rect.centerx
self.shell.y = self.rect.centery
self.shell.speed = self.power
self.shell.dir = self.dir
self.shell.calcVector()

def rotate(self):
oldCenter = self.rect.center
self.image = pygame.transform.rotate(self.imageMaster, self.dir)
self.rect = self.image.get_rect()
self.rect.center = oldCenter

class Label(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.font = pygame.font.SysFont("None", 30)
self.text = ""
self.center = (320, 240)

def update(self):
self.image = self.font.render(self.text, 1, (0, 0, 0,))
self.rect = self.image.get_rect()
self.rect.center = self.center

class Shell(pygame.sprite.Sprite):
def __init__(self, screen):
pygame.sprite.Sprite.__init__(self)
self.screen = screen
self.image = pygame.Surface((10, 10))
self.image.fill((0xff, 0xff, 0xff))
self.image.set_colorkey((0xff, 0xff, 0xff))
pygame.draw.circle(self.image, (0, 0, 0), (5, 5), 5)
self.image = pygame.transform.scale(self.image, (5, 5))
self.rect = self.image.get_rect()
self.rect.center = (-100, -100)
self.speed = 0
self.dir = 0
self.gravity = .5
self.dx = 0
self.dy = 0
self.reset()

def update(self):
self.calcPos()
self.checkBounds()
self.rect.center = (self.x, self.y)

def calcVector(self):
radians = self.dir * math.pi / 180

self.dx = self.speed * math.cos(radians)
self.dy = self.speed * math.sin(radians)
self.dy *= -1

def calcPos(self):
self.x += self.dx
self.y += self.dy
self.dy += self.gravity

def checkBounds(self):
screen = self.screen
if self.x > screen.get_width():
self.reset()
if self.x < 0:
self.reset()
if self.y > screen.get_height():
self.reset()
if self.y < 0:
self.reset()

def reset(self):
self.x = -100
self.y = -100
self.speed = 0

class Target(pygame.sprite.Sprite):
def __init__(self, screen):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("target.gif")
self.image = self.image.convert()
self.rect = self.image.get_rect()
self.screen = screen
self.reset()

def reset(self):
screen = self.screen
self.rect.centery = random.randrange(0, screen.get_height())
self.rect.centerx = random.randrange(0, screen.get_width())

def main():
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption ("Rotating Turret")

background = pygame.Surface(screen.get_size())
background.fill((0, 250, 0))
screen.blit(background, (0, 0))

shell = Shell(screen)
turret = Turret(shell)
lblOutput = Label()
lblOutput.center = (110, 20)
target = Target(screen)
allSprites = pygame.sprite.OrderedUpdates(shell, turret, lblOutput, target)

clock = pygame.time.Clock()
keepGoing = True
while keepGoing:
clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT:
keepGoing = False

if shell.rect.colliderect(target.rect):
target.reset()
shell.reset()

lblOutput.text = "Angle: %d Power: %d" % (turret.dir, turret.power)
allSprites.clear(screen, background)
allSprites.update()
allSprites.draw(screen)
pygame.display.flip()

if __name__ == "__main__":
main()
[/spoiler]

New Program (Complete):

[spoiler]import pygame, random, gameEngine

class Turret(gameEngine.SuperSprite):
def __init__(self, scene):
gameEngine.SuperSprite.__init__(self, scene)
self.setImage("turret.gif")
self.setPosition((320, 240))
self.power = 5

def checkEvents(self):
self.checkKeys()

def checkKeys(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.turnBy(10)
if keys[pygame.K_RIGHT]:
self.turnBy(-10)

if keys[pygame.K_UP]:
self.power += 1
if self.power > 20:
self.power = 20
if keys[pygame.K_DOWN]:
self.power -= 1
if self.power < 0:
self.power = 0

if keys[pygame.K_SPACE]:
self.scene.shell.fire()

class Shell(gameEngine.SuperSprite):
def __init__(self, scene):
gameEngine.SuperSprite.__init__(self, scene)
self.setImage("bullet.gif")
self.imageMaster = pygame.transform.scale(self.imageMaster, (5, 5))
self.setBoundAction(self.HIDE)
self.setSpeedLimits(20, 0)
self.reset()

def fire(self):
self.setPosition((self.scene.turret.x, self.scene.turret.y))
self.setAngle(self.scene.turret.rotation)
self.setSpeed(self.scene.turret.power)

def reset(self):
self.setPosition((-100, -100))
self.setSpeed(0)

class Target(gameEngine.SuperSprite):
def __init__(self, scene):
gameEngine.SuperSprite.__init__(self, scene)
self.setImage("target.gif")
self.reset()

def reset(self):
x = random.randint(0, self.screen.get_width())
y = random.randint(0, self.screen.get_height())
self.setPosition((x, y))

class Game(gameEngine.Scene):
def __init__(self):
gameEngine.Scene.__init__(self)
self.setCaption("Rotating Turret")
self.turret = Turret(self)
self.shell = Shell(self)
self.target = Target(self)
self.gravity = .5

self.lblOutput = gameEngine.Label()
self.lblOutput.center = (110, 20)
self.lblOutput.size = (225, 20)

self.sprites = [self.lblOutput, self.shell, self.turret, self.target]

self.background.fill((0, 245, 0))

def update(self):
self.updateInfo()

self.shell.dy += self.gravity

if self.shell.collidesWith(self.target):
self.target.reset()
self.shell.reset()

def updateInfo(self):
info = "Angle: %d Power: %d" % (self.turret.dir, self.turret.power)
self.lblOutput.text = info

def main():
game = Game()
game.start()

if __name__ == "__main__":
main()
[/spoiler]

Game Engine (Complete):

[spoiler]import pygame, math
pygame.init()

class BasicSprite(pygame.sprite.Sprite):
""" use this sprite when you want to
directly control the sprite with dx and dy
or want to extend in another direction than DirSprite
"""
def __init__(self, scene):
pygame.sprite.Sprite.__init__(self)
self.screen = scene.screen
self.image = pygame.Surface((25, 25))
self.image.fill((255, 0, 0))
self.rect = self.image.get_rect()
self.x = 100
self.y = 100
self.dx = 0
self.dy = 0

def update(self):
self.x += self.dx
self.y += self.dy
self.checkBounds()
self.rect.center = (self.x, self.y)

def checkBounds(self):
scrWidth = self.screen.get_width()
scrHeight = self.screen.get_height()

if self.x > scrWidth:
self.x = 0
if self.x < 0:
self.x = scrWidth
if self.y > scrHeight:
self.y = 0
if self.y < 0:
self.y = scrHeight

class SuperSprite(pygame.sprite.Sprite):
""" An enhanced Sprite class
expects a gameEngine.Scene class as its one parameter
Use methods to change image, direction, speed
Will automatically travel in direction and speed indicated
Automatically rotates to point in indicated direction
Five kinds of boundary collision
"""

def __init__(self, scene):
pygame.sprite.Sprite.__init__(self)
self.scene = scene
self.screen = scene.screen

#create constants
self.WRAP = 0
self.BOUNCE = 1
self.STOP = 2
self.HIDE = 3
self.CONTINUE = 4

#create a default text image as a placeholder
#This will usually be changed by a setImage call
self.font = pygame.font.Font("freesansbold.ttf", 30)
self.imageMaster = self.font.render(">sprite>", True, (0, 0,0), (0xFF, 0xFF, 0xFF))
self.image = self.imageMaster
self.rect = self.image.get_rect()

#create properties
#most will be changed through method calls
self.x = 200
self.y = 200
self.dx = 0
self.dy = 0
self.dir = 0
self.rotation = 0
self.speed = 0
self.gravity = 0
self.maxSpeed = 10
self.minSpeed = -3
self.boundAction = self.WRAP
self.pressed = False
self.oldCenter = (100, 100)

def update(self):
self.oldCenter = self.rect.center
self.checkEvents()
self.__rotate()
self.__calcVector()
self.__calcPosition()
self.checkBounds()
self.rect.center = (self.x, self.y)

def checkEvents(self):
""" overwrite this method to add your own event code """
pass

def __rotate(self):
""" PRIVATE METHOD
change visual orientation based on
rotation property.
automatically called in update.
change rotation property directly or with
rotateBy(), setAngle() methods
"""
oldCenter = self.rect.center
self.oldCenter = oldCenter
self.image = pygame.transform.rotate(self.imageMaster, self.rotation)
self.rect = self.image.get_rect()
self.rect.center = oldCenter

def __calcVector(self):
""" calculates dx and dy based on speed, dir
automatically called in update()
"""
theta = self.dir / 180.0 * math.pi
self.dx = math.cos(theta) * self.speed
self.dy = math.sin(theta) * self.speed
self.dy *= -1

def __calcPosition(self):
""" calculates the sprites position adding
dx and dy to x and y.
automatically called in update()
"""
self.x += self.dx
self.y += self.dy
self.dy += self.gravity

def checkBounds(self):
""" checks boundary and acts based on
self.BoundAction.
WRAP: wrap around screen (default)
BOUNCE: bounce off screen
STOP: stop at edge of screen
HIDE: move off stage and wait
CONTINUE: keep going at present course and speed

automatically called by update()
"""

scrWidth = self.screen.get_width()
scrHeight = self.screen.get_height()

#create variables to simplify checking
offRight = offLeft = offTop = offBottom = offScreen = False

if self.x > scrWidth:
offRight = True
if self.x < 0:
offLeft = True
if self.y > scrHeight:
offBottom = True
if self.y < 0:
offTop = True

if offRight or offLeft or offTop or offBottom:
offScreen = True

if self.boundAction == self.WRAP:
if offRight:
self.x = 0
if offLeft:
self.x = scrWidth
if offBottom:
self.y = 0
if offTop:
self.y = scrHeight

elif self.boundAction == self.BOUNCE:
if offLeft or offRight:
self.dx *= -1
if offTop or offBottom:
self.dy *= -1

self.updateVector()
self.rotation = self.dir

elif self.boundAction == self.STOP:
if offScreen:
self.speed = 0

elif self.boundAction == self.HIDE:
if offScreen:
self.speed = 0
self.setPosition((-1000, -1000))

elif self.boundAction == self.CONTINUE:
pass

else:
# assume it's CONTINUE - keep going forever
pass

def setSpeed(self, speed):
""" immediately sets the objects speed to the
given value.
"""
self.speed = speed

def setGravity(self, amount):
self.gravity = amount

def speedUp(self, amount):
""" changes speed by the given amount
Use a negative value to slow down
"""
self.speed += amount
if self.speed < self.minSpeed:
self.speed = self.minSpeed
if self.speed > self.maxSpeed:
self.speed = self.maxSpeed

def setAngle(self, dir):
""" sets both the direction of motion
and visual rotation to the given angle
If you want to set one or the other,
set them directly. Angle measured in degrees
"""
self.dir = dir
self.rotation = dir

def turnBy (self, amt):
""" turn by given number of degrees. Changes
both motion and visual rotation. Positive is
counter-clockwise, negative is clockwise
"""
self.dir += amt
if self.dir > 360:
self.dir = amt
if self.dir < 0:
self.dir = 360 - amt
self.rotation = self.dir

def rotateBy(self, amt):
""" change visual orientation by given
number of degrees. Does not change direction
of travel.
"""
self.rotation += amt
if self.rotation > 360:
self.rotation = amt
if self.rotation < 0:
self.rotation = 360 - amt

def setImage (self, image):
""" loads the given file name as the master image
default setting should be facing east. Image
will be rotated automatically """
self.imageMaster = pygame.image.load(image)
self.imageMaster = self.imageMaster.convert()

def setDX(self, dx):
""" changes dx value and updates vector """
self.dx = dx
self.updateVector()

def addDX(self, amt):
""" adds amt to dx, updates vector """
self.dx += amt
self.updateVector()

def setDY(self, dy):
""" changes dy value and updates vector """
self.dy = dy
self.updateVector()

def addDY(self, amt):
""" adds amt to dy and updates vector """
self.dy += amt
self.updateVector()

def setComponents(self, components):
""" expects (dx, dy) for components
change speed and angle according to dx, dy values """

(self.dx, self.dy) = components
self.updateVector()

def setBoundAction (self, action):
""" sets action for boundary. Values are
self.WRAP (wrap around edge - default)
self.BOUNCE (bounce off screen changing direction)
self.STOP (stop at edge of screen)
self.HIDE (move off-stage and stop)
self.CONTINUE (move on forever)
Any other value allows the sprite to move on forever
"""
self.boundAction = action

def setPosition (self, position):
""" place the sprite directly at the given position
expects an (x, y) tuple
"""
(self.x, self.y) = position

def moveBy (self, vector):
""" move the sprite by the (dx, dy) values in vector
automatically calls checkBounds. Doesn't change
speed or angle settings.
"""
(dx, dy) = vector
self.x += dx
self.y += dy
self.__checkBounds()

def forward(self, amt):
""" move amt pixels in the current direction
of travel
"""

#calculate dx dy based on current direction
radians = self.dir * math.pi / 180
dx = amt * math.cos(radians)
dy = amt * math.sin(radians) * -1

self.x += dx
self.y += dy

def addForce(self, amt, angle):
""" apply amt of thrust in angle.
change speed and dir accordingly
add a force straight down to simulate gravity
in rotation direction to simulate spacecraft thrust
in dir direction to accelerate forward
at an angle for retro-rockets, etc.
"""

#calculate dx dy based on angle
radians = angle * math.pi / 180
dx = amt * math.cos(radians)
dy = amt * math.sin(radians) * -1

self.dx += dx
self.dy += dy
self.updateVector()

def updateVector(self):
#calculate new speed and angle based on dx, dy
#call this any time you change dx or dy

self.speed = math.sqrt((self.dx * self.dx) + (self.dy * self.dy))

dy = self.dy * -1
dx = self.dx

radians = math.atan2(dy, dx)
self.dir = radians / math.pi * 180

def setSpeedLimits(self, max, min):
""" determines maximum and minimum
speeds you will allow through
speedUp() method. You can still
directly set any speed you want
with setSpeed() Default values:
max: 10
min: -3
"""
self.maxSpeed = max
self.minSpeed = min

def dataTrace(self):
""" utility method for debugging
print major properties
extend to add your own properties
"""
print "x: %d, y: %d, speed: %.2f, dir: %.f, dx: %.2f, dy: %.2f" % \
(self.x, self.y, self.speed, self.dir, self.dx, self.dy)

def mouseDown(self):
""" boolean function. Returns True if the mouse is
clicked over the sprite, False otherwise
"""
self.pressed = False
if pygame.mouse.get_pressed() == (1, 0, 0):
if self.rect.collidepoint(pygame.mouse.get_pos()):
self.pressed = True
return self.pressed

def clicked(self):
""" Boolean function. Returns True only if mouse
is pressed and released over sprite

"""
released = False
if self.pressed:
if pygame.mouse.get_pressed() == (0, 0, 0):
if self.rect.collidepoint(pygame.mouse.get_pos()):
released = True
return released

def collidesWith(self, target):
""" boolean function. Returns True if the sprite
is currently colliding with the target sprite,
False otherwise
"""
collision = False
if self.rect.colliderect(target.rect):
collision = True
return collision

def collidesGroup(self, target):
""" wrapper for pygame.sprite.spritecollideany() function
simplifies checking sprite - group collisions
returns result of collision check (sprite from group
that was hit or None)
"""
collision = pygame.sprite.spritecollideany(self, target)
return collision

def distanceTo(self, point):
""" returns distance to any point in pixels
can be used in circular collision detection
"""
(pointx, pointy) = point
dx = self.x - pointx
dy = self.y - pointy

dist = math.sqrt((dx * dx) + (dy * dy))
return dist

def dirTo(self, point):
""" returns direction (in degrees) to
a point """

(pointx, pointy) = point
dx = self.x - pointx
dy = self.y - pointy
dy *= -1

radians = math.atan2(dy, dx)
dir = radians * 180 / math.pi
dir += 180
return dir

def drawTrace(self, color=(0x00, 0x00, 0x00)):
""" traces a line between previous position
and current position of object
"""
pygame.draw.line(self.scene.background, color, self.oldCenter,
self.rect.center, 3)
self.screen.blit(self.scene.background, (0, 0))

class Scene(object):
""" encapsulates the IDEA / ALTER framework
properties:
sprites - a list of sprite objects
that forms the primary sprite group
background - the background surface
screen - the display screen

it's generally best to add all sprites
as attributes, so they can have access
to each other if needed
"""

def __init__(self):
""" initialize the game engine
set up a sample sprite for testing
"""
pygame.init()
self.screen = pygame.display.set_mode((640, 480))
self.background = pygame.Surface(self.screen.get_size())
self.background.fill((0, 0, 0))

self.sampleSprite = SuperSprite(self)
self.sampleSprite.setSpeed(3)
self.sampleSprite.setAngle(0)
self.sampleSprite.boundAction = self.sampleSprite.WRAP
self.sprites = [self.sampleSprite]
self.groups = []

def start(self):
""" sets up the sprite groups
begins the main loop
"""
self.mainSprites = pygame.sprite.OrderedUpdates(self.sprites)
self.groups.append(self.mainSprites)

self.screen.blit(self.background, (0, 0))
self.clock = pygame.time.Clock()
self.keepGoing = True
while self.keepGoing:
self.__mainLoop()

def stop(self):
"""stops the loop"""
self.keepGoing = False

def __mainLoop(self):
""" manage all the main events
automatically called by start
"""
self.clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.keepGoing = False
self.doEvents(event)

self.update()
for group in self.groups:
group.clear(self.screen, self.background)
group.update()
group.draw(self.screen)

pygame.display.flip()

def makeSpriteGroup(self, sprites):
""" create a group called groupName
containing all the sprites in the sprites
list. This group will be added after the
sprites group, and will automatically
clear, update, and draw
"""
tempGroup = pygame.sprite.OrderedUpdates(sprites)
return tempGroup

def addGroup(self, group):
""" adds a sprite group to the groups list for
automatic processing
"""
self.groups.append(group)

def doEvents(self, event):
""" overwrite this method to add your own events.
Works like normal event handling, passes event
object
"""
pass

def update(self):
""" happens once per frame, after event parsing.
Overwrite to add your own code, esp event handling
that doesn't require event obj. (pygame.key.get_pressed,
pygame.mouse.get_pos, etc)
Also a great place for collision detection
"""
pass

def setCaption(self, title):
""" set's the scene's title text """
pygame.display.set_caption(title)

class Label(pygame.sprite.Sprite):
""" a basic label
properties:
font: font to use
text: text to display
fgColor: foreground color
bgColor: background color
center: position of label's center
size: (width, height) of label
"""

def __init__(self, fontName = "freesansbold.ttf"):
pygame.sprite.Sprite.__init__(self)
self.font = pygame.font.Font(fontName, 20)
self.text = ""
self.fgColor = ((0x00, 0x00, 0x00))
self.bgColor = ((0xFF, 0xFF, 0xFF))
self.center = (100, 100)
self.size = (150, 30)

def update(self):
self.image = pygame.Surface(self.size)
self.image.fill(self.bgColor)
fontSurface = self.font.render(self.text, True, self.fgColor, self.bgColor)
#center the text
xPos = (self.image.get_width() - fontSurface.get_width())/2

self.image.blit(fontSurface, (xPos, 0))
self.rect = self.image.get_rect()
self.rect.center = self.center

class Button(Label):
""" a button based on the label
same properties as label +
active: True if user is clicking on sprite
False if user is not currently clicking
clicked: True when user releases mouse over a
currently active button
"""

def __init__(self):
Label.__init__(self)
self.active = False
self.clicked = False
self.bgColor = (0xCC, 0xCC, 0xCC)

def update(self):
Label.update(self)

self.clicked = False

#check for mouse input
if pygame.mouse.get_pressed() == (1, 0, 0):
if self.rect.collidepoint(pygame.mouse.get_pos()):
self.active = True

#check for mouse release
if self.active == True:
if pygame.mouse.get_pressed() == (0, 0, 0):
self.active = False
if self.rect.collidepoint(pygame.mouse.get_pos()):
self.clicked = True

class Scroller(Button):
""" like a button, but has a numeric value that
can be decremented by clicking on left half
and incremented by clicking on right half.
new atributes:
value: the scroller's numeric value
minValue: minimum value
maxValue: maximum value
increment: How much is added or subtracted
format: format of string interpolation
"""

def __init__(self):
Button.__init__(self)
self.minValue = 0
self.maxValue = 10
self.increment = 1
self.value = 5
self.format = "<< %.2f >>"

def update(self):
Button.update(self)
if self.active:
(mousex, mousey) = pygame.mouse.get_pos()
if mousex < self.rect.centerx:
self.value -= self.increment
if self.value < self.minValue:
self.value = self.minValue
else:
self.value += self.increment
if self.value > self.maxValue:
self.value = self.maxValue

self.text = self.format % self.value

class MultiLabel(pygame.sprite.Sprite):
""" accepts a list of strings, creates a multi-line
label to display text
same properties as label except textLines
is a list of strings. There is no text
property.
Set the size manually. Vertical size should be at
least 30 pixels per line (with the default font)
"""

def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.textLines = ["This", "is", "sample", "text"]
self.font = pygame.font.Font("freesansbold.ttf", 20)
self.fgColor = ((0x00, 0x00, 0x00))
self.bgColor = ((0xFF, 0xFF, 0xFF))
self.center = (100, 100)
self.size = (150, 100)

def update(self):
self.image = pygame.Surface(self.size)
self.image.fill(self.bgColor)
numLines = len(self.textLines)
vSize = self.image.get_height() / numLines

for lineNum in range(numLines):
currentLine = self.textLines[lineNum]
fontSurface = self.font.render(currentLine, True, self.fgColor, self.bgColor)
#center the text
xPos = (self.image.get_width() - fontSurface.get_width())/2
yPos = lineNum * vSize
self.image.blit(fontSurface, (xPos, yPos))

self.rect = self.image.get_rect()
self.rect.center = self.center

if __name__ == "__main__":
# change this code to test various features of the engine
# This code will not run when gameEngine is run as a module
# (as it usually will be

game = Scene()
thing = SuperSprite(game)
thing.setSpeed(5)
thing.setBoundAction(thing.BOUNCE)
thing.setAngle(230)
game.sprites = [thing]

game.start()
[/spoiler]

Please also note, I did not program the Game Engine. I am using a sample Game Engine from the book I am currently teaching myself from. I know this is a lot to go through, and I appreciate any help I can receive! This is my first foray into programming with the assistance of an Engine, and I know that this knowledge will be pivotal in taking my understanding of programming to the next level.

Share this post


Link to post
Share on other sites
Advertisement
After sleeping on it, and looking at it again, I think I've found what the problem with the program is. Mainly, the SuperSprite's update() method contains both __calcVector() and __calcPosition(). In the original program, __calcVector() is only called once at the initialization of the fire() function, while __calcPosition() is called every frame. In this program, both are called every frame making any changes to dy irrelevant as it gets over written when __calcVector() is called again in the next frame.

My new question is, since they're both private functions in update(), is there no way to over write them, meaning the game is impossible to program with this engine, or is there some technique I do not know yet that might help me get the effect I'm looking for?

Share this post


Link to post
Share on other sites
Not sure if any one was following this post, but after three days of staring at my screen, I finally found a solution to the problem.

I wanted to modify the original Engine as little as possible, but I found it impossible to achieve the results I was looking for without it. Essentially, I added another variable cumGrav to track the cumulative effects of gravity during each frame. If a sprite does not check for gravity, it will have no effect. I guess now that I've figured that out, I do have a more general question in regards to Engines:

Is it normal to have to tinker with engine code to get something to work properly?
Was the engine I was working with just extremely narrow in focus and literally made it impossible to properly code my games with it alone?
Am I missing some trick of the trade that gets an engine to do what you want it to when the code in the engine does not?
Finally, are all engines this frustrating to work with compared to working without, or will the more popular engines make coding easier in every regard?

Thank you for any who spent some time pondering my coding dilemma before I found the answer.

Share this post


Link to post
Share on other sites
Loosely speaking, I think you are running up against the issue Write Games, Not Engines. The main thrust of which being that game engines which are built for the sake of building a game engine, should be avoided like the plague.

A mature engine, one that has been used to develop multiple games (that have actually been shipped to paying customers) - those tend to work in fairly sensical ways. The vast majority, however, of so-called 'engines', are shoddily-designed Rube Goldberg machines with little or no relation to how games actually function :)

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!