This question already has answers here:
Pygame doesn't let me use float for rect.move, but I need it
(2 answers)
Closed 1 year ago.
It is updated using pygame groups in run
But when my x change is smaller than 1, my sprite just doesn't move
However when it is a negative value and larger than -1, it will move as intended.
class Enemy(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('img/blob.png')
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
def update(self):
self.rect.x += 0.5
pygame.Rect only accepts integer values. floating points will get rounded, and for very small values will equal 0 which is why it won't move unless it's bigger than -1 in your case.
From the documentation
The coordinates for Rect objects are all integers.
Are you calling the function with Enemy.update() ? If so try to add self.rect.x by 1 instead of 0.5
Related
This question already has answers here:
calculating direction of the player to shoot pygame
(1 answer)
Moving forward after angle change. Pygame
(1 answer)
Shooting a bullet in pygame in the direction of mouse
(2 answers)
Closed 2 years ago.
So far, the enemies in my game only fire straight down. I want to be able to aim at the player. This is my Enemy class's shoot method.
class Enemy(sprite.Sprite):
def shoot(self):
# Origin position, direction, speed
# Direction of (0,1) means straight down. 0 pixels along the x axis, and +1 pixel along the y axis
# Speed of (10,10) means that every frame, the object will move 10 px along the x and y axis.
self.bullets.shoot(self.rect.center, (0,1), (10,10))
self.bullets is an instance of my BulletPool class.
class BulletPool(sprite.Group):
def shoot(self, pos, direction, speed):
# Selects a bullet from the pool
default = self.add_bullet() if self.unlimited else None
bullet = next(self.get_inactive_bullets().__iter__(), default)
if bullet is None:
return False
# Sets up bullet movement
bullet.rect.center = pos
bullet.start_moving(direction)
bullet.set_speed(speed)
# Adds bullet to a Group of active bullets
self.active_bullets.add(bullet)
And here is my Bullet class.
class Bullet(sprite.Sprite):
def update(self):
self.move()
def set_speed(self, speed):
self.speed = Vector2(speed)
def start_moving(self, direction):
self.direction = Vector2(direction)
self.is_moving = True
def stop_moving(self):
self.is_moving = False
def move(self):
if self.is_moving:
x_pos = int(self.direction.x*self.speed.x)
y_pos = int(self.direction.y*self.speed.y)
self.rect = self.rect.move(x_pos,y_pos)
Using this, I can only make sprites go straight up (0,-1), down (0,1), left (-1,0) or right (1,0), as well as combining combining x and axes to make a 45 degree angle, (i.e. (1,1) is going down and right). I don't know how to angle something to make it go towards a particular direction other than these. Should I change the way I move my objects? I use the same methods to move my player, and it works perfectly when it's just taking controls from the arrow keys.
I am trying to make a sprite move directly towards the mouse, utilizing the angle between them. This angle is found via the atan2 function. While this angle works fine for rotating the sprite towards the mouse, the sprite moves in the wrong directions depending on the quadrant of the given angle. It will sometimes freeze up in one quadrant, or move directly opposite the mouse.
I am using basic Trig functions to find the angle, and calculate proper additions to the X and Y variables of the sprite. It is also important to note that the angle I calculate, while it doesn't work for movement, does work perfectly for rotation. What's odd is that I pass the X-difference between the two spots, and THEN the Y-difference, which is the opposite of how the inverse tangent function is supposed to be handled. Therefore, I'm not even sure how this angle has been making rotation work correctly.
I've attempted to pass the Y-difference and the X-difference (in that order) into the atan2 function. However, this causes the rotation on my sprite to be wrong, pointing me towards the idea that the angle as a whole is also incorrect. I've also tried following along with numerous other programs, all of which use the same formulas as me. However, these don't work, even when I change the order of the arguments to the atan2 function to match the example programs.
def main():
ExitLoop = False
image = IMAGELOADER.AllImages["Fighter1"]
image2 = IMAGELOADER.AllImages["Fighter2"]
Fighter1 = FighterClass.Fighter(image,(700,700))
Fighter2 = FighterClass.Fighter(image2,(300,300))
while not ExitLoop:
ScreenController.Refresh()
mouse_pos = pygame.mouse.get_pos()
Fighter2.set_x(mouse_pos[0]-32)
Fighter2.set_y(mouse_pos[1]-32)
angle = math.atan2(Fighter1.get_x()-mouse_pos[0]+32, Fighter1.get_y()-mouse_pos[1]+32)
degrees_angle = math.degrees(angle)
Fighter1.rotate(degrees_angle)
xval = Fighter1.get_x()
yval = Fighter1.get_y()
speed = Fighter1.get_speed()
changex = (speed*math.cos(angle))
changey = (speed*math.sin(angle))
Fighter1.set_x(xval+changex)
Fighter1.set_y(yval+changey)
for event in pygame.event.get():
if event.type == pygame.QUIT:
ExitLoop = True
ScreenController.Draw(Fighter1.get_image(),Fighter1.get_rect(),False)
ScreenController.Draw(Fighter2.get_image(),Fighter2.get_rect(),False)
ScreenController.DisplayUpdate()
clock.tick(60)
Class Code (Relevant to the fighter class)
import pygame
import WoodysFunctions
class Fighter(pygame.sprite.Sprite):
def __init__(self,image,XnY):
pygame.sprite.Sprite.__init__(self)
self.image = image
self.__image_source = image
self.rect = self.image.get_rect()
self.__mask = pygame.mask.from_surface(self.image)
self.rect.x = XnY[0]
self.rect.y = XnY[1]
self.__speed = 1
def get_image(self):
return self.image
def get_rect(self):
return self.rect
def get_mask(self):
return self.__mask
def get_x(self):
return self.rect.x
def get_y(self):
return self.rect.y
def get_speed(self):
return self.__speed
def set_image(self,value):
self.image = value
def set_rect(self,value):
self.__rect = value
def set_mask(self,value):
self.__mask = value
def set_x(self,value):
self.rect.x = value
def set_y(self,value):
self.rect.y = value
def set_speed(self,value):
self.__speed = value
def rotate(self,angle):
old_center = self.rect.center
self.image = pygame.transform.rotate(self.__image_source,angle)
self.rect = self.image.get_rect()
self.rect.center = old_center
Expected output: Sprite moves straight towards the mouse
Actual behavior: Sprite moves in wrong directions, with behavior showing patterns depending on quadrant of calculated angle.
Edit:
I changed the program so that the X and Y variables of the sprite are stored in variables separate from the rect object. This prevents decimal truncation. I also recalculated the angle between the sprite and the mouse pointer after the rotation code is finished. In the recalculation, the X and Y difference parameters are swapped to match the inverse tangent function instead of the inverse cotangent function. This recalculated angle is used for angular movement, and the first angle, with the X difference passed first, is used for rotation. It is important to note that after I calculated the changeX and changeY variables using the recalculated angle (with the Y difference passed first), I multiplied them by -1, as otherwise the sprite will move away from the mouse pointer.
I cannot be 100% sure, but I think the problem is that pygame.Rect stores position as integers, because it's supposed to store coordinates and dimensions in pixel unit, and of course you cannot paint half pixel.
Since you are dealing with any angle and trigonometric functions, you end with floats which are truncated when you do:
def set_x(self,value):
self.rect.x = value
Here, if value is 1.4, self.rect.x becomes 1. So you lose "accuracy."
This loss of accuracy is propagated each iteration of the main loop (each frame), resulting in an unexpected motion direction.
The best solution is to store all your value in a separate data structure and update the rect attribute only for drawing in the screen.
I have a classic snake game, Im trying to make the snake bigger, so I started to use printing rects to screen instead of pixels. I tried these codes for that(Im not going to write whole code because it may confused):
class Worm(pygame.sprite.Sprite):
def __init__(self, surface,seg_width,seg_height):
self.surface = surface #this is ==> screen=pygame.display.set_mode((w, h))
self.x = surface.get_width() / 2
self.y = surface.get_height() / 2
self.length = 1
self.grow_to = 50
self.vx = 0
self.vy = -seg_width
self.body = []
self.crashed = False
self.color = 255, 255, 0
self.rect = pygame.Rect(self.x,self.y,seg_width,seg_height)
self.segment = pygame.Rect(0,0,seg_width,seg_height)
def move(self):
""" Move the worm. """
self.x += self.vx
self.y += self.vy
self.rect.topleft = self.x, self.y
if (self.x, self.y) in self.body:
self.crashed = True
self.body.insert(0, (self.x, self.y)) #Func of moving the snake
new_segment = self.segment.copy()
self.body.insert(0, new_segment.move(self.x, self.y))
if len(self.body) > self.length:
self.body.pop()
def draw(self):
for SEGMENT in self.body: #Drawing the snake
self.surface.fill((255,255,255), SEGMENT) #This is where I got error <-----
My codes are working perfectly if I use pixels, but I want a bigger snake (not longer, I mean thicker) so I thought I can use rects, I tried this, but It gives me;
self.surface.fill((255,255,255), SEGMENT)
ValueError: invalid rectstyle object
SEGMENT is actually a tuple of coordinates, why I got this error then?
Also after I press f5, I see this screen first, so basically my theory is working but its a weird output, one rect is smaller than other? Why? That "0" is scoreboard, I didnt wrote whole code as I said.
I just don't understand why, what can I do for fixing it?
I want to say this again because it may confuse too, that surface argument is actually representing screen = pygame.display.set_mode((w, h)) this, where w = widht and h = height.
This is with pixels, so I want a thicker snake, builded by rects not pixels.
Edit: From nbro's answer, I change this one
self.body.insert(0, (self.x, self.y,self.x, self.y))
So it is a tuple with 4 elements now, but output is very strange..
It's easy to debug your code with a simple print somewhere. I have just put a print under:
def draw(self):
for SEGMENT in self.body:
print(SEGMENT)
self.surface.fill((255,255,255), SEGMENT) #This is where I got error <-----
and I immediately discovered your problem. SEGMENT has to be a rectangle based shape, which means that you have to specify x, y, width and height, but, at certain point, SEGMENT assumes a tuple value composed of 2 elements, which is not correct, it has to be a tuple of 4 elements!
Specifically the output when I get the error is this:
<rect(320, 210, 30, 30)>
<rect(320, 180, 30, 30)>
(320.0, 180.0)
Now I think you can try to see why it assumes that third tuple of 2 elements even without my help.
I'm making a basic pong game (paddle is a rectangle on the bottom of the screen and the ball drops from the top of the screen). I want the ball to bounce back up ONLY when it hits the paddle. So far, I've written code that will make the ball bounce off the top and bottom screen, but I'm having trouble with getting the ball to bounce off the paddle.
I have to modify the parameters that are passed to my test_collide_ball method. If it’s current x values are within the range of the paddle, then it bounces back up.
I've been trying to think of a solution for this, and what I'm thinking is that if the ball hits the paddle's y coordinate (the height), then it bounces back up. But it also has to be within the range of x coordinates that make up the paddle (so the width of the paddle).
But when I do this, the ball just gets stuck in place. Any feedback is appreciated! Thanks in advance.
Here is my code for the ball class/methods:
import pygame
class Ball:
def __init__(self, x, y, radius, color, dx, dy):
self.x = x
self.y = y
self.radius = radius
self.color = color
self.dx = dx
self.dy = dy
def draw_ball(self, screen):
pygame.draw.ellipse(screen, self.color,
pygame.Rect(self.x, self.y, self.radius, self.radius))
def update_ball(self):
self.x += self.dx
self.y += self.dy
def test_collide_top_ball(self, top_height):
if (self.y <= top_height):
self.dy *= -1
def test_collide_bottom_ball(self, paddle):
if (self.y == paddle.y) and (self.x >= paddle.x) and (self.x <= paddle.x + paddle.width):
self.dy *= -1
What appears to be happening is your ball enters the collision zone and reverses it's direction. The ball is still in the collision zone, however, and it reverses it's direction again.
What you should look into is a debounce check. Put simply, this is code that prevents something from happening twice or more times (de-bouncing it).
From your code example, the ball's momentum is reversed when it enters the paddle zone. What you might add is a boolean flag to see if you have already detected that the ball entered the zone. When it is first detected, set the flag to true. When the ball moves outside of the zone, set the flag back to false. Only reverse the ball's momentum if the flag is false.
So, (excusing my rusty Python)
def test_collide_bottom_ball(self, paddle):
if (self.y == paddle.y) and (self.x >= paddle.x) and (self.x <= paddle.x + paddle.width) and (!self.hitPaddle):
self.dy *= -1
self.hitPaddle = true
else
self.hitPaddle = false
And in your entity:
self.hitPaddle = false
Just like #MrDoomBringer is saying, you need to prevent it from getting stuck within the pad.
One easy method to solve that is to check whether self.dy is positive - the ball is moving downwards. This way you could also add the same "within" check for the Y-pos as you did with the X-pos. Otherwise, having a collision with an exact Y-coordinate is pretty hard unless you're using the right speed etc.
Another thing - if you have a ball, you most likely want to add it's size to the equation. Then you might want to use some more fancy collision-techniques, such as this: http://www.migapro.com/circle-and-rotated-rectangle-collision-detection/
I've taken an introductory course in Computer Science, but a short while back I decided to try and make a game. I'm having a problem with collision detection. My idea was to move an object, and if there is a collision, move it back the way it came until there is no longer a collision. Here is my code:
class Player(object):
...
def move(self):
#at this point, velocity = some linear combination of (5, 0)and (0, 5)
#gPos and velocity are types Vector2
self.hitBox = Rect(self.gPos.x, self.gPos.y, 40, 40)
self.gPos += self.velocity
while CheckCollisions(self):
self.gPos -= self.velocity/n #see footnote
self.hitBox = Rect(self.gPos.x, self.gPos.y, 40, 40)
...
def CheckCollisions(obj):
#archList holds all 'architecture' objects, solid == True means you can't walk
#through it. colliderect checks to see if the rectangles are overlapping
for i in archList:
if i.solid:
if i.hitBox.colliderect(obj.hitBox):
return True
return False
*I substituted several different values for n, both integers and floats, to change the increment by which the player moves back. I thought by trying a large float, it would only move one pixel at a time
When I run the program, the sprite for the player vibrates very fast over a range of about 5 pixels whenever I run into a wall. If I let go of the arrow key, the sprite will get stuck in the wall permanently. I wondering why the sprite is inside the wall in the first place, since by the time I blit the sprite to the screen, it should have been moved just outside of the wall.
Is there something wrong with my method, or does the problem lie within my execution?
Looks like you're setting the hitbox BEFORE updating the position. The Fix seems simple.
Find:
self.hitBox = Rect(self.gPos.x, self.gPos.y, 40, 40)
self.gPos += self.velocity
Replace:
self.gPos += self.velocity
self.hitBox = Rect(self.gPos.x, self.gPos.y, 40, 40)
Other Suggestions: What you should do is check the position BEFORE you move there, and if it's occupied, don't move. This is untested so please just use this as psuedocode intended to illustrate the point:
class Player(object):
...
def move(self):
#at this point, velocity = some linear combination of (5, 0)and (5, 5)
#gPos and velocity are types Vector2
selfCopy = self
selfCopy.gPos += self.velocity
selfCopy.hitBox = Rect(selfCopy.gPos.x, selfCopy.gPos.y, 40, 40)
if not CheckCollisions(selfCopy)
self.gPos += self.velocity
...
def CheckCollisions(obj):
#archList holds all 'architecture' objects, solid == True means you can't walk
#through it. colliderect checks to see if the rectangles are overlapping
for i in archList:
if i.solid:
if i.hitBox.colliderect(obj.hitBox):
return True
return False