3
\$\begingroup\$

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,],
        ]
\$\endgroup\$
3
  • 1
    \$\begingroup\$ I have rolled back the last edit. Please see what you may and may not do after receiving answers. \$\endgroup\$
    – Vogel612
    Commented Jun 23, 2016 at 21:56
  • \$\begingroup\$ Can you describe the scrolling behaviour you are trying to implement? Are you trying for behaviour like the original Legend of Zelda, where the world stays put and when the hero walks off one side of screen the world scrolls far enough that he is on the other side of the screen? \$\endgroup\$ Commented Jun 24, 2016 at 17:15
  • \$\begingroup\$ @Gareth Rees, yes, that's exactly the type of camera behavior I am looking for. \$\endgroup\$
    – justthom8
    Commented Jun 25, 2016 at 19:33

1 Answer 1

2
\$\begingroup\$

[…] suggest better ways to structure it so I can handle the increasing if statement complexity.

Some suggestions:

  1. Replace the whole while running loop content with a generic function or method, such as handle_input or Game.handle_input.
  2. Replace long if..else..[…]..elif blocks with chains of maybe_x functions, such as maybe_no_input or maybe_left or ….
  3. Extract everything common to the various movement functions into a separate move function.
\$\endgroup\$
1
  • \$\begingroup\$ Good thinking, that will make it much easier to see where I go wrong when the code isn't working correctly.. I was thinking of doing some kind of dictionary type thing, but even that would be complicated. \$\endgroup\$
    – justthom8
    Commented Jun 23, 2016 at 15:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.