Python/Pygame.. Trouble w/ classes - python

Hey guys I have some issues while I was making my game.... I want to move my character while the image changes every "step" that it looks like an animation...
I haven been working on this for hours but the only thing I have done so far is to move the char but all images are drawn at once while moving
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
currentimageleft = 1
gamedisplay.blit(background, (0,0))
if currentimageleft == 1:
gamedisplay.blit(temp1, (template_x, template_y), (g1x, g1y, box, box))
template_x -= moveit
currentimageleft += 1
if currentimageleft == 2:
gamedisplay.blit(temp1, (template_x, template_y), (g2x, g2y, box, box))
template_x -= moveit
currentimageleft += 1
if currentimageleft == 3:
gamedisplay.blit(temp1, (template_x, template_y), (g3x, g3y, box, box))
template_x -= moveit
currentimageleft += 1
if currentimageleft == 4:
gamedisplay.blit(temp1, (template_x, template_y), (g4x, g4y, box, box))
template_x -= moveit
currentimageleft = 1
pygame.display.update()

Use elif for the subsequent comparisons, so that only one is done per iteration.

Since you are incrementing currentimgeleft in each of your if blocks, it then triggers the next if block down the line. You only want one to trigger each frame, so switch the if to elif for all but the first one and then you'll have only one triggering each time the K_LEFT button is pressed on the keyboard. Like so
if currentimageleft == 1:
# code
elif currentimageleft == 2:
# code
...
However, notice that you are setting currentimageleft each time you enter the bigger if statement for the keypress. This will ensure that you always end up using the very first sprite of your animation. You may want to associate currentimageleft with a class variable by declaring it in __init__ to be something like
def __init__(self, ...):
# other code, including possible parent constructors
self.currentleftimage = 1
# more code
You would then access your variable with self.currentimageleft instead of currentimageleft. This would then allow you to use else or elif clauses on your button press detection (the outer if in your code snippet) to reset the self.currentleftimage variable back to 1 allowing you to track motion that way instead.
You may also find this other answer on the equivalent form of switch in Python interesting, though several calls to elif clauses is also perfectly normal and may lead to more legible code in many cases.

Related

How to properly update tkinter label element

I'm working on creating a stopwatch that is given two values. It starts with value A and counts down to 0, then changes to value B, counts down to 0, then goes back to value A, counts down to 0 etc until I close the program (I'll probably add a pause button at some point) and overall it's working really well. However, when it updates the label with the new text, It seems to just be making a new text item and putting it as a layer on top of the previous. I can see then when I go from a single double-digit number to a single digit, and the sentence is shortened, the part of the old sentence that isn't covered can still be seen. So I'm hoping I'm just missing something incredibly simple. The newWindow.update() is what I thought would update the window but it does not appear to be doing that. Below is my snippet of code that handles the logic.
def countdown(timer_count,count_type):
counter = timer_count
count_type = count_type
while counter >= 0:
timer = tk.Label(newWindow, text=f"{count_type} for: {counter}")
timer.config(font=("TkDefaultFont",30))
timer.grid(row=0,column=2)
newWindow.update()
time.sleep(1)
counter -= 1
print(counter)
if count_type == "work":
count_type = "rest"
elif count_type == "rest":
count_type = "work"
return count_type
def interval():
counter_type = "work"
while True:
if counter_type == "work":
counter_type = countdown(int(exer_var.get()),counter_type)
elif counter_type == "rest":
counter_type = countdown(int(rest_var.get()),counter_type)
You are creating a new Label widget each time through your while loop instead of changing the text inside the while loop. That is why it is layering one widget on top of the other, so you need to create your widget, then run the while loop, and set the text to change in the timer.config inside the loop. You should also declare the font in the original tk.Label, no need to change that each trip through the loop. For "some_starting value" it would probably be text = counter
timer = tk.Label(newWindow, font=("TkDefaultFont",30), text="some_starting_value")
while counter >= 0:
timer.config(text=f"{count_type} for: {counter}")
timer.grid(row=0,column=2)
Its hard to say from your code where this is taking place, but here is how its usually done:
Make the label outside all functions in the main block.
timer = tk.Label(newWindow,font=("TkDefaultFont",30)) # Adding the font here itself
Then now inside the function just change its value using config:
def countdown(timer_count,count_type):
counter = timer_count
count_type = count_type
while counter >= 0:
timer.config(text=f"{count_type} for: {counter}") # Update the options for the created label.
timer.grid(row=0,column=2)
So now each time function/loop is running, new labels wont be created and overwritten, instead existing label will be configured.
On a side note, using time.sleep() and while loop is not the very best practice, even with update() it will still cause some kind of disturbance to GUI. Instead, re-arrange your code to use after(ms,func) method, which will not freeze the GUI. You can ask a new question on that if you face any trouble, later.

How to update polygon size with cumulative key press in Psychopy

I am using Psychopy to create a psychological task. For a given routine, I would like the height of a polygon (rectangle) to increase with every key press (same key every time), until it reaches the maximum number of key presses (e.g. 10). I cannot figure out how to create a loop to count number of key presses within the same routine, nor how to use this to generate a variable that will constantly update the size of the polygon.
Here is what I have tried as code in the routine which gets stuck in while loop... and I am not sure if the loop should go in the code "Before Routine" or for "Each Frame"
total_key_count = 0
while True:
resp_key = event.waitKeys(keyList=['1'])
if resp_key == '1':
total_key_count = total_key_count + 1
# .. or break out of the loop if reach 10
elif total_key_count == 10:
break
Thanks!
Never use event.waitKeys() in a Builder code component. Builder is structured around a drawing loop that requires updating the screen and responding to events on every screen refresh. If you call waitKeys(), you pause execution completely until a key is pressed, which will completely break Builder's temporal structure.
In the Begin routine tab, put this:
key_count = 0
max_keys = 10
In the Each frame tab, put this:
key_press = event.getKeys('1')
if key_press: # i.e. if list not empty
key_count = key_count + 1
if key_count <= max_keys:
# increment the height of the stimulus by some value
# (use what is appropriate to its units):
your_stimulus.size[1] = your_stimulus.size[1] + 0.1
else:
# terminate the routine (if required)
continueRoutine = False
Note that getKeys(), unlike waitKeys() just checks instantaneously for keypresses. i.e. it doesn't pause, waiting for a key. This is fine though, as this code will run on every screen refresh, until the required number of keys have been pushed.
Presumably you also need to save some data about the response. This would best be done in the End routine tab, e.g.
thisExp.addData('completion_time', t) # or whatever needs recording

Using a String to Call a Variable in Python 3.3 with Pygame

I've been looking for an answer to a question I've been having, and to be honest this is the first time in the ten months that I couldn't find an answer. I've looked at multiple questions, Dictionaries do work, this conclusion was found in error(and I tried to use dictionaries(which most of the answers suggest), but they haven't worked.) The only other answers I could find was to use exec(), but they always seem to be accompanied by a comment saying they're dangerous, so I've steered clear of them.
On to my question:
This is an example of the code that I've been working on in Pygame:
import pygame,sys
from pygame.locals import *
pygame.init()
screen = pygame.display.set_mode((800,230))
pygame.display.set_caption('Example')
tickclock = pygame.time.Clock()
change = 1
car1 = pygame.image.load('Car1.png')
car2 = pygame.image.load('Car2.png')
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
pygame.quit()
sys.exit()
elif event.key == K_SPACE:#To change the picture when the user presses space
change = change + 1
#Current way-----#
if change == 1:
screen.blit(car1,(0,0))
elif change == 2:
screen.blit(car2,(0,0))
#Continue until all pictures are used...
elif change > 2:#The 2 will change based on the amount of pictures.
change = 1
#----------------#
pygame.display.flip()
tickclock.tick(60)
Where it is commented as "current way" is where my question is. Using this message of if/elif statements can take up a tun of lines if there are a lot of pictures for Pygame to display, and that would be doubled by the need to call screen.blit() every after every if/elif statement.
This is an example of what I would like to do:
import pygame,sys
from pygame.locals import *
pygame.init()
screen = pygame.display.set_mode((800,230))
pygame.display.set_caption('Example')
tickclock = pygame.time.Clock()
varcar = 'car'
varnum = '1'
car1 = pygame.image.load('Car1.png')
car2 = pygame.image.load('Car2.png')
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
pygame.quit()
sys.exit()
elif event.key == K_SPACE:#To change the picture when the user presses space
varnum = int(varnum)
varnum = varnum + 1
varnum = int(varnum)
#Using this to substitute the class I set up for the actual file--#
varcombined = varcar + varnum#To make varcombined = 'car1','car2',etc.
if varnum > 2:#To keep varnum within the loaded image numbers
varnum = 1
#-----------------------------------------------------------------#
#The way I wish to use--------------------------------------------#
#Some Code
screen.blit(varcombined,(0,0))
pygame.display.flip()
tickclock.tick(60)
What I am trying to do here is combine the varcar and varnumber variable so that I can combine them in the varcombined variable while only changing the varnumber variable. This will give strings that are the same as the variable names of the loaded images: car1 and car2. Then what I want to do is somehow call the variables car1 and car2 in 'screen.blit(varcombined,(0,0))' on line 31 by using the string as a substitute to the actual variable name. (Without actually having to dynamicly create a new variable)
Is there a way this can be done, and if so, how can it be done?
Even though the question may seem a bit specific, I think this can benefit a lot of people who may have a need/want to use a string to call a variable.
If anyone doesn't understand what I'm asking, just say so, and I'll try to edit the question to clarify.
EDIT
Thank you to abarnert and Fernando Aires for their speedy answers. :)
I'm not sure if I should make this an edit or another question, but I need to expand my question a little due to recent issues.
I used abarnert's answer, and it worked like a charm.
car_dictionary = {1: car1 2: car2}
#-----------#
screen.blit(car_dictionary[change], (0,0))
I would now like to ask, what I should do in the case that I added a 'truck1' and 'truck2' image, and now needed to not only interchange between 1 and 2 but also car and truck?
If there's a way to do that of course.
So, what you want here is some way to get car1 or car2 depending on whether change is 1 or 2. And, ideally, to be able to expand this to car73 without writing a chain of 73 elif statements.
While it's possible to do this by constructing the variable names and then looking them up in the appropriate global/local/object scope (e.g., vars()['car{}'.format(change)]), this is a very bad idea, for lots of reasons.
The right way to do this is to store the cars in a dictionary, keyed by the varnum values:
cars = {1: car1, 2: car2}
Or, even better, load the cars directly into the dictionary with a loop, and don't even create the separate variables in the first place (which would get really tedious when you have 73 cars):
cars = {}
for num in range(1, 3):
car = pygame.image.load('Car{}.png'.format(num))
cars[num] = car
Or, more simply:
cars = {num: pygame.image.load('Car{}.png'.format(num)) for num in range(1, 3)}
Now, to look up a car by its key, that's just cars[change]. In particular, your code becomes:
screen.blit(cars[change], (0.0))
It's worth noting that in your existing code, the keys aren't really doing anything after load time. They're just contiguous integers, and you just start at the first one, count up one by one, and roll back to the first one whenever you reach the end. If that's true in your real code, you don't even really need a dictionary here, just a list; the only change you need to make is to start counting from 0 instead of from 1.
change = 0
cars = [pygame.image.load('Car{}.png'.format(num)) for num in range(1, 3)]
And if the only thing you need change for is to pick a car, you can simplify this even further by getting rid of change, too. You just want to iterate over cars, repeating forever. itertools.cycle makes that a one-liner. Instead of while True:, you can do this:
for car in itertools.cycle(cars):
And then, instead of cars[change] you just use car.
You could use a dictionary for that, as in:
car1 = pygame.image.load('Car1.png')
car2 = pygame.image.load('Car2.png')
...
screen.blit( {"car1": car1, "car2": car2}[varcombined], (0,0))
Or you could even replace your varcombined with a carNumber variable, for instance - and therefore changing your dictionary to {1: car1, 2: car2}.
Hope that helps.

Applying the getMouse() function to one part of the window

I am attempting to apply the getMouse() function to a specific part of the window, instead of the whole thing. I need the block that is clicked in to change color if it is 'rect1'. However if any other block is clicked, nothing should happen. I have attached the portion of my code which I feel relates to this in case anyone can be of any assistance.
#draw the grid
for j in range (6):
for i in range(6):
sq_i = Rectangle(Point(20 + (40*i), 20 + (40*j)),
Point(60 + (40*i),60 + (40*j)))
sq_i.draw(window)
sq_i.setFill('white')
sq_i.setOutline('grey')
#wait for a click
window.getMouse ()
#turn the alotted region red
rect1 = Rectangle(Point(20 + (40*1), 20 + (40*1)),
Point(60 + (40*1), 60 + (40*1)))
rect1.setOutline('black')
rect1.draw(window)
rect1.setFill('brown')
#if the mouse is clicked in rect1, change the block color to black
while window.getMouse() in rect1:
rect1.setFill('black')
First off, you need to understand what window.getMouse() in rect1 does. Python's in operator works by turning a in b into the method call b.__contains__(a). Unfortunately, the Rectangle class doesn't have a __contains__ method. This your immediate problem.
So, you need to use a different test. I suggest using Python's chained comparison operators to do the bounds checking yourself (there doesn't seem to be any library support for this in the graphics module):
mouse = window.getMouse()
if rect1.p1.x < mouse.x < rect1.p2.x and rect1.p1.y < mouse.y < rect1.p2.y
rect1.setFill("black")
Note that I've changed the while loop for an if statement. If you want to repeatedly test mouse clicks until one hits the rectangle, you probably want to loop on while True and then break if the tested condition is true:
while True:
mouse = window.getMouse()
if rect1.p1.x < mouse.x < rect1.p2.x and rect1.p1.y < mouse.y < rect1.p2.y
rect1.setFill("black")
break

Having a certain piece of code run for a specified amount of time

I'm working on a galactica type of game using pygame and livewires. However, in this game, instead of enemy's, there are balloons that you fire at. Every 25 mouse clicks, I have the balloons move down a row using the dy property set to a value of 1. If a balloon reaches the bottom, the game is over. However, I'm having some trouble figuring out how to get this to run only for, say, 1 second, or 2 seconds. Because I don't have a way to "time" the results, the dy value just indefinitely gets set to 1. Therefore, after the first 25 clicks, the row just keeps moving down. This is ok, but like I said, it's not my intended result.
Here is the code I have so far for this action:
if games.mouse.is_pressed(0):
new_missile = missile(self.left + 6, self.top)
games.screen.add(new_missile)
MISSILE_WAIT = 0 #25
CLICKS += 1
if CLICKS == 25:
SPEED = 1
CLICKS = 0
CLICKS, and MISSILE_WAIT are global variables that are created and set to an initial value of 0 before this block of code. What I'm trying to figure out is the algorithim to put underneath the if CLICKS statement. I've looked through the python documentation on the time module, but just can't seem to find anything that would suit this purpose. Also, I don't think using a while loop would work here, because the computer checks those results instantly, while I need an actual timer.
I'm not sure if I got your question but what I can suggest is that:
class Foo():
def __init__(self):
self.start_time = time.time()
self.time_delay = 25 # seconds
def my_balloon_func(self):
if(time.time() - self.start_time) > self.time_delay:
self.start_time = time.time()
else:
# do something

Categories

Resources