class _Physics(object):
"""
A simplified physics class. Using a 'real' gravity function here, though
it is questionable whether or not it is worth the effort. Compare to the
effect of gravity in fall_rect and decide for yourself.
"""
def init(self):
"""You can experiment with different gravity here."""
self.x_vel = self.y_vel = self.y_vel_i = 0
self.grav = 20
self.fall = False
self.time = None
def physics_update(self):
"""If the player is falling, calculate current y velocity."""
if self.fall:
time_now = pg.time.get_ticks()
if not self.time:
self.time = time_now
self.y_vel = self.grav*((time_now-self.time)/1000.0)+self.y_vel_i
else:
self.time = None
self.y_vel = self.y_vel_i = 0
class Player(_Physics, pg.sprite.Sprite):
"""Class representing our player."""
def init(self,location,speed):
"""
The location is an (x,y) coordinate pair, and speed is the player's
speed in pixels per frame. Speed should be an integer.
"""
_Physics.init(self)
pg.sprite.Sprite.init(self)
self.image = PLAYER_IMAGE
self.mask = pg.mask.from_surface(self.image)
self.speed = speed
self.jump_power = 10
self.rect = self.image.get_rect(topleft=location)
def get_position(self, obstacles):
"""Calculate the player's position this frame, including collisions."""
if not self.fall:
self.check_falling(obstacles)
else:
self.fall = self.check_collisions((0,self.y_vel), 1, obstacles)
if self.x_vel:
self.check_collisions((self.x_vel,0), 0, obstacles)
def check_falling(self, obstacles):
"""If player is not contacting the ground, enter fall state."""
self.rect.move_ip((0,1))
collisions = pg.sprite.spritecollide(self, obstacles, False)
collidable = pg.sprite.collide_mask
if not pg.sprite.spritecollideany(self, collisions, collidable):
self.fall = True
self.rect.move_ip((0,-1))
def check_collisions(self, offset, index, obstacles):
"""
This function checks if a collision would occur after moving offset
pixels. If a collision is detected position is decremented by one
pixel and retested. This continues until we find exactly how far we can
safely move, or we decide we can't move.
"""
unaltered = True
self.rect.move_ip(offset)
collisions = pg.sprite.spritecollide(self, obstacles, False)
collidable = pg.sprite.collide_mask
while pg.sprite.spritecollideany(self, collisions, collidable):
self.rect[index] += (1 if offset[index]<0 else -1)
unaltered = False
return unaltered
def check_keys(self, keys):
"""Find the player's self.x_vel based on currently held keys."""
self.x_vel = 0
if keys[pg.K_LEFT] or keys[pg.K_a]:
self.x_vel -= self.speed
if keys[pg.K_RIGHT] or keys[pg.K_d]:
self.x_vel += self.speed
def jump(self):
"""Called when the user presses the jump button."""
if not self.fall:
self.y_vel_i = -self.jump_power
self.fall = True
def update(self, obstacles, keys):
"""Everything we need to stay updated."""
self.check_keys(keys)
self.get_position(obstacles)
self.physics_update()
def draw(self,surface):
"""Blit the player to the target surface."""
surface.blit(self.image, self.rect)
class Block(pg.sprite.Sprite):
"""A class representing solid obstacles."""
def init(self, location):
"""The location argument is an (x,y) coordinate pair."""
pg.sprite.Sprite.init(self)
self.make_image()
self.mask = pg.mask.from_surface(self.image)
self.rect = pg.Rect(location, (50,50))
def make_image(self):
"""Something pretty to look at."""
color = [random.randint(0,255) for _ in range(3)]
self.image = pg.Surface((50,50)).convert_alpha()
self.image.fill(color)
self.image.blit(SHADE_IMG, (0,0))
class Control(object):
"""Class for managing event loop and game states."""
def init(self):
"""Nothing to see here folks. Move along."""
self.screen = pg.display.get_surface()
self.clock = pg.time.Clock()
self.fps = 60.0
self.keys = pg.key.get_pressed()
self.done = False
self.player = Player((50,-25), 4)
self.obstacles = self.make_obstacles()
def make_obstacles(self):
"""Adds some arbitrarily placed obstacles to a sprite.Group."""
obstacles = [Block((400,400)), Block((300,270)), Block((150,170))]
obstacles += [Block((500+50*i,220)) for i in range(3)]
for i in range(12):
obstacles.append(Block((50+i*50,450)))
obstacles.append(Block((100+i*50,0)))
obstacles.append(Block((0,50*i)))
obstacles.append(Block((650,50*i)))
return pg.sprite.Group(obstacles)
def event_loop(self):
"""We can always quit, and the player can sometimes jump."""
for event in pg.event.get():
if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]:
self.done = True
elif event.type == pg.KEYDOWN:
if event.key == pg.K_SPACE:
self.player.jump()
def update(self):
"""Update held keys and the player."""
self.keys = pg.key.get_pressed()
self.player.update(self.obstacles, self.keys)
def draw(self):
"""Draw all necessary objects to the display surface."""
self.screen.fill(BACKGROUND_COLOR)
self.obstacles.draw(self.screen)
self.player.draw(self.screen)
def display_fps(self):
"""Show the programs FPS in the window handle."""
caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps())
pg.display.set_caption(caption)
def main_loop(self):
"""As simple as it gets."""
while not self.done:
self.event_loop()
self.update()
self.draw()
pg.display.update()
self.clock.tick(self.fps)
self.display_fps()
import os import sys import random import pygame as pg
CAPTION = "Basic Platforming: Pixel Perfect Collision" SCREEN_SIZE = (700, 500) BACKGROUND_COLOR = (50, 50, 50)
class _Physics(object): """ A simplified physics class. Using a 'real' gravity function here, though it is questionable whether or not it is worth the effort. Compare to the effect of gravity in fall_rect and decide for yourself. """ def init(self): """You can experiment with different gravity here.""" self.x_vel = self.y_vel = self.y_vel_i = 0 self.grav = 20 self.fall = False self.time = None
class Player(_Physics, pg.sprite.Sprite): """Class representing our player.""" def init(self,location,speed): """ The location is an (x,y) coordinate pair, and speed is the player's speed in pixels per frame. Speed should be an integer. """ _Physics.init(self) pg.sprite.Sprite.init(self) self.image = PLAYER_IMAGE self.mask = pg.mask.from_surface(self.image) self.speed = speed self.jump_power = 10 self.rect = self.image.get_rect(topleft=location)
class Block(pg.sprite.Sprite): """A class representing solid obstacles.""" def init(self, location): """The location argument is an (x,y) coordinate pair.""" pg.sprite.Sprite.init(self) self.make_image() self.mask = pg.mask.from_surface(self.image) self.rect = pg.Rect(location, (50,50))
class Control(object): """Class for managing event loop and game states.""" def init(self): """Nothing to see here folks. Move along.""" self.screen = pg.display.get_surface() self.clock = pg.time.Clock() self.fps = 60.0 self.keys = pg.key.get_pressed() self.done = False self.player = Player((50,-25), 4) self.obstacles = self.make_obstacles()
if name == "main": os.environ['SDL_VIDEO_CENTERED'] = '1' pg.init() pg.display.set_caption(CAPTION) pg.display.set_mode(SCREEN_SIZE) PLAYER_IMAGE = pg.image.load("smallface.png").convert_alpha() SHADE_IMG = pg.image.load("shader.png").convert_alpha() run_it = Control() run_it.main_loop() pg.quit() sys.exit()