I am working on a simple 2-D game right now in Python. I was wondering if there was a better way than the below to write the game logic to check if the camera is near the edge of the player view, or near the map edge. The logic is getting pretty big, and it's making it much harder to check fringe cases, such as when the character tries to go through one of the corners of the screen rather than just left or right.
I'm not asking for anybody to comb through the code, but to just suggest better ways to structure it so I can handle the increasing if
statement complexity.
Most of the logic I want to look at is in Game.py I have started seperating it out into a camera class. But, it's not complete, and the complex (increasingly) map crossing logic is still there. In fact, I haven't gotten all of the edge cases correct yet, so baring those, I'd like to focus on structure.
Game Loop - Game.py
"""
Author - Thomas Just
Improvements - Seperate constants into a constants file, it's getting kind of large
- Need a way to store the map in a different file, and pre-load it
- Need to improve controls, there's a little bit of funny business sometimes
depending on what order buttons are pushed, the character will stop for a second
- Optimize so only the character is redrawn on the tiles he's currently on, not redraw
the entire screen, too consuming
- start cleaning up code and seperating into classes, including camera, and input code
classes to create: player,time input, camera, and screen. Later on, can add collision
Top priority
- Fix code near left and up boundary, current != 0 isn't allowing sprite to move up or left
- There is some odd behavior near the edges of the screen, including bouncing of sprite
and when sprite is going in two directions at once, the screen occasionally shifts to another screen
"""
import pygame, sys
import os
from os.path import dirname, realpath, abspath
from pygame.locals import *
from Maps.TileMap import *
from Maps.Map import *
from INIT import *
from Input import *
from Player import *
theClock = pygame.time.Clock()
localMapWidth = LOCALTILENUMBERX* TILESIZEX
localMapHeight = LOCALTILENUMBERY * TILESIZEY
SPRITEX = 40
SPRITEY = 40
WORLDMAPWIDTH = MAPWIDTH*TILESIZEX
WORLDMAPHEIGHT = MAPHEIGHT*TILESIZEY
running = True
keystates={'up':False, 'down':False, 'left':False, 'right':False}
#screen = pygame.display.set_mode((200,200))
screen = pygame.display.set_mode((localMapWidth, localMapHeight))
index = 0
map = Map()
tilemap = map.loadMap(tiles)
motion = 0
pygame.init()
down = False
doNotDrawMe = 0
KeyStatesFalse = 0
keyboardInput = Input()
keystates =keyboardInput.update()
__file__ = "C:\\Users\\ASUS1\\Documents\\MyRepo\\Sandbox\\Python\\Learning\\Zelda_Rip_Off\\Sprites"
playerPath = os.path.join(dirname(__file__),"Sprites", "Zelda_Front_Left.png")
player = Player(WHITE, playerPath)
count = 0
while running:
globalX = cameraX + player.player_rect.x
globalY = cameraY + player.player_rect.y
#print("globalY = ", globalY, "cameraY = ", cameraY, "maxMapY = ", maxMapY, "player.player_rect.y = ", player.player_rect.y, "countY = ", countY, "\n")
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
down = event.type == pygame.KEYDOWN
keystates = keyboardInput.update()
player.update(keyboardInput)
#print("cameraX = ", cameraX)
#Solved map drawing issue
#Use camera units to determine which local camera matrix
#to use to draw the scene, alternatingly increasing map in x direction
#requires adding cameraYunits, and increases in y direction
#requires adding cameraXunits
if not down:
if (count <60 ):
count = count + 1;
for column in range(0,LOCALTILENUMBERX):
for row in range(0,LOCALTILENUMBERY):
screen.blit(map.textures[tilemap[int(cameraY/TILESIZEY)+column][int(cameraX/TILESIZEX)+row]],(row*TILESIZEX, column*TILESIZEY))
screen.blit(player.image, (player.player_rect.x, player.player_rect.y))
#print("player.player_rect.x = ", player.player_rect.x, "player.player_rect.y = ", player.player_rect.y)
elif (down):
count = 0
#print("Yes yes yes yes\n")
doNotDrawMe = 0
#Is the player trying to go left
if keystates['left']:
#Are they near the edge of camera?
print("Entered Left\n")
if(player.player_rect.x <= minlocalX and globalX > minMapX and countX > 0):
#Go ahead and move the camera left
print("Entered edge of camera\n")
cameraX -= TILESIZEX
for column in range(0,LOCALTILENUMBERX):
for row in range(0,LOCALTILENUMBERY):
screen.blit(map.textures[tilemap[int(cameraY/TILESIZEY)+column][int(cameraX/TILESIZEX)+row]],(row*TILESIZEX, column*TILESIZEY))
screen.blit(player.image, (maxlocalX, player.player_rect.y))
player.player_rect.x = maxlocalX
if(screensX > 0):
countX -= 1
#if they are not near the edge of the camera
elif(player.player_rect.x > minlocalX):
print("Entered go left, not near left edge\n")
#player_rect.x -= 1
for column in range(0,LOCALTILENUMBERX):
for row in range(0,LOCALTILENUMBERY):
screen.blit(map.textures[tilemap[int(cameraY/TILESIZEY)+column][int(cameraX/TILESIZEX)+row]],(row*TILESIZEX, column*TILESIZEY))
screen.blit(player.image, (player.player_rect.x, player.player_rect.y))
elif(globalX <= minMapX):
print("Don't move player left, reached map edge\n")
player.player_rect.x = minlocalX
else:
print("You have reached error state: LEFT")
elif keystates['up']:
#Are they near the edge of camera?
print("Entered up key if\n")
if(player.player_rect.y <= minlocalY and globalY > minMapY and countY > 0):
#Go ahead and move the camera up
print("Entered up edge local screen, move screen up\n")
cameraY -= 1*TILESIZEY
#cameraY -= 1*TILESIZEY
for column in range(0,LOCALTILENUMBERX):
for row in range(0,LOCALTILENUMBERY):
screen.blit(map.textures[tilemap[int(cameraY/TILESIZEY)+column][int(cameraX/TILESIZEX)+row]],(row*TILESIZEX, column*TILESIZEY))
screen.blit(player.image, (player.player_rect.x, maxlocalY))
player.player_rect.y = maxlocalY
if(countY > 0):
countY -= 1
#if they are not near the edge of the camera
elif(player.player_rect.y > minlocalY):
print("Entered key up, not near edge\n")
#player_rect.x += 1
for column in range(0,LOCALTILENUMBERX):
for row in range(0,LOCALTILENUMBERY):
screen.blit(map.textures[tilemap[int(cameraY/TILESIZEY)+column][int(cameraX/TILESIZEX)+row]],(row*TILESIZEX, column*TILESIZEY))
screen.blit(player.image, (player.player_rect.x, player.player_rect.y))
elif(globalY <= minMapY):
print("Entered y edge of map, stay put\n")
player.player_rect.y = minlocalY
else:
print("You have reached error state: UP")
elif keystates['down']:
print("Entered down key state\n")
#Are they near the edge of camera?
if(player.player_rect.y >= maxlocalY and globalY < maxMapY and countY < screensY):
#Go ahead and move the camera down
print("Entered move camera down\n")
cameraY += 1*TILESIZEY
for column in range(0,LOCALTILENUMBERX):
for row in range(0,LOCALTILENUMBERY):
screen.blit(map.textures[tilemap[int(cameraY/TILESIZEY)+column][int(cameraX/TILESIZEX)+row]],(row*TILESIZEX, column*TILESIZEY))
player.player_rect.y = minlocalY
if(countY < screensY):
countY +=1
#if they are not near the edge of the camera
elif(player.player_rect.y < maxlocalY):
#player_rect.y -= 1
print("Entered down, not near camera edge, go ahead and move\n")
for column in range(0,LOCALTILENUMBERX):
for row in range(0,LOCALTILENUMBERY):
screen.blit(map.textures[tilemap[int(cameraY/TILESIZEY)+column][int(cameraX/TILESIZEX)+row]],(row*TILESIZEX, column*TILESIZEY))
#print("motion", motion)
screen.blit(player.image, (player.player_rect.x, player.player_rect.y))
elif(globalY >= maxMapY):
print("Entered down, edge of map, don't move")
print("player.player_rect.x = ", player.player_rect.x, "player.player_rect.y = ", player.player_rect.y)
#print("Glonal max!")
player.player_rect.y = maxlocalY
else:
print("globalY = ", globalY, "cameraY = ", cameraY, "maxMapY = ", maxMapY, "player.player_rect.y = ", player.player_rect.y, "countY = ", countY, "\n")
print("You have reached error state: DOWN")
elif keystates['right']:
#Are they near the edge of camera?
print("Entered right key state\n")
#print("globalX = ", globalX, "maxlocalX = ", maxlocalX, "player_rect.x = ", player_rect.x)
if(player.player_rect.x >= maxlocalX and globalX < maxMapX and countX < screensX):
print("Entering, edge of camera right, move camera right\n")
#print(screensX)
#print("-----------------Entered-----------")
#print("globalX = ", globalX, "maxlocalX = ", maxlocalX, "player_rect.x = ", player_rect.x)
#Go ahead and move the camera right
cameraX += 1*TILESIZEX
for column in range(0,LOCALTILENUMBERY):
for row in range(0,LOCALTILENUMBERX):
# print(int(cameraY/TILESIZEY)+column)
screen.blit(map.textures[tilemap[int(cameraY/TILESIZEY)+column][int(cameraX/TILESIZEX)+row]],(row*TILESIZEX, column*TILESIZEY))
screen.blit(player.image, (minlocalX, player.player_rect.y))
player.player_rect.x = minlocalX
if (countX < screensX):
countX += 1
#if they are not near the edge of the camera
elif(player.player_rect.x < maxlocalX):
#player_rect.x -= 1
#print("Here")
print("Entering, not edge of right camera, do normal stuff\n")
for column in range(0,LOCALTILENUMBERY):
for row in range(0,LOCALTILENUMBERX):
screen.blit(map.textures[tilemap[int(cameraY/TILESIZEY)+column][int(cameraX/TILESIZEX)+row]],(row*TILESIZEX, column*TILESIZEY))
screen.blit(player.image, (player.player_rect.x, player.player_rect.y))
elif(globalX >= maxMapX):
player.player_rect.x = maxlocalX
else:
print("You have reached error state: RIGHT")
#print("player_rect.x = ", player_rect.x, "player_rect.y = ", player_rect.y)
pygame.display.flip()
pygame.display.update()
theClock.tick(60)
Map Creation - TileMap.py
import pygame
import os
from os.path import dirname, realpath, abspath
class Map():
"""
class: TileMap()
Inherits: None
Purpose: Allow user to load any sized map
"""
def __init__(self):
self.tilemap = []
self.DIRT = 0
self.GRASS = 1
self.WATER = 2
self.COAL = 3
__file__ = "C:\\Users\\ASUS1\\Documents\\MyRepo\\Sandbox\\Python\\Learning\\Zelda_Rip_Off\\Textures"
filepath1 = os.path.join(dirname(__file__),"Textures", "dirt.png")
filepath2 = os.path.join(dirname(__file__),"Textures", "grass.png")
filepath3 = os.path.join(dirname(__file__),"Textures", "water.png")
filepath4 = os.path.join(dirname(__file__),"Textures", "coal.png")
self.textures = {self.DIRT: pygame.image.load(filepath1),
self.GRASS: pygame.image.load(filepath2),
self.WATER: pygame.image.load(filepath3),
self.COAL: pygame.image.load(filepath4)}
def loadMap(self,obj):
"""
Function - loadMap(obj)
Purpose: Loads user defined map
Input: obj - should be a list inside a list (matrix)
which will be converted to the association
in using definitions in the init function
obj is a square n x n matrix
"""
list = []
count = 0
k = 0
for i in range(0,len(obj)):
count = count + 1
for j in range(0,len(obj[0]) ):
if (obj[i][j] == 0):
list.append(self.DIRT)
elif (obj[i][j] == 1):
list.append(self.GRASS)
elif (obj[i][j] == 2):
list.append(self.WATER)
elif (obj[i][j] == 3):
list.append(self.COAL)
k = k + 1
self.tilemap.append(list)
list = []
print("count = ",count, "k = ", len(self.tilemap))
return self.tilemap
def MapLength(obj):
"""
Gives you the size of the map in a tuple, n x n, in the number of tiles
The map assumes it is square
"""
if (not tilemap):
return (0,0)
else:
return (len(obj),len(obj[0]))
Player Class - Player.py
import pygame
import math
class Player(object):
def __init__(self, color, path):
self.SPRITEX = 40
self.SPRITEY = 40
#self.cCamera = Camera(localMapWidth, localMapHeight)
self.image = pygame.image.load(path).convert()
self.image.set_colorkey(color)
self.player_rect = self.image.get_rect()
self.player_rect.x = 0
self.player_rect.y = 0
self.speed = 3
def update(self, InputState):
keystates = InputState.update()
if keystates['right'] and keystates['up']:
self.player_rect.x = self.player_rect.x+ 1/math.sqrt(self.player_rect.x*self.player_rect.x + self.speed*self.speed)
self.player_rect.y = self.player_rect.y- 1/math.sqrt(self.player_rect.y*self.player_rect.y + self.speed*self.speed)
#print(math.sqrt(self.player_rect.x*self.player_rect.x + self.player_rect.y*self.player_rect.y))
elif keystates['right'] and keystates['down']:
self.player_rect.x = self.player_rect.x+ 1/math.sqrt(self.player_rect.x*self.player_rect.x + self.speed*self.speed)
self.player_rect.y = self.player_rect.y+ 1/math.sqrt(self.player_rect.y*self.player_rect.y + self.speed*self.speed)
elif keystates['left'] and keystates['down']:
self.player_rect.x = self.player_rect.x- 1/math.sqrt(self.player_rect.x*self.player_rect.x + self.speed*self.speed)
self.player_rect.y = self.player_rect.y+ 1/math.sqrt(self.player_rect.y*self.player_rect.y + self.speed*self.speed)
elif keystates['left'] and keystates['up']:
self.player_rect.x = self.player_rect.x- 1/math.sqrt(self.player_rect.x*self.player_rect.x + self.speed*self.speed)
self.player_rect.y = self.player_rect.y- 1/math.sqrt(self.player_rect.y*self.player_rect.y + self.speed*self.speed)
if keystates['right']:
self.player_rect.x += self.speed
if keystates['left']:
self.player_rect.x -= self.speed
if keystates['up']:
self.player_rect.y -= self.speed
if keystates['down']:
self.player_rect.y += self.speed
INIT File - INIT.py
#INITIALIZATION
BLACK = (0, 0, 0)
BROWN = (153, 76, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
WHITE = (255, 255, 255)
DIRT = 0
GRASS = 1
WATER = 2
COAL = 3
cameraX = 0 #moved to camera class
cameraY = 0
TILESIZEX = 100
TILESIZEY = 100
MAPWIDTH = 10#6
MAPHEIGHT = 6#7
countX = 0 #moved to camera class
countY = 0
#Sprite buffer for camera
LOCALTILENUMBERX = 3
LOCALTILENUMBERY = 3
localMapWidth = LOCALTILENUMBERX* TILESIZEX
localMapHeight = LOCALTILENUMBERY * TILESIZEY
WORLDMAPWIDTH = MAPWIDTH*TILESIZEX
WORLDMAPHEIGHT = MAPHEIGHT*TILESIZEY
minlocalX = 0 #moved to camera class
minlocalY = 0
minMapX = 50 #Minimum map distance with sprite buffer
minMapY = 50
maxlocalX = LOCALTILENUMBERX*TILESIZEX - minMapX
maxlocalY = LOCALTILENUMBERY*TILESIZEY - minMapY
maxMapX = MAPWIDTH*TILESIZEX - minMapX
maxMapY = MAPHEIGHT*TILESIZEY - minMapY
step = 1 #number of steps the camera will move when player reaches screen edge
screensX = int((maxMapX - maxlocalX)/(TILESIZEX*step)) #The number of x direction screens the player can transverse before being at the edge of the map
screensY = int((maxMapY - maxlocalY)/(TILESIZEY*step)) #The number of screens the player can transverse before being at the edge of the map
# #Global Coordinates
globalX = 0
globalY = 0
# #Local coordinates
# localX = 0
# localY = 0
maxLocalX = LOCALTILENUMBERX*TILESIZEX
maxLocalY = LOCALTILENUMBERY*TILESIZEY
Input Class - Input.py
import pygame
class Input(object):
"""
Class - Input()
Summary: Responsible for grouping key inputs
and updating the game loop with pressed keys
Limitations: Currently only checks keynoard up,down, right, and left
Future Improvements: Add other keyboard key listening logic, and allow player
to map the keys to their own liking, but have a default
key mapping
"""
def __init__(self):
self.keystates={'up':False, 'down':False, 'left':False, 'right':False}
def update(self):
currently_pressed = pygame.key.get_pressed()
#print(currently_pressed)
if currently_pressed[pygame.K_UP]:
self.keystates['up'] = True
else:
self.keystates['up'] = False
if currently_pressed[pygame.K_DOWN]:
self.keystates['down'] = True
else:
self.keystates['down'] = False
if currently_pressed[pygame.K_LEFT]:
self.keystates['left'] = True
else:
self.keystates['left'] = False
if currently_pressed[pygame.K_RIGHT]:
self.keystates['right'] = True
# print("Got here")
else:
self.keystates['right'] = False
return self.keystates
Actual Map - Map.py
tiles = [
[1, 3, 0, 1, 0, 0, 1, 3, 2, 1,],
[2, 2, 1, 1, 2, 0, 1, 3, 2, 2,],
[3, 1, 2, 2, 2, 1, 3, 2, 2, 3,],
[1, 2, 0, 1, 3, 0, 1, 3, 3, 2,],
[3, 1, 2, 2, 2, 0, 3, 3, 2, 3,],
[1, 2, 0, 1, 1, 0, 0, 3, 1, 1,],
]