I have a method in a Pygame Sprite subclass, defined as such:
def walk(self):
"""move across screen"""
displacement = self.rect.move((self.move, 0))
if self.rect.left < self.area.left or self.rect.right > self.area.right:
self.move = -self.move
displacement = self.rect.move((self.move, 0))
self.rect = displacement
I modified it, adding a parameter speed_x, and now the program is broken.
def walk(self, speed_x):
"""move across screen"""
displacement = self.rect.move((speed_x, 0))
if self.rect.left < self.area.left or self.rect.right > self.area.right:
speed_x = -speed_x
displacement = self.rect.move((speed_x, 0))
self.rect = displacement
Before I called the method like this:
def update(self):
self.walk()
Now I do:
def update(self):
self.walk(self.move)
Why doesn't this work?
You don't explain how it's "broken", but the main difference is that
speed_x = -speed_x
which you have in your second version, is only changing the local variable (arguments are local variables!) speed_x, so that changed value does not persist.
In the first version,
self.move = -self.move
does alter self (specifically one of its attriubtes) and the alteration "persists" in future method calls on the object which is here accessed as self.
Just one of the many key differences between bare names (like speed_x) and qualified names (line self.move), and, I suspect, what's biting you here (hard as you may make it to guess by not saying how the second version is failing your expectations).
You are no storing the offset back in to self.move.
If you want to use the second version of your code, try adding this line:
self.move = speed_x
At the bottom of your function.
As mentioned by others, you are not changing the value of self.move in your new code. I assume the reason you modified this function was so you could reuse this function for values other than self.move.
If you want to be able to pass different arguments into your function and modify them as well, you could pass the modified value of speed_x back as a return value:
def walk(self, speed_x):
"""move across screen"""
displacement = self.rect.move((speed_x, 0))
if self.rect.left < self.area.left or self.rect.right > self.area.right:
speed_x = -speed_x
displacement = self.rect.move((speed_x, 0))
self.rect = displacement
return speed_x
And call the function like this as:
def update(self):
self.move = self.walk(self.move)
Note: This answer assumes that self.move should not always be updated when calling walk. If this assumption is false and self.move should in fact be updated every time walk is run, then you should instead use Xavier Ho's answer.
Related
import pygame
width = 500
height = 500
win = pygame.display.set_mode((width,height))
pygame.display.set_caption("Client")
clientNumber = 0
class Player():
def __init__(self,x,y,width,height,color,win):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
self.rect = (self.x,self.y,self.width,self.height)
self.val = 3
self.win = win
def draw(self):
pygame.draw.rect(self.win, self.color, self.rect)
def move(self):
keyPressed = pygame.key.get_pressed()
if keyPressed[pygame.K_LEFT]:
self.x = self.x - self.val
if keyPressed[pygame.K_RIGHT]:
self.x = self.x + self.val
if keyPressed[pygame.K_UP]:
self.y = self.y - self.val
if keyPressed[pygame.K_DOWN]:
self.y = self.y + self.val
def refreshWindow(win,player):
win.fill((255,255,255))
player.draw()
pygame.display.update()
def main():
player = Player(50,50,100,100,(0,255,0),win)
clock = pygame.time.Clock()
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.quit()
player.move()
refreshWindow(win,player)
main()
The code is updating self.x and self.y and key is being pressed, but the display is not changing the
position of the rectangle. I am new to this and was learning from a tutorial. My code is the exact as that tutorial guide but its not working. Like only its not displaying the updated version. The rest is working fine, i printed statements to verify that the window object is the same and if key was pressed and if x and y coordinates were being changed or not.
Though you have answered your question and fixed your issue I want to mention something. It is not a great idea to have your game object keep both a self.rect and separate self.x, self.y, self.width, self.height attributes as you are doing.
All those attributes are contained within the rect and keeping to separate versions of the same data just means that you have to be updating them both and is large potential for errors either because one or the other did not get updated, or because the state was checked before it got updated and so it contained an old value.
just use a Rect and access the x, y, width, height values as attributes in it like this self.rect.x, self.rect.y, self.rect.width, self.rect.height.
You should check the docs for Rect here and see how much more useful it is than working with separate x,y,width, and height values. For example being able to manipulate it by the center, or being able to directly test the right edge without having to add the width, etc.
The exception with respect to keeping a separate self.x and self.y is if you are having issues related to angular movement where your velocity could be fractional and need floating point accuracy for the positions, since Rects keep int values. In that case you would keep adjust the self.x += vel_x and self.y += vel_y and then immediately assign the x,y to the rect self.rect.topleft = round(self.x), round(self.y) and then use the rect for the rest. The x and y are just for the position accuracywhen you need fractional position adjustments, which does not apply here in your example.
Never mind, i fixed it. I was not changing the self.rect values. Although the tutorial didnt change it. Anyways, its working now.
I keep getting this error whilst loading my program global name 'load_jpeg' is not defined
when running my class code.
class Hero:
def __init__(self,x,y):
self.x=x
self.y=y
self.width=70
self.height=70
self.image = pygame.image.load('ezio.jpg')
self.rect = self.image.get_rect()
The fact that pygame.blit() did not work is very clear and same with load_jpeg. First, the load_jpeg error. Just like how Martijn Pieters said, you can't just create a function that Python did not originally have. Maybe you could have wrote a specific function with that name, but that is not the case. Second, the pygame.blit(). To use this function, you need two arguments inside of it. To be able to do that, you might want to change your class:
class Hero(pygame.sprite.Sprite):
def __init__(self, location, image_file):
pygame.sprite.Sprite.__init__(self)
self.rect.top, self.rect.left = location #The equivalent as self.x and self.y
self.width = 70
self.height = 70
self.image = pygame.image.load(image_file)
self.rect = self.image.get_rect()
And add this line (if you haven't done this already and outside your class and before your while loop):
Heroes = Hero([100, 100], 'ezio.jpg')
This creates an variable that can be used for the pygame.blit() function. It is usually more better to do self.rect.top, self.rect.left = location than defining self.x and self.y. It is usually more of a PyGame style with rects. It looks like you are going to __init__ this into a sprite and define self. After you have done this, pygame.blit() should work. Saying that the variable name of the class is x, you must always do it in this order:
pygame.blit(x.image, x.rect)
With x.image first, then x.rect after. In your case, that line should look like:
pygame.blit(Heroes.image, Heroes.rect) #Assuming the variable name is Heroes
This answer should cancel your error and make the pygame.blit() function working again. I hope this helps you!
This is a the class of a sprite that goes left and right on the screen, when it hits the boundaries, it makes a "boing" sound and goes the opposite direction, everything works perfectly fine except for there is no boing sound when it hits the edge
class MySprite(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("vegetables.gif")
self.image = self.image.convert()
self.rect = self.image.get_rect()
self.rect.left = 0
self.rect.top = 167
self.__direction = 10
def update(self):
self.rect.left += self.__direction
sound = pygame.mixer.Sound("Bounce.mp3")
sound.set_volume(0.5)
if (self.rect.left < 0) or (self.rect.right > screen.get_width()):
sound.play()
self.__direction = -self.__direction
If you want the class to play its own sound, just load it like any attribute on __init__.
class MySprite(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("vegetables.gif")
self.image = self.image.convert()
self.sound = pygame.mixer.Sound("Bounce.mp3") #like this
self.rect = self.image.get_rect()
self.rect.left = 0
self.rect.top = 167
self.__direction = 10
Then whenever it's correct to do so, just call self.sound.play().
def update(self):
self.rect.left += self.__direction
if (self.rect.left < 0) or (self.rect.right > screen.get_width()):
self.sound.play() #as seen here
self.__direction = -self.__direction
For whatever it's worth - if you're going to do it in this way (have the sprite play its own sounds, etc), I would recommend loading them beforehand and then passing them as arguments (perhaps default arguments to avoid errors), such that each instance of the class might call a unique sound if need be.
So in your code prior to these classes, one could do something like:
JumpSound = pygame.Mixer.Sound("jump.wav")
BonkSound = pygame.Mixer.Sound("bonk.wav")
#etc etc etc...
...and then later on, pass the sounds as arguments:
class MySprite(pygame.sprite.Sprite):
def __init__(self, jumpsound, bonksound):
#...your other code precedes...
self.jumpsound = jumpsound
self.bonksound = bonksound
#...your other code continues...
myHero = MySprite(JumpSound, BonkSound)
The names are a little bit lousy b/c they are the same barring the CamelCasing, but forgetting that, this is probably a much cleaner approach. You can set your volume on the sounds way before they are passed into the sprites, along with whatever other changes you feel are necessary before the sprite gets ahold of them.
I'm trying to get ball objects on the canvas to collide, and move appropriately. I have some extra movement vars that are not important for my question, but I'd like to keep them the way they are. My ball:
class Ball(Coords):
def __init__(self,canvas,color,drawX1,drawY1,drawX2,drawY2,startX,startY,moveX,moveY):
self.canvas = canvas
self.drawX1 = drawX1
self.drawY1 = drawY1
self.drawX2 = drawX2
self.drawY2 = drawY2
self.startX = startX
self.startY = startY
self.moveX = moveX
self.moveY = moveY
self.id = canvas.create_oval(drawX1,drawY1,drawX2,drawY2, fill=color)
self.canvas.move(self.id,self.startX,self.startY)
def draw(self,ran1,ran2):
self.ran1 = ran1
self.ran2 = ran2
ranspeed = random.randint(ran1,ran2)
pos = self.canvas.coords(self.id)
self.canvas_height = self.canvas.winfo_height()
self.canvas_width = self.canvas.winfo_width()
self.canvas.move(self.id,self.moveX,self.moveY)
The variables in ball's init just let me change various attributes the ball has, and in draw I set the speed to a random int range because I like it that way.
I decided to use tkinters get overlapping func to get the item IDs of balls so I could make collision object indepent. My problem is that the result is a tuple which I cannot use to extract the item ID out of in order to perform some movement operation on it. My collision check code is inside of the draw function, and looks like this:
pos = self.canvas.coords(self.id)
inside = canvas.find_overlapping(pos[0],pos[1],pos[2],pos[3])
if pos[0] <= 0:
self.moveX = ranspeed
if pos[1] <= 0:
self.moveY = ranspeed
if pos[2] >= self.canvas_width:
self.moveX = ranspeed*-1
if pos[3] >= self.canvas_height:
self.moveY = ranspeed*-1
Also note that the tuple also returns self.id because it's in itself, so I exclude the first tuple index:
idcolliders = inside[1:]
When I print idcolliders, I receive a stream of tuples for ballobject one - last, containing all of the id's currently within its' coords.
Is there a way to get an ID result from this tuple and put it in a function to change it (move it, specifically) while it exists, that doesn't throw errors while it doesn't?
You can use tkinter's bbox to get the 4 tuples, x1,y1,x2,y2 for your item canvas.bbox(item) and then use that in a math calculation to check if other items or your item is inside another items tuples.
Specifically, I would like each sprite in sg_fireball to have 'bounces', without giving 'bounces' to every sprite from Spell(). Is there a clean way to do this without making 'bounces' an argument of Spell(), or looping through sg_fireball?
The relevant code snippets:
self.sg_fireball = pygame.sprite.Group()
self.sg_fireball.speed = 6.0
self.sg_fireball.image = pygame.image.load("fireball.png")
self.sg_fireball.bounces = 1
if event.type == pygame.MOUSEBUTTONDOWN:
self.character.cast(self.sg_fireball)
def cast(self, sg):
sg.add(Spell(self.rect.center, sg.speed, self.dir, sg.image))
class Spell(pygame.sprite.Sprite):
def __init__(self,pos, speed, direction, img, bounces):
pygame.sprite.Sprite.__init__(self)
self.bounces = bounces
self.image = img
self.rect = pygame.Rect(pos, (8,8))
self.posx = self.rect.x
self.posy = self.rect.y
self.speed = speed
self.dir = direction
self.velx = self.speed*math.cos(self.dir)
self.vely = self.speed*math.sin(self.dir)
If I understood correctly, you wish some of the sprites to have a certain attribute, while others won't. This is a perfect example of polimorphism and inheritance.
This is one of the options that you can do:
You subclass a normal spell as a bouncy spell. You can then have another update function where you will take care of bouncing. You can normally add a BouncySpell in the same sprite group as the NormalSpell.