This question already has answers here:
Setting up an invisible boundary for my sprite
(1 answer)
Use vector2 in pygame. Collide with the window frame and restrict the ball to the rectangular area
(1 answer)
Closed last month.
Greeting, I trying to set the boundaries for players in a game so they do not go passed the boundaries of the screen. They only move in the x-axis.
Bellow is the code for the player class. Right now In the move method I am trying to set a distance relative to the rectangle object(the player) do that it does not go past a certain points to the left and right of the x-axis. However, right now I keep getting the error
if self.rect.left + dx < 0:
AttributeError: 'tuple' object has no attribute 'left'
when I run the code.
class Player():
def __init__(self, x, y, width, height, color):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
self.rect = pygame.Rect((x,y, width, height))
def move(self, screen_width, screen_height):
SPEED = 10
dx = 0
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.x -= SPEED
dx = -SPEED
if keys[pygame.K_RIGHT]:
self.x += SPEED
dx = SPEED
# ensure player stays on screen
if self.rect.left + dx < 0:
self.rect.x += -self.rect.left
if self.rect.right + dx > screen_width:
self.rect.x += screen_width - self.rect.right
self.update()
def update(self):
# update player position
self.rect = (self.x, self.y, self.width, self.height)
Related
This question already has answers here:
How do I detect collision in pygame?
(5 answers)
Closed 24 days ago.
my enemy movement is fine except when colliding with walls it teleports.
video: https://imgur.com/a/tuK0yBg
I have tried to simplify my code by deleting any code that could affect this, e.g. the zombies rotation code etc, however I still can not find the issue. I have no idea why it is teleporting.
Enemy class code:
class Enemy(pygame.sprite.Sprite):
def __init__(self, position):
super().__init__(enemy_group, all_sprites_group)
self.position = pygame.math.Vector2(position)
self.zombie_speed = 2
self.image = pygame.image.load("zombieAssets/skeleton-idle_0.png").convert_alpha()
self.image = pygame.transform.rotozoom(self.image, 0, 0.35)
self.base_zombie_image = self.image
self.base_zombie_rect = self.base_zombie_image.get_rect(center = position)
self.rect = self.base_zombie_rect.copy()
self.velocity = pygame.math.Vector2()
def hunt_player(self):
player_position = player.base_player_rect.center
distance_to_player = player_position - self.position
try:
self.velocity = distance_to_player.normalize() * self.zombie_speed
self.position += self.velocity
self.base_zombie_rect.centerx = self.position.x
self.check_collision("horizontal")
self.base_zombie_rect.centery = self.position.y
self.check_collision("vertical")
self.rect.center = self.base_zombie_rect.center
except:
return
def check_collision(self, direction):
for sprite in obstacles_group:
if sprite.rect.colliderect(self.base_zombie_rect):
if direction == "horizontal":
if self.velocity.x > 0:
self.base_zombie_rect.right = sprite.rect.left
if self.velocity.x < 0:
self.base_zombie_rect.left = sprite.rect.right
if direction == "vertical":
if self.velocity.y < 0:
self.base_zombie_rect.top = sprite.rect.bottom
if self.velocity.y > 0:
self.base_zombie_rect.bottom = sprite.rect.top
def update(self):
self.hunt_player()
It seems to me that in your collision code you are keeping the zombie rect updated properly so as not to phase through the wall, but I see no changes to self.position. I think that if you updated the zombie's actual position in the same way as the rect that the teleportation behavior should stop.
This question already has answers here:
How do I detect collision in pygame?
(5 answers)
How do I prevent the player from moving through the walls in a maze?
(3 answers)
Closed 9 months ago.
I am trying to learn the basics of pygame by making a simple pacman game with a friend, but I have been having trouble figuring out how to make walls and check collision with the pacman and the wall, and also stopping the movement through a wall.
(this is a 2 player pacman, ghost is arrow keys, and pacman is wasd)
This is main.py
import pygame
import random
import time
from Pacman import pacman
from Ghost import ghost
#colors
Yellow = (255,255,0)
Blackish_Blue = (20,0,70)
Red = (255,0,0)
P_B = (40,60,100)
clock = pygame.time.Clock()
#display
pygame.init()
pygame.display.set_caption(' Scuffed Pacman')
width, height = 640, 480
screen = pygame.display.set_mode((width, height))
pacman = pacman(screen)
ghost = ghost(screen)
font = pygame.font.Font('freesansbold.ttf', 32)
text = font.render('Game Over', True, Yellow)
textRect = text.get_rect()
textRect.center = (width / 2, height / 2)
run = True
while run == True:
pygame.time.delay(10)
clock.tick(60)
screen.fill(Blackish_Blue)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
color = (182,163,192)
#attempt to make wall and check collision
ghostspawn = pygame.Rect(0,0,60,40)
ghostspawn.center = (width / 2, (height / 2)-50)
pygame.draw.rect(screen, color, ghostspawn)
if ghostspawn.collidepoint(pacman.x, pacman.y):
print("collision detected")
pacman.draw(screen)
ghost.draw(screen)
ghost.update()
pacman.update()
distance = (((pacman.x - ghost.x)**2) + ((pacman.y - ghost.y)**2))**(1/2)
sumrad = pacman.radius + ghost.radius
if distance < sumrad:
while True:
screen.fill(P_B)
screen.blit(text, textRect)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
pygame.display.update()
pygame.display.update()
pygame.quit()
This is the code for pacman:
import pygame
Yellow = (255,255,0)
class pacman(pygame.sprite.Sprite):
def __init__(self, mainscreen):
super().__init__()
self.x = 320
self.y = 240
self.radius = 15
self.velocity = 2
self.origin = (self.x, self.y)
def draw(self, mainscreen):
pygame.draw.circle(mainscreen, Yellow, (self.x, self.y), self.radius)
def update(self):
self.movement()
self.origin = (self.x, self.y)
def movement(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_d] and self.velocity > 0 and self.x < 625:
self.x += self.velocity
if keys[pygame.K_a] and self.velocity > 0 and self.x > 15:
self.x -= self.velocity
if keys[pygame.K_w] and self.velocity > 0 and self.y > 15:
self.y -= self.velocity
if keys[pygame.K_s] and self.velocity > 0 and self.y < 465:
self.y += self.velocity
and this is the code for ghost:
import pygame
Red = (255,0,0)
class ghost(pygame.sprite.Sprite):
def __init__(self, mainscreen):
super().__init__()
self.x = 213
self.y = 240
self.radius = 15
self.velocity = 2
self.origin = (self.x, self.y)
def draw(self, mainscreen):
pygame.draw.circle(mainscreen, Red, (self.x, self.y), self.radius)
def update(self):
self.movement()
self.origin = (self.x, self.y)
def movement(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_RIGHT] and self.velocity > 0 and self.x < 625:
self.x += self.velocity
if keys[pygame.K_LEFT] and self.velocity > 0 and self.x > 15:
self.x -= self.velocity
if keys[pygame.K_UP] and self.velocity > 0 and self.y > 15:
self.y -= self.velocity
if keys[pygame.K_DOWN] and self.velocity > 0 and self.y < 465:
self.y += self.velocity
this is my attempt at checking for wall collision, but I am unaware of how to stop pacman and the ghost from going through. the collision also only checks the middle of the circle.
ghostspawn = pygame.Rect(0,0,60,40)
ghostspawn.center = (width / 2, (height / 2)-50)
pygame.draw.rect(screen, color, ghostspawn)
if ghostspawn.collidepoint(pacman.x, pacman.y):
print("allowed")
here is how i detect collision between pacman and ghost, as it may be helpful for doing it with walls, but i don't know how.
distance = (((pacman.x - ghost.x)**2) + ((pacman.y - ghost.y)**2))**(1/2)
sumrad = pacman.radius + ghost.radius
ive looked at a few similar questions on here, but I can't quite grasp how it works.
So, if anyone could help, I'm having trouble with checking collision between a circle and rectangle, and also preventing the circle/pacman from moving through.
To check if a circle is colliding with a wall you just need to do 2 things
Get the distance between the circle and the wall
Then check if the distance is smaller or equal to the circles distance
Pretty much like this:
distance = abs(circle.x - wall.x) + abs(circle.y - wall.y)
if distance <= circle.radius: # abs() Makes the value in the brackets positive
circle.x = oldX
circle.y = oldY
To implement this I with your game would first add a self.oldX and a self.oldY in the self.__init__() in both of the pacman and ghost classes. And I would set them to 0 for now.
Then I would update the oldX and oldY in the movement function before I move the object, like this:
def movement(self):
keys = pygame.key.get_pressed()
self.oldX = self.x
self.oldY = self.y
if keys[pygame.K_d] and self.velocity > 0 and self.x < 625:
self.x += self.velocity
if keys[pygame.K_a] and self.velocity > 0 and self.x > 15:
self.x -= self.velocity
if keys[pygame.K_w] and self.velocity > 0 and self.y > 15:
self.y -= self.velocity
if keys[pygame.K_s] and self.velocity > 0 and self.y < 465:
self.y += self.velocity
Then I would have a list that containes every wall in the game and a list that containes every ghost in the game (That's if you want to have more than one ghost).
Then I would go in the main loop (The while loop that you have) and I would add this After calling the movement function of the ghosts and the pacman:
for wall in walls:
distance = abs(pacman.x - wall.x) + abs(pacman.y - wall.y)
if distance <= pacman.radius:
pacman.x = pacman.oldX
pacman.y = pacman.oldY
for ghost in ghosts:
distance = abs(ghost.x - wall.x) + abs(ghost.y - wall.y)
if distance <= ghost.radius:
ghost.x = ghost.oldX
ghost.y = ghost.oldY
and that should work, Thanks.
This question already has answers here:
how to center image inside a rect pygame
(1 answer)
Drag multiple sprites with different "update ()" methods from the same Sprite class in Pygame
(2 answers)
How to drag multiple images in PyGame? [duplicate]
(1 answer)
Closed 2 years ago.
I'm making a tower defense game in pygame where I'm trying to make a Drag&Drop interface where the player can pick the tower and place it in any desired place. I then advanced the game by making a circle around each tower which denotes the range of it. Now the problem here is that I need the circle to be in the center. As I have used classes for the towers, adding to the x and y coordinates did not help because I had different sized towers. I just need to know how to find the center of an image in pygame. Help will be appreciated. Thanks!
Here is the classes part of my code
class DragProperty():
def __init__(self, img, x, y):
self.img = img
self.x = x
self.y = y
self.width = img.get_width()
self.height = img.get_height()
self.dragging = False
def draw(self):
screen.blit(self.img, (self.x, self.y))
def startdrag(self):
x = self.x
y = self.y
if mousex > self.x and mousex < self.x + self.width:
if mousey > self.y and mousey < self.y + self.height:
self.dragging = True
self.offset_x = x - mousex
self.offset_y = y - mousey
def beingdragged(self):
if self.dragging:
self.x = mousex + self.offset_x
self.y = mousey + self.offset_y
def stopdrag(self):
global balance
global canDrag
self.dragging = False
canDrag = False
balance -= 250
class AttackModule(DragProperty):
def EnenmyinRange(self):
pygame.draw.circle(screen, (0, 0, 0, 0), (self.x + 20, self.y + 20), 100)
def attack(self):
global balance
balance += 50
#objects
MenuArcher = AttackModule(archertower, 5, 580)
Since you already have width and height defined, you can do:
self.x + self.width / 2 and self.y + self.height / 2
There seems to be a weird offset when the bullets are shot from the ship in certain angles while the ship is moving. Also if the ship is shooting in the same direction is heading then the bullet speed is lower.
I tried to work with some SO answers and that's what I came up with:
import sys
import pygame
from pygame.locals import *
vec = pygame.math.Vector2
pygame.init()
FPS = 60
fps_clock = pygame.time.Clock()
WIDTH = 800
HEIGHT = 800
DISPLAY = pygame.display.set_mode((WIDTH, HEIGHT))
BLACK = (0, 0, 0)
BLUE = (0, 0, 255)
MAX_SPEED = 7
class Player(pygame.sprite.Sprite):
"""This class represents the Player."""
def __init__(self):
"""Set up the player on creation."""
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((70, 50), pygame.SRCALPHA)
pygame.draw.polygon(self.image, (50, 120, 180), ((35, 0), (0, 35), (70, 35)))
self.original_image = self.image
self.position = vec(WIDTH / 2, HEIGHT / 2)
self.rect = self.image.get_rect(center=self.position)
self.vel = vec(0, 0)
self.acceleration = vec(0, -0.2) # The acceleration vec points upwards.
self.angle_speed = 0
self.angle = 0
def update(self):
"""Update the player's position."""
keys = pygame.key.get_pressed()
if keys[K_LEFT]:
self.angle_speed = -2
player.rotate()
if keys[K_RIGHT]:
self.angle_speed = 2
player.rotate()
# If up is pressed, accelerate the ship by
# adding the acceleration to the velocity vector.
if keys[K_UP]:
self.vel += self.acceleration
if keys[K_SPACE]:
player.shoot()
# max speed
if self.vel.length() > MAX_SPEED:
self.vel.scale_to_length(MAX_SPEED)
self.position += self.vel
self.rect.center = self.position
def rotate(self):
# rotate the acceleration vector
self.acceleration.rotate_ip(self.angle_speed)
self.angle += self.angle_speed
if self.angle > 360:
self.angle -= 360
elif self.angle < 0:
self.angle += 360
self.image = pygame.transform.rotate(self.original_image, -self.angle)
self.rect = self.image.get_rect(center=self.rect.center)
def wrap_around_screen(self):
"""Wrap around screen."""
if self.position.x > WIDTH:
self.position.x = 0
if self.position.x < 0:
self.position.x = WIDTH
if self.position.y <= 0:
self.position.y = HEIGHT
if self.position.y > HEIGHT:
self.position.y = 0
def shoot(self):
# create and add missile object to the group
missile = Missile(self.rect.center, self.acceleration, player.acceleration.as_polar()[1])
all_sprites.add(missile)
missiles.add(missile)
class Missile(pygame.sprite.Sprite):
"""This class represents the bullet.
A missile launched by the player's ship.
"""
def __init__(self, position, direction, angle):
"""Initialize missile sprite.
Take the position, direction and angle of the player.
"""
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([4, 10], pygame.SRCALPHA)
self.image.fill(BLUE)
# Rotate the image by the player.angle
self.image = pygame.transform.rotozoom(self.image, angle, 1)
# Pass the center of the player as the center of the bullet.rect.
self.rect = self.image.get_rect(center=position)
self.position = vec(position) # The position vector.
self.velocity = direction * 50 # Multiply by desired speed.
def update(self):
"""Move the bullet."""
self.position += self.velocity # Update the position vector.
self.rect.center = self.position # And the rect.
if self.rect.x < 0 or self.rect.x > WIDTH or self.rect.y < 0 or self.rect.y > HEIGHT:
self.kill()
all_sprites = pygame.sprite.Group()
player = Player()
all_sprites.add(player)
missiles = pygame.sprite.Group()
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
player.wrap_around_screen()
all_sprites.update()
DISPLAY.fill(BLACK)
all_sprites.draw(DISPLAY)
pygame.display.set_caption('angle {:.1f} accel {} accel angle {:.1f}'.format(
player.angle, player.acceleration, player.acceleration.as_polar()[1]))
pygame.display.update()
fps_clock.tick(FPS)**
Any help would be appreciated
You almost certainly want to have your missiles start with the velocity of the launching ship (in addition to some speed in the direction the ship is facing).
You might replace the direction argument to Missile.__init__ with a velocity parameter that you save directly as self.velocity (with no multiplication). The Player.shoot method can then be modified to pass an appropriate value that takes both the ship's orientation and velocity into account:
def shoot(self):
# create and add missile object to the group
missile = Missile(self.rect.center,
self.velocity + 50 * self.acceleration, # new value here!
player.acceleration.as_polar()[1])
all_sprites.add(missile)
missiles.add(missile)
You might want to use a smaller multiple of ship's the acceleration vector, rather than the 50 I copied from your current code, since it won't be the only component of the missile's velocity any more.
The only problem there is that .rotozoom uses the angles in counter-clockwise, while the .as_polar vector method returns the angle in clockwise direction (one might expect .as_polar to yield an anti-clockwise angle, given what was learned in past math classes, but the Y axis on Surfaces points downwards, while math classes usually had Y pointing upwards).
TL;DR: You just have to invert the angle passed in the call to rotozoom:
self.image = pygame.transform.rotozoom(self.image, -angle, 1)
You probably will also want to swap the width X height of your missile:
self.image = pygame.Surface([10, 4], pygame.SRCALPHA)
I have a method in my Enemy class called huntPlayer. It takes a player object p. Here it is:
def huntPlayer(self, p):
if self.dist2p < 200:
self.hunting = True
if p.x > self.rect.x:
self.rect.x += self.speed #this is a constant at value 1
elif p.x < self.rect.x:
self.rect.x -= self.speed
else:
self.rect.x += 0
if p.y > self.rect.y:
self.rect.y += self.speed
elif p.y < self.rect.y:
self.rect.y -= self.speed
else:
self.rect.y += 0
else:
self.rect.x += 0
self.rect.y += 0
The enemies are randomly placed around a 2d top down plain, they randomly roam this plain. I have calculated the hypotenuse which is shortest distance to the player = Enemy.dist2p -- When the dist2p value < 200. The enemy will move towards player, p.x and p.y respectively.
My solution above is crude so therefore my problem is the enemy equally moves 1 place on the x or y axis resulting in a diagonal movement to each axis, then sliding along the axis until it reaches the player. (The player is in a fixed position near the centre screen.)
Can you help me fix the huntPlayer method/algorithm so the enemy follows the hypotenuse path to the player, rather than quickest path to x/y axis?
EDIT: If you need any further info I may have left out, let me know.
Moving on the hypotenuse will most likely require your object to move less than one pixel each frame in either the y or x-axis, and since rects only can hold integers you'd need a new attribute position which contains the position of the sprite in float precision. You can use pygame.math.Vector2 to create a vector with useful methods such as normalize() and adding, subtracting, multiplying with other vectors etc.
Assuming you've created an attribute self.position = pygame.math.Vector2(0, 0) (or whatever position you want it to start on) you could do something like this:
def hunt_player(self, player):
player_position = pygame.math.Vector2(player.rect.topleft)
direction = player_position - self.position
velocity = direction.normalize() * self.speed
self.position += velocity
self.rect.topleft = self.position
By subtracting the player's position with the enemy's position, you'll get a vector that points from the enemy to the player. If we would to add the direction vector to our position we would teleport to the player immediately. Instead we normalize the vector (making it to length 1 pixel) and multiply our speed attribute. The newly created vector will be an vector pointing towards the player with the length of our speed.
Full example
import pygame
pygame.init()
SIZE = WIDTH, HEIGHT = 720, 480
FPS = 60
BACKGROUND_COLOR = pygame.Color('white')
screen = pygame.display.set_mode(SIZE)
clock = pygame.time.Clock()
class Hunter(pygame.sprite.Sprite):
def __init__(self, position):
super(Hunter, self).__init__()
self.image = pygame.Surface((32, 32))
self.image.fill(pygame.Color('red'))
self.rect = self.image.get_rect(topleft=position)
self.position = pygame.math.Vector2(position)
self.speed = 2
def hunt_player(self, player):
player_position = player.rect.topleft
direction = player_position - self.position
velocity = direction.normalize() * self.speed
self.position += velocity
self.rect.topleft = self.position
def update(self, player):
self.hunt_player(player)
class Player(pygame.sprite.Sprite):
def __init__(self, position):
super(Player, self).__init__()
self.image = pygame.Surface((32, 32))
self.image.fill(pygame.Color('blue'))
self.rect = self.image.get_rect(topleft=position)
self.position = pygame.math.Vector2(position)
self.velocity = pygame.math.Vector2(0, 0)
self.speed = 3
def update(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.velocity.x = -self.speed
elif keys[pygame.K_RIGHT]:
self.velocity.x = self.speed
else:
self.velocity.x = 0
if keys[pygame.K_UP]:
self.velocity.y = -self.speed
elif keys[pygame.K_DOWN]:
self.velocity.y = self.speed
else:
self.velocity.y = 0
self.position += self.velocity
self.rect.topleft = self.position
player = Player(position=(350, 220))
monster = Hunter(position=(680, 400))
running = True
while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
player.update()
monster.update(player)
screen.fill(BACKGROUND_COLOR)
screen.blit(player.image, player.rect)
screen.blit(monster.image, monster.rect)
pygame.display.update()
Result
Since we want to move along the hypotenuse we can use Pythagoras theorem. Here's a brief snippet the should give you the general idea.
I'll use p.x, p.y for the player's position and e.x, e.y for the enemy's position.
# Find the horizontal & vertical distances between player & enemy
dx = p.x - e.x
dy = p.y - e.y
#Get the hypotenuse
d = sqrt(dx*dx + dy*dy)
#Calculate the change to the enemy position
cx = speed * dx / d
cy = speed * dy / d
# Note that sqrt(cx*cx + cy*cy) == speed
# Update enemy position
e.x += cx
e.y += cy
You need to add some extra code to this to make sure that d isn't zero, or you'll get a division by zero error, but that only happens when the enemy reaches the player, so I assume you want to do something special when that happens anyway. :)
I should mention that this technique works best if the positions are floats, not integer pixel coordinates.
Just calculating the distance based on the hypotenuse is not enough. You must pass the coordinates of the enemy into the function and calculate the slope or pass the slope also into the function by value. Then you should move to one of 8 pixels around about your current position where the one you move to is most representative of the path of direction to the enemy. Essentially you move diagonally if the tan of the angle is less than 2 or great that 1/2 otherwise you move in a vertical or horizontal direction. You need to draw a 3x3 set of pixels to see what is actually going on if you can't visualise it.