PyGame - functions in other files are not defined

37 views Asked by At

Been making my first PyGame project and previously after clicking on "start game" button it would take user to the gameplay window, whereas now it claims gameplay is undefined. I have tried debugging but am relatively new to pygame, could somebody explain a solution to this issue? thanks. Below is the code:

# main.py

import pygame
import sys

from gameplay import *
pygame.init()

#CONSTANTS
screen_width = 800
screen_height = 800
running = True
sky_blue = (102, 230, 225)
white = (255, 255, 255)
black = (0, 0, 0)
blue = (0, 0, 255)
red = (255, 0, 0)
green = (0, 255, 0)
screen = pygame.display.set_mode((screen_height,screen_width))


############# SOURCED FROM: stackoverflow (https://stackoverflow.com/questions/47855725/pygame-how-can-i-allow-my-users-to-change-their-input-keys-custom-keybinding)
def create_key_list(input_map):
  """A list of surfaces of the action names + assigned keys, rects and the actions."""
  font = pygame.font.Font(None, 50)
  key_list = []
  for y, (action, value) in enumerate(input_map.items()):
    surf = font.render('{}: {}'.format(action, pygame.key.name(value)), True, red)
    rect = surf.get_rect(topleft=(40, y*40+20))
    key_list.append([surf, rect, action])
  return key_list


def assignment_menu(input_map):
  """Allow the user to change the key assignments in this menu.

  The user can click on an action-key pair to select it and has to press
  a keyboard key to assign it to the action in the `input_map` dict.
  """
  selected_action = None
  key_list = create_key_list(input_map)
  while True:
    for event in pygame.event.get():
      if event.type == pygame.QUIT:
        pygame.quit()
        sys.exit()
      elif event.type == pygame.KEYDOWN:
        if selected_action is not None:
          # Assign the pygame key to the action in the input_map dict.
          input_map[selected_action] = event.key
          selected_action = None
          # Need to re-render the surfaces.
          key_list = create_key_list(input_map)
        if event.key == pygame.K_ESCAPE:  # Leave the menu.
          # Return the updated input_map dict to the main function.
          return input_map
      elif event.type == pygame.MOUSEBUTTONDOWN:
        selected_action = None
        for surf, rect, action in key_list:
          # See if the user clicked on one of the rects.
          if rect.collidepoint(event.pos):
            selected_action = action

      screen.fill(sky_blue)
      # Blit the action-key table. Draw a rect around the
      # selected action.
      for surf, rect, action in key_list:
        screen.blit(surf, rect)
        if selected_action == action:
          pygame.draw.rect(screen, red, rect, 2)

    pygame.display.update()




def draw_button(text, x, y, width, height, colour): # draw button function
  font_button = pygame.font.Font(None, 50)
  pygame.draw.rect(screen, colour, (x, y, width, height))
  text_surface = font_button.render(text, True, white)
  text_rect = text_surface.get_rect(center=(x + width / 2, y + height / 2))
  screen.blit(text_surface, text_rect)


def bootup_menu():  # main menu function
  pygame.display.set_caption("Mimit | Main Menu")
  input_map = {"RIGHT": pygame.K_d, "LEFT": pygame.K_a, "UP": pygame.K_w, "DOWN": pygame.K_s, "DASH": pygame.K_LSHIFT, "ATTACK": pygame.K_SPACE, "INVENTORY" : pygame.K_i} ######################
  while running:
    screen.fill(sky_blue)
    font_title = pygame.font.Font(None, 125)
    text_surface = font_title.render("Mimit!", True, pygame.Color((25, 25, 255)))
    text_rect = text_surface.get_rect()
    text_rect.midtop = (screen_width // 2, 88)
    screen.blit(text_surface, text_rect) # renders title
    draw_button("Start Game!", 200, 300, 400, 48, (25, 25, 255))
    draw_button("Settings", 200, 480, 400, 48, (25, 25, 255))
    draw_button("Quit", 200, 660, 400, 48, (25, 25, 255)) # render buttons
    for event in pygame.event.get(): # give buttons functionality
      if event.type == pygame.QUIT:
        pygame.quit() 
        sys.quit() 
      if event.type == pygame.MOUSEBUTTONDOWN:
        mouse_x, mouse_y = pygame.mouse.get_pos()
        if (200 <= mouse_x <= 600) and (300 <= mouse_y <= 348): # start game
          gameplay(running, input_map)
          #character_preset_menu()
          pass
        elif (200 <= mouse_x <= 600) and (480 <= mouse_y <= 528): # settings
          input_map = assignment_menu(input_map)
        elif (200 <= mouse_x <= 600) and (660 <= mouse_y <= 708): # quit
          pygame.quit()
          sys.exit()
      if event.type == pygame.K_ESCAPE:
        input_map = assignment_menu(input_map)


    pygame.display.update() # updates the screen using the variables current states above



def character_preset_menu():
  pass



bootup_menu() # loads bootup menu
# gameplay.py

import pygame
import sys
import math
import time
from main import *
import csv

pygame.init()

#consts
screen_width = 800
screen_height = 800
sky_blue = (102, 230, 225)
white = (255, 255, 255)
black = (0, 0, 0)
blue = (0, 0, 255)
red = (255, 0, 0)
green = (0, 255, 0)
map_size = 40
tile_size = 20
PLAYER_SPRITE_STATIONARY = pygame.image.load("assets/PLAYER_SPRITE_STATIONARY.png")
PLAYER_SPRITE_STATIONARY = pygame.transform.scale(PLAYER_SPRITE_STATIONARY, (20, 20))
PLAYER_SPRITE_MOVING = pygame.image.load("assets/PLAYER_SPRITE_STATIONARY.png")
PLAYER_SPRITE_MOVING = pygame.transform.scale(PLAYER_SPRITE_STATIONARY, (20, 20))

#map
MAP = (
      "########################################"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "#                                      #"
      "########################################"
)

#Initialise the pygame window
screen = pygame.display.set_mode((screen_height,screen_width))
clock = pygame.time.Clock()

#Draw the map based on the MAP string, utilising loops
def draw_map():
  for row in range(map_size):
    for col in range(map_size): # iterate over each row/column
      square = row * map_size + col
      pygame.draw.rect(
        screen,
        (230,15,15) if MAP[square] == "#" else (0,0,255), # Uses the character flags given
        (col*tile_size ,row*tile_size , tile_size, tile_size)
      )


def user_position_check(self, position_change): 
  # Up is minus 40, Down is plus 40, left is minus 1, right is plus 1
  next_tile = MAP[self.tile + position_change]
  if next_tile == "#": # Checks the flags in MAP
    return False
  else:
    return True


class USER(pygame.sprite.Sprite): # Grouping the users sprite as its own class
  def __init__(self, x, y):
    super().__init__() 
    self.image = PLAYER_SPRITE_STATIONARY
    self.rect = self.image.get_rect()
    self.rect.topleft = (x, y)
    self.speed = 20
    self.tile = 41
    self.inventory = {}
    self.selected_item = None
    self.stamina = 100
    self.health = 100
    
    # Self variables update globally and store values about the user only


  def update(self, keys, input_map): # Takes key binds from input map and responds with appropriate movement
    if keys[input_map["LEFT"]]:
      position_change = (-1)
      if user_position_check(self, position_change):
        self.tile += position_change # position change changes the vector position of the user, not the physical location
        self.rect.x -= self.speed # changes the physical location of the sprite
        self.image = PLAYER_SPRITE_MOVING # Image of moving, must be changed once graphics are developed
      time.sleep(0.2)
    elif keys[input_map["RIGHT"]]:
      position_change = 1
      if user_position_check(self, position_change):
        self.tile += position_change
        self.rect.x += self.speed
        self.image = PLAYER_SPRITE_MOVING
      time.sleep(0.2)
    elif keys[input_map["UP"]]:
      position_change = (-40)
      if user_position_check(self, position_change):
        self.tile += position_change
        self.rect.y -= self.speed
        self.image = PLAYER_SPRITE_MOVING
      time.sleep(0.2)
    elif keys[input_map["DOWN"]]:
      position_change = 40
      if user_position_check(self, position_change):
        self.tile += position_change
        self.rect.y += self.speed
        self.image = PLAYER_SPRITE_MOVING
      time.sleep(0.2)
    elif keys[input_map["INVENTORY"]]:
      inventory_open(self, input_map)
    else:
      position_change = 0 # stationary else statement
      self.image = PLAYER_SPRITE_STATIONARY


  def inventory_open(self, input_map, screen):
    inventory_width, inventory_height = 640, 220
    selected = None
    while selected == None:
      pygame.draw.rect(screen, (0,0,0), (80, 290, inventory_width, inventory_height))
      screen.blit()


# Create USER sprite using groupings
user = USER(20, 20) # Spawn point in pixels (1st walkable tile)
user_sprites = pygame.sprite.Group()
user_sprites.add(user)

def gameplay(running, input_map):
  pygame.display.set_caption("Gameplay!")
  screen.fill(sky_blue)
  while running: # new game instance
    draw_map()
    for event in pygame.event.get():
      if event.type == pygame.QUIT:
        pygame.quit()
        sys.exit()
    keys = pygame.key.get_pressed() # Checks state of all keys, returns True when a key is pressed
    user_sprites.update(keys, input_map) # Updates based on this check ^
    user_sprites.draw(screen)


    pygame.display.update()

I have already attempted moving the functions into different files and renaming them, this seems not to have worked. (sorry for poor English and coding works, I'm new to both)

1

There are 1 answers

0
jsbueno On

Multiple Python files may not work correctly if they are just loosely put together in the same directory.

In particular, if you try to run a file from a parent directory - like python game/mygame.py when game_play.py is also in the game directory, it will fail.

The best approach is to turn your project in a minimalist Python package, and install it with the editable flag. Then, from one file, you will be able to type things like from .game_play import * (notice the "." prefixing "game_play" here).

As a side note, I should also remind you that ... import * is usually a poor choice - in general, it is better to either import the module, and keep it as prefix, or import names from it explicitly.

Anyway, if your files are inside a "game" directory, to create a game-package you have to do this: on the parent directory of "game", (i.e. in the same directory "game" is listed) create a file with 0 bytes named "pyproject.toml" This will create a project spec using the default values, which are the classic "setuptools" and "wheel", and will not need a "setup.py" file. Then, in the same directory, type on the O.S. shell pip install -e . (you'd better have a proper virtualenv for that, but it will work otherwise, and that is a separate issue).

From that point on, "game" is an installed package, and you can do things like import game.game_play . If you have a file named __main__.py inside the game folder it will function as the entry point when you type python -m game from the shell prompt.