0

Overview I am making a game with a spaceship that can move in all 4 directions. In the game while loop my spaceship can be controlled by the keyboard as seen below. For the sake of simplicity I have only shown some part of my code, and only the part where I move my spaceship to the right.

Method As seen, I use a 'for loop' and the pygame module to receive the input from the keyboard. And I am using the flag method to enable continuous moving. I start by setting all the flags to False. Then depending on the keyboard press it moves in the different direction and set to False again when the key goes up. Its all working fine... NB: the variable surf_centerx is the center of my spaceship and the screen.blit(surf, surf_rect), pygame.display.flip() at the end is just displaying the spaceship on the screen and updating the latest changes in position of the spaceship, respectively.

import pygame
# pygame initializing
pygame.init()

#create the screen surface
screen =  pygame.display.set_mode((400, 400))

#..snip...

speedfactor = 0.1
move_right = False
#..snip...
while True:
    for event in pygame.event.get():
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RIGHT:
                move_right = True
#..snip...
        elif event.type == pygame.KEYUP:
            if event.key == pygame.K_RIGHT:
                move_right = False
#..snip...

    if move_right:
        surf_centerx += speedfactor

#..snip...

    screen.blit(surf, surf_rect)
    pygame.display.flip()

Code refactoring - I thought But because my while loop keeps growing I want to do some code refactoring, I thought :o) So I would put my keyboard stuff in a separate module, with a function I would then call in my while loop. I save the value from k_c.check_key_events() in the variable, 'returned' and below that the value is unpacked by the line 'move_right = returned'. My while loop now look like this (a lot shorter):

import keyboard_control as k_c
while True:
    returned = k_c.check_key_events()
    move_right = returned
    if move_right:
        surf_centerx += speedfactor
#..snip...

    screen.blit(surf, surf_rect)
    pygame.display.flip()

And the side module controlling the keyboard (keyboard_control) is:

import pygame

#necessary pygame initializing
pygame.init()

def check_key_events():
    for event in pygame.event.get():
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RIGHT:
                move_right = True
#..snip...
        elif event.type == pygame.KEYUP:
            if event.key == pygame.K_RIGHT:
                move_right = False
#..snip...
    return move_right

I now call the keyboard_control module within the 'while loop' in the main module and the keyboard_control module should now return the value of the 'move_right' variable. Putting the for loop etc. in its own module is effectively simplifying the main module.

Problem But running my program now gives the error: *return move_right UnboundLocalError: local variable 'move_right' referenced before assignment *

The problem is that the function does not know the variable 'move_right', because it is not defined within the function 'check_key_events()'. And if I defines it at the beginning of the function, something like this:

#..snip...
def check_key_events():
    move_right = False
    for event in pygame.event.get():
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RIGHT:
                move_right = True
#..snip...

Then my problem is that I loose the continuous moving, because in every cycle of the while loop the line 'returned = k_c.check_key_events()' calls the function 'check_key_events()', which sets the variable move_right to False even though I haven't released the 'move right' key. So my question is how do I cleverly come around this problem?? I just can't figure that out.

  • I think this question would fit better to https://softwareengineering.stackexchange.com. I'd also recommend an object-oriented solution. There's a [`Game` class template](http://programarcadegames.com/python_examples/show_file.php?file=game_class_example.py) in [Program Arcade Games](http://programarcadegames.com) that will get you started (I suggest to read chapter 12 and 13). I often let the sprite objects (e.g. a spaceship) handle the events in their own event handler methods (I just pass the events in the event loop to the objects). – skrx Jun 11 '18 at 15:33
  • Okay. Because I am still rather new in programming my approach has been to write the code without classes because then I can keep track off what's going on - and then my plan is to refactor it to OOP afterwards. Because the dynamic of classes is still a little 'mystic' to me. So I hoped I could make this code without OOP. – Jesper Joachim Sørensen Jun 12 '18 at 08:09

1 Answers1

0

There are (at least) two ways to fix it, the quick'n'dirty, and the proper one.

The quick'n'dirty: declare your move_right out of the function as a global variable and put global move_right at the beginning of the function. This will work, but it's quite ugly, because global variables are evil.

The proper way is to have a class for your spaceship, and set a property there telling you in which direction you're moving. This is kind of a long way, but definitely something that you want, because maybe later you'll want to expand on the spaceship object.

ChatterOne
  • 3,381
  • 1
  • 18
  • 24
  • okay, it seems that classes is the obvious choice so. So I have to learn a bit of this. One more question: when I make a class for my spaceship, I can create a global variable which is not evil :o) Why is it that global vaiables generated from classes is good and the other (explicit stated) variables are evil? Thanks – Jesper Joachim Sørensen Jun 11 '18 at 07:05
  • The variables are not "generated from classes", they are instance variables, which are limited in scope to the current instance of the class. This means that if you have two spaceship (maybe two players?), one can move to the right, and the other to the left, and the two variables will not interfere with each other. This will come in handy if/when you'll want to add enemies, because you probably will need more than one or two. – ChatterOne Jun 11 '18 at 07:10
  • Now a tried the quick-n-dirty method, but still can get it working. My module code is modified to 'move_right = False def keyboard_events(): global move_right for event in pygame.event.get(): It will not make continuous moving. Guess it's because – Jesper Joachim Sørensen Jun 11 '18 at 07:36
  • Now a tried the quick-n-dirty method, but still can get it working. My module code is now modified. I put 'move_right = False' before my function (check_key_events()) and then I declare 'global move_right' at the top inside the function. But it still won't make continuous moving. Guess it's because it still sets 'move_right' to false every time it runs the cycle in the while loop? – Jesper Joachim Sørensen Jun 11 '18 at 07:42
  • Regarding classes. Is it the 'self.' attribute of the variables made from class instances, that make them limited in scope? – Jesper Joachim Sørensen Jun 11 '18 at 07:48
  • About question 1) Yes, you should only set the moving_right=false on the key up event, otherwise it will not work (and use the `global move_right` at the top of that function, too). About question 2) Yes, kinda. But keep in mind that in classes there are instance variables and class variables. You want the `self.move_right` one because it's an instance variable. – ChatterOne Jun 11 '18 at 07:50
  • Now I tried 1) - I have set the move_right = False at the key up event - but I get the error: 'NameError: name 'move_right' is not defined' even though I put the global move_right at the top of the function. – Jesper Joachim Sørensen Jun 12 '18 at 08:02
  • Did you declare `move_right` outside of functions, i.e. as a global variable? If you did, post your updated code, because blind guessing is not easy – ChatterOne Jun 12 '18 at 08:18