#!/usr/bin/python """ Squirrelly Movement Demo by Kris Schnee. Meant to represent a side-scrolling action game character that can run and jump around in a fun, agile way. I got as far as collision detection, dashing, sliding on ice, jumping, and a questionable form of wall-jumping (bouncing off walls). Controls: Arrows move, space jumps, hold left-shift to dash. """ ##___________________________________ ## ~Header~ ##___________________________________ __author__ = "Kris Schnee" __version__ = "2010.1.23" __license__ = "Public Domain" ##___________________________________ ## ~Imported Modules~ ##___________________________________ ## Standard import time ## Third-party import pygame from pygame.locals import * ## Mine ##___________________________________ ## ~Constants~ ##___________________________________ SCREEN_SIZE = (800,400) screen = pygame.display.set_mode(SCREEN_SIZE) pygame.display.set_caption("Squirrelly, Squirrelly Movement Demo") TILE_SIZE = (20,20) TILE_HALFSIZE = (10,10) PLAYER_SIZE = (20,40) PLAYER_DRAW_OFFSET = (-10,-40) FALL_SPEED = .1 JUMP_SPEED = -0.8 TERMINAL_VELOCITY = 0.5 RUN_SPEED = .3 MIN_SPEED = .1 WALL_JUMP_AIR_CONTROL_BLOCKED = 5 WALL_JUMP_SPEED = RUN_SPEED * 2.0 K_DASH = K_LSHIFT ## Slipping = 0.0 to 1.0 LOAD_LEVEL_GUIDE = {" ":"empty", ".":"block", "i":"ice", } TERRAINS = {"block":{"slipping":.5}, "ice":{"slipping":.9}, } TERRAINS_SOLID = ["block","ice"] TERRAIN_EMPTY = "empty" ##___________________________________ ## ~Classes~ ##___________________________________ class Squirrel: def __init__(self,**options): self.coords = list(options.get("coords",[0,0])) self.speed_x = 0.0 self.speed_y = 0.0 self.size_x = 1.0 self.size_y = 2.0 self.half_x = self.size_x / 2.0 self.half_y = self.size_y / 2.0 self.dash = False self.jumping = False self.air_control_disabled = 0 ## Number of frames left till restored class Game: def __init__(self): self.frames = 0 self.start_time = time.time() self.autoquit_time = self.start_time + 5.0 ## Test self.done = False ## Load sample level f = open("level.txt","r") text = f.read().replace("\n","") f.close() self.size = (40,20) sx, sy = self.size self.terrain = [] for x in range(sx): self.terrain.append([]) for y in range(self.size[1]): self.terrain[-1].append( LOAD_LEVEL_GUIDE[ text[(sx*y)+x] ] ) self.tiles = {} ## Improvised graphics block = pygame.surface.Surface((20,20)) block.fill((32,64,192)) self.tiles["block"] = block blank = pygame.surface.Surface((20,20)) self.tiles["empty"] = blank ice = pygame.surface.Surface((20,20)) ice.fill((200,200,255)) self.tiles["ice"] = ice player = pygame.surface.Surface(PLAYER_SIZE) player.fill((0,255,0)) self.tiles["p"] = player ## Player object self.player = Squirrel(coords=(3.0,9.0)) ## Junky physics test self.jumped_already = False ## self.player.speed_y = .1 def Draw(self): sx, sy = TILE_SIZE for y in range(self.size[1]): for x in range(self.size[0]): t = self.tiles[ self.terrain[x][y] ] screen.blit(t,(sx*x,sy*y)) pc = self.player.coords player_draw_coords = (sx*pc[0])+PLAYER_DRAW_OFFSET[0], (sy*pc[1])+PLAYER_DRAW_OFFSET[1] screen.blit(self.tiles["p"],player_draw_coords) pygame.display.update() def CheckBlocked(self,x,y): """Is this block occupied?""" return self.terrain[ int(x) ][ int(y) ] in TERRAINS_SOLID def Logic(self): ## PHYSICS ## ## Movement new_x = self.player.coords[0]+self.player.speed_x new_y = self.player.coords[1]+self.player.speed_y ## Needed if terminal velocity is > 1 block, so that player can't fall through thin floors! ## fell_through_y = range(int(self.player.last_coords[1]),int(self.player.coords[1])) ## print fell_through_y ## New physics attempt: ## Check blocks surrounding player. player_on_ground = False ## Left: left_x = new_x-self.player.half_x left_y1 = new_y-self.player.size_y left_y2 = new_y-self.player.half_y left_y3 = new_y left_blocked = self.CheckBlocked(left_x,left_y1) or self.CheckBlocked(left_x,left_y2) or self.CheckBlocked(left_x,left_y3) ## print "Left: "+str(left_x)+", blocked: "+str(left_blocked) ## Right: right_x = new_x+self.player.half_x right_y1 = new_y-self.player.size_y right_y2 = new_y-self.player.half_y right_y3 = new_y right_blocked = self.CheckBlocked(right_x,right_y1) or self.CheckBlocked(right_x,right_y2) or self.CheckBlocked(right_x,right_y3) ## print "Right: "+str(right_x)+", blocked: "+str(right_blocked) if left_blocked: self.player.speed_x = 0.0 new_x = self.player.coords[0] ## print "left blocked" if right_blocked: self.player.speed_x = 0.0 new_x = self.player.coords[0] ## print "right blocked" ## Top: top_x1 = new_x - self.player.half_x top_x2 = new_x top_x3 = new_x + self.player.half_x top_y = new_y-self.player.size_y top_blocked = self.CheckBlocked(top_x1,top_y) or self.CheckBlocked(top_x2,top_y) or self.CheckBlocked(top_x3,top_y) ## if top_blocked: ## print "top blocked" ## print top_y, self.player.coords ## Bottom: bottom_x1 = new_x - self.player.half_x bottom_x2 = new_x bottom_x3 = new_x + self.player.half_x bottom_y = new_y + .01 bottom_blocked = self.CheckBlocked(bottom_x1, bottom_y) or self.CheckBlocked(bottom_x2, bottom_y) or self.CheckBlocked(bottom_x3, bottom_y) if bottom_blocked: ## print "Bottom is blocked. Coords: "+str((new_x,new_y))+". Setting..." player_on_ground = True self.player.jumping = False ## print "Player is on ground." ## What's the player standing on? ground = self.terrain[int(new_x)][int(bottom_y)] if ground == TERRAIN_EMPTY: ## Check other spots to allow for a toehold on nearby blocks. ground = self.terrain[int(bottom_x1)][int(bottom_y)] if ground == TERRAIN_EMPTY: ground = self.terrain[int(bottom_x3)][int(bottom_y)] if ground == TERRAIN_EMPTY: raise SystemError("Player seems to be standing solidly on nothing.") ## print "Ground: "+str(ground) ## self.player.coords[1] = self.player.last_coords[1] new_y = int(new_y+.01)-.01 ## print "...to new y: "+str(new_y) ## print "New Y: "+str(self.player.coords[1]) self.player.speed_y = 0.0 else: ## Gravity. self.player.speed_y = min(self.player.speed_y+FALL_SPEED,TERMINAL_VELOCITY) ## print "Fall: "+str(self.player.speed_y) if top_blocked: self.player.speed_y = 0.0 new_y = self.player.coords[1] ## ## Stuck in something? Undo. ## ## Assume player is 1.0 units wide. ## bounding_x1 = int(self.player.coords[0] - .5) ## bounding_x2 = int(self.player.coords[0] + .5) ## left_block = self.terrain[bounding_x1][int(self.player.coords[1])-1] ## if left_block in TERRAINS_SOLID: ## left_wall = True ## self.player.speed_x = 0.0 ## self.player.coords[0] = self.player.last_coords[0] ## else: ## right_block = self.terrain[bounding_x2][int(self.player.coords[1])-1] ## if right_block in TERRAINS_SOLID: ## right_wall = True ## self.player.speed_x = 0.0 ## self.player.coords[0] = self.player.last_coords[0] #### above_block = self.terrain[int(self.player.coords[0])][int(self.player.coords[1])-1] ## above_block = self.terrain[int(self.player.coords[0])][int(self.player.coords[1])-2] ## if above_block in TERRAINS_SOLID: ## self.player.speed_y = 0.0 ## self.player.coords[1] = self.player.last_coords[1] ## ## ## Falling ## pc = self.player.coords #### print pc ## ground = self.terrain[int(pc[0])][int(pc[1])] ## player_on_ground = ground in TERRAINS_SOLID ## Assumes never on bottom row #### print player_on_ground ## if player_on_ground: ## self.player.speed_y = 0.0 ## self.player.coords[1] = int(self.player.coords[1]) - .001 ## else: ## self.player.speed_y = min(self.player.speed_y+FALL_SPEED,TERMINAL_VELOCITY) ## CONTROLS ## m_left = False m_right = False m_up = False m_down = False m_jump = False keys_down = pygame.key.get_pressed() if keys_down[K_ESCAPE]: self.done = True if keys_down[K_RIGHT]: m_right = True self.player.dash = keys_down[K_DASH] if keys_down[K_LEFT]: m_left = True if keys_down[K_SPACE]: m_jump = True ## ## Test: Force input. ## m_right = True ## if player_on_ground and not self.jumped_already: ## m_jump = True ## self.jumped_already = True ## Act on input. if m_jump: if player_on_ground: self.player.speed_y = JUMP_SPEED self.player.jumping = True else: if left_blocked and not top_blocked and self.CheckBlocked(left_x,left_y1): self.player.speed_y = JUMP_SPEED self.player.speed_x = WALL_JUMP_SPEED self.player.jumping = True self.player.air_control_disabled = WALL_JUMP_AIR_CONTROL_BLOCKED elif right_blocked and not top_blocked and self.CheckBlocked(right_x,right_y1): self.player.speed_y = JUMP_SPEED self.player.speed_x = -WALL_JUMP_SPEED self.player.jumping = True self.player.air_control_disabled = WALL_JUMP_AIR_CONTROL_BLOCKED if player_on_ground or not self.player.air_control_disabled: if m_left: self.player.speed_x = -RUN_SPEED if self.player.dash: self.player.speed_x *= 1.5 elif m_right: self.player.speed_x = RUN_SPEED if self.player.dash: self.player.speed_x *= 1.5 else: if player_on_ground: ## Friction self.player.speed_x *= TERRAINS[ground]["slipping"] if abs(self.player.speed_x) < MIN_SPEED: self.player.speed_x = 0.0 ## Count down air-control blockage. if self.player.air_control_disabled: self.player.air_control_disabled -= 1 ## Set the player's coordinates now. ## print "Old & new coords: "+str(self.player.coords)+", "+str((new_x,new_y)) self.player.coords = (new_x,new_y) ## ## Test: Time limit. ## if time.time() > self.autoquit_time: ## self.done = True ## Pump events to clear input. pygame.event.pump() def MainLoop(self): self.done = False while not self.done: self.Draw() self.Logic() self.frames += 1 time_elapsed = time.time() - self.start_time print "FPS: "+str(self.frames / time_elapsed) ##___________________________________ ## ~Functions~ ##___________________________________ ##__________________________________ ## ~Autorun~ ##__________________________________ if __name__ == "__main__": g = Game() g.MainLoop()