This question already has answers here:
How to make smooth movement in pygame
(2 answers)
Closed 2 years ago.
I have two blocks, one is controlled by the user. When i move my block, i want the other block to follow me. I tried doing something like this
def follow():
distance = math.hypot(abs(m.x - p.x), abs(m.y - p.y))
angle_radians = math.atan2(abs(m.y - p.y), abs(m.x - p.x))
if distance != 0:
p.y += math.sin(angle_radians)
p.x += math.cos(angle_radians)
However, the block ends up moving in the complete opposite direction to me . Any help would be appreciated.
To make the algorithm work, you have to operate with floating point numbers. If m and p are pygame.Rect objects, then the algorithm won't work, pygame.Rect operates with integral numbers and the fraction part gets lost.
Note math.sin(angle_radians) and math.cos(angle_radians) is <= 1.
That means you have to store the positions of the objects in separate variables. Let's assume you have the floating point coordinates (mx, my) and (py, py)
You have to find the Unit vector from (mx, my) to (px, py).
The unit vector can be found by dividing the vector from (mx, m.y) to (px, py) by its length.
The length of a vector can be computed by the Euclidean distance.
Finally multiply the vector by a scale (step) that is not greater than the distance between the points and add it to the position. e.g:
stepDist = 1
# vector from (`mx`, `my`) to (`px`, `py`)
dx, dy = p.y - mx, py - px
# [Euclidean distance](https://en.wikipedia.org/wiki/Euclidean_distance)
len = math.sqrt(dx*dx + dy*dy)
if len > 0:
# [Unit vector](https://en.wikipedia.org/wiki/Unit_vector)
ndx, ndy = dx/len, dy/len
# minimum of step size and distance to target
step = min(len, stepDist)
# step forward
px += ndx * step
py += ndy * step
If a pygame.Rect object is of need, then the position of the rectangle can be set. e.g:
m.topleft = round(mx), round(my)
p.topleft = round(px), round(py)
But not you have to store the positions in (mx, my) respectively (px, py). If you would do mx, my = m.topleft respectively px, py = p.topleft, then the algorithm will break down, because the fraction component gets lost.
Code works for me if I remove abs() from atan2()
import pygame
import random
import math
# --- constants --- (UPPER_CASE_NAMES)
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
FPS = 25 # for more than 220 it has no time to update screen
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
# --- classes --- (CamelCaseNames)
class Player(pygame.sprite.Sprite):
def __init__(self, x=SCREEN_WIDTH//2, y=SCREEN_HEIGHT//2):
super().__init__()
self.image = pygame.image.load("image.png").convert()
#self.rect = self.image.get_rect(x=x, y=y)
self.rect = self.image.get_rect(centerx=x, centery=y)
def update(self):
#self.rect.centerx = random.randint(0, SCREEN_WIDTH)
#self.rect.centery = random.randint(0, SCREEN_HEIGHT)
move_x = random.randint(-15, 15)
move_y = random.randint(-15, 15)
self.rect.move_ip(move_x,move_y)
def draw(self, surface):
surface.blit(self.image, self.rect)
class Follower(Player):
def update(self, player):
distance = math.hypot(abs(player.rect.x - self.rect.x), abs(player.rect.y - self.rect.y))
angle_radians = math.atan2((player.rect.y - self.rect.y), (player.rect.x - self.rect.x))
if distance != 0:
self.rect.y += 5*math.sin(angle_radians)
self.rect.x += 5*math.cos(angle_radians)
# --- functions --- (lower_case_names)
# --- main ---
pygame.init()
screen = pygame.display.set_mode( (SCREEN_WIDTH, SCREEN_HEIGHT) )
player = Player()
follower = Follower(0, 0)
# --- mainloop ---
clock = pygame.time.Clock()
running = True
while running:
# --- events ---
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYUP:
if event.key == pygame.K_ESCAPE:
running = False
# --- changes/moves/updates ---
if not pygame.key.get_pressed()[pygame.K_SPACE]:
player.update()
follower.update(player)
# --- draws ---
screen.fill(BLACK)
player.draw(screen)
follower.draw(screen)
pygame.display.flip()
# --- FPS ---
ms = clock.tick(FPS)
#pygame.display.set_caption('{}ms'.format(ms)) # 40ms for 25FPS, 16ms for 60FPS
fps = clock.get_fps()
pygame.display.set_caption('FPS: {}'.format(fps))
# --- end ---
pygame.quit()
Related
Im creating a program that will show every image from a folder, i created a function for scrolling because in case there were a lot of images it wouldn't fit the screen, the problem is that after reaching 65536 on the y value, it will start bliting at 0 again, like placing the new names on top of the old ones.
This is a preview code that recreates my problem, what should happen when you press a key is a black screen or maybe a little of green on top, what really happens is that the top half is the y = 0 and the bottom half is y = -65540
import pygame
screen = pygame.display.set_mode((500,500))
colors = pygame.Surface((50,66000))#73825
colors.fill("green", (0,33, 50, 65600))
x = 0
running = True
while running:
pygame.time.Clock().tick(30)
screen.fill("black")
screen.blit(colors, (225,x))
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.KEYUP:
x = 0
if event.type == pygame.KEYDOWN:
x = -65540
if event.type == pygame.QUIT:
running = False
this only happens when using blit as rects work perfectly beyond the 65k pixel
it wouldn't fit the screen, the problem is that after reaching 65536
65535 is 0xffff, which is the maximum number that can be represented by 2 bytes or a variable of type "uint16". Likely this is the internal data type pygame uses to represent a pixel on the screen or the size of a pygame.Surface. This is far enough for any display in the world.
It is not a good idea to create a pygame.Surface of this size. It would be better to draw the parts of the images on the screen that should be displayed in each frame.
Minimal example of a scrollable image grid of images of the same size:
import pygame, random, math
screen = pygame.display.set_mode((500, 500))
scr_w, scr_h = screen.get_size()
img_w, img_h = 300, 200
images = []
for i in range(98):
img = pygame.Surface((img_w, img_h))
img.fill([random.randrange(256) for _ in range(3)])
images.append(img)
columns = 10
rows = math.ceil(len(images) / columns)
offset_x = 0
offset_y = 0
def fill_display():
row = offset_y // img_h
display_y = (img_h - (offset_y % img_h)) - img_h
while display_y < scr_w:
col = offset_x // img_w
display_x = (img_w - (offset_x % img_w)) - img_w
while display_x < scr_h:
i = row * columns + col
if i < len(images):
screen.blit(images[i], (display_x, display_y))
display_x += img_w
col += 1
display_y += img_h
row += 1
clock = pygame.time.Clock()
running = True
while running:
clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
keys = pygame.key.get_pressed()
offset_x += (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * 20
offset_y += (keys[pygame.K_DOWN] - keys[pygame.K_UP]) * 20
offset_x = min((columns) * img_w - scr_w, max(0, offset_x))
offset_y = min((rows) * img_h - scr_h, max(0, offset_y))
screen.fill("black")
fill_display()
pygame.display.update()
In my pygame-code, I have a drone that is supposed to follow a flight path.
I used pygame.draw.lines to draw lines between specified points. Now, I have a flight path with 10 points where after each point the path angle changes (a bit like a zigzag). The player can move the drone by pressing the keys.
My goal is to print a warning once the drone deviates from the path, e.g. by +/-30. I have been racking my brain for two days but can't come up with a condition to detect a deviation. I just can't figure out how to approach this.
I can determine the drone's x-coordinate at any time but how do I determine the offset from the path? I have attached an image to visualize my problem.
Edit:
As I am a beginner my code is a mess but when copy-pasting it, I guess only the lines 35-91 are interesting. Thank you for any kind of advice in advance!!
import pygame
import pygame.gfxdraw
import random
import sys
import math
pygame.init()
# Define some colors
black = (0,0,0)
white = (255,255,255)
red = (255,0,0)
red_transp = (255,0,0, 150)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
X = 0
Y = 250
#Display
display_width, display_height = 1200, 700
h_width, h_height = display_width/2, display_height/2
gameDisplay = pygame.display.set_mode((display_width,display_height))
pygame.display.set_caption('Game Display')
#Drone Sprite Image Load Function
droneImg_interim = pygame.image.load('drone.png')
droneImg = pygame.transform.scale(droneImg_interim, [50,50])
drone_width, drone_height = droneImg.get_rect().size
#Create 11 Waypoints with the same coordinates
p1=[X, Y]
p2=[X, Y]
p3=[X, Y]
p4=[X, Y]
p5=[X, Y]
p6=[X, Y]
p7=[X, Y]
p8=[X, Y]
p9=[X, Y]
p10=[X, Y]
p11=[X, Y]
pointlist = [p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11]
x_min=drone_width
x_max=100
#Setting new x-coordinate for each point
for i in pointlist:
i[0] = random.randrange(x_min, x_max)
x_min+=250
x_max+=250
#Setting new y-coordinate for each point
for i in range(len(pointlist)):
if i == 0:
pointlist[i][1] = random.randrange(200, 400)
else:
prev = pointlist[i-1][1]
pointlist[i][1] = random.randrange(200, prev+100)
#Plotting pointlist on gameDisplay and connecting dots
def flightpath(pointlist):
pygame.draw.lines(gameDisplay, (255, 0, 0), False, pointlist, 2)
def margin(x):
for i in range(len(pointlist)-1):
p1_x = pointlist[i][0]
p2_x = pointlist[i+1][0]
p1_y = pointlist[i][1]
p2_y = pointlist[i+1][1]
distance_x = p2_x - p1_x
distance = math.sqrt((p2_x-p1_x)**2+(p2_y-p1_y)**2)
halfwaypoint_x = math.sqrt((p2_x - p1_x)**2)/2 + p1_x
halfwaypoint_y = math.sqrt((p2_y - p1_y)**2)/2 + p1_y
if p2_y < p1_y:
angle_rad = math.acos(distance_x/distance)
elif p2_y > p1_y:
angle_rad = 0 - math.acos(distance_x/distance)
angle_deg = math.degrees(angle_rad)
rect_width = distance
rect_height = 60
"""
This part of the code is meant for displaying the margins (the rectangles) around the flight path on the display.
marginSize = (rect_width, rect_height)
surface = pygame.Surface(marginSize, pygame.SRCALPHA)
surface.fill((255,0,0,25))
rotated_surface = pygame.transform.rotate(surface, angle_deg)
#new_rect = rotated_surface.get_rect(center = surface.get_rect(center = ((pointlist[i][0], pointlist[i][1]))).center)
new_rect = rotated_surface.get_rect(center = surface.get_rect(center = ((halfwaypoint_x, halfwaypoint_y))).center)
#gameDisplay.blit(rotated_surface, new_rect)
"""
#Placing drone on the screen
def drone(x,y):
rect = droneImg.get_rect ()
rect.center=(x, y)
gameDisplay.blit(droneImg,rect)
def displayMSG(value,ttext,posx,posy):
myFont = pygame.font.SysFont("Verdana", 12)
Label = myFont.render(ttext, 1, black)
Value = myFont.render(str(value), 1, black)
gameDisplay.blit(Label, (posx, posy))
gameDisplay.blit(Value, (posx + 100, posy))
#Main Loop Object
def game_loop():
global X, Y, FThrustX, FThrustY, FDragY, Time
FThrustY = 0
gameExit = False
while not gameExit:
#Event Checker (Keyboard, Mouse, etc.)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
pygame.quit()
sys.exit()
keys = pygame.key.get_pressed() #checking pressed keys
if keys[pygame.K_LEFT]:
X -= 1
if keys[pygame.K_RIGHT]:
X +=1
if keys[pygame.K_DOWN]:
Y += 1
if keys[pygame.K_UP]:
Y -=1
#Display Background Fill
gameDisplay.fill(white)
#Plot flightpath
flightpath(pointlist)
#YS: Determine the position of the mouse
current_pos_x, current_pos_y = pygame.mouse.get_pos()
displayMSG(current_pos_x,'x:',20,665)
displayMSG(current_pos_y,'y:',20,680)
#Plot margin
margin(5)
#Move Drone Object
drone(X,Y)
#Determine the position of the mouse
current_pos_x, current_pos_y = pygame.mouse.get_pos()
#No exceeding of display edge
if X > display_width - drone_width: X = display_width - drone_width
if Y > display_height - drone_height: Y = display_height - drone_height
if X < drone_width: X = drone_width
if Y < drone_height: Y = drone_height
pygame.display.update()
#MAIN
game_loop()
pygame.quit()
sys.exit()
One approach is to find the minimum distance between the center of the drone and the line.
Write the function that calculates the minimum distance from a point to a line segment. To do this, use pygame.math.Vector2 and the Dot product:
def distance_point_linesegment(pt, l1, l2):
LV = pygame.math.Vector2(l2[0] - l1[0], l2[1] - l1[1])
PV = pygame.math.Vector2(pt[0] - l1[0], pt[1]- l1[1])
dotLP = LV.dot(PV)
if dotLP < 0:
return PV.length()
if dotLP > LV.length_squared():
return pygame.math.Vector2(pt[0] - l2[0], pt[1]- l2[1]).length()
NV = pygame.math.Vector2(l1[1] - l2[1], l2[0] - l1[0])
return abs(NV.normalize().dot(PV))
Find the line segment with the shortest distance in a loop:
def minimum_distance(pt, pointlist):
min_dist = -1
for i in range(len(pointlist)-1):
dist = distance_point_linesegment(pt, pointlist[i], pointlist[i+1])
if i == 0 or dist < min_dist:
min_dist = dist
return min_dist
Create an alert when the distance exceeds a certain threshold:
def game_loop():
# [...]
while not gameExit:
# [...]
dist_to_path = minimum_distance((X, Y), pointlist)
if dist_to_path > 25:
pygame.draw.circle(gameDisplay, (255, 0, 0), (X, Y), 25, 4)
drone(X,Y
# [...]
Another possible solution is to use pygame.Rect.clipline and detect the collision of the line segments and the rectangle surrounding the drone:
def intersect_rect(rect, pointlist):
for i in range(len(pointlist)-1):
if rect.clipline(pointlist[i], pointlist[i+1]):
return True
return False
def game_loop():
# [...]
while not gameExit:
# [...]
if not intersect_rect(droneImg.get_rect(center = (X, Y)), pointlist):
pygame.draw.circle(gameDisplay, (255, 0, 0), (X, Y), 25, 4)
drone(X,Y
# [...]
The interesting part of the question is of course finding the nearest point on the desired path to the actual position; distance is easy. The hard part of that is in turn identifying the nearest element (line segment) of the path; projecting onto it is also straightforward.
If the path is simple enough (in particular, if it doesn’t branch and it’s impossible/disallowed to skip sections at a self-intersection), you can finesse that part by just maintaining that current element in a variable and updating it to the previous or next element when the projection onto one of them is closer than the projection onto the current one. This is a typical algorithm used by racing games to determine the instantaneous order of racers.
This question already has answers here:
How to detect collisions between two rectangular objects or images in pygame
(1 answer)
How do I detect collision in pygame?
(5 answers)
Closed 2 years ago.
Hi sorry for the many questions but again I know python but am trying to learn pygame. In the game I'm making im trying to make it so when a block/image touches the sprite it goes back to the top of the screen. I have looked at many tutorials but can't seem to figure it our in a way that would work good. Any advice? Heres the code thanks!!!:
import pygame
import sys
from random import randint
import os
x = 250
y = 30
os.environ["SDL_VIDEO_WINDOW_POS"] = "%d,%d" % (x, y)
width = 1024
height = 768
icon1 = pygame.image.load("Firstpygamegame/santa-claus.png")
pygame.display.set_icon(icon1)
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Gift Catcher")
background_image = pygame.image.load("Firstpygamegame/wintervillage.png")
sprite1 = pygame.image.load("Firstpygamegame/santabag2.png")
spriterect = sprite1.get_rect()
speed = 2.5
# 442 or 467
spriterect.x = 442
icon2 = pygame.image.load("Firstpygamegame/present1.png")
icon3 = pygame.image.load("Firstpygamegame/present2.png")
icon4 = pygame.image.load("Firstpygamegame/present3.png")
icon5 = pygame.image.load("Firstpygamegame/present4.png")
cubes = [[
randint(1, 1000), # X coordinate
randint(-1500, -350)] # Y coordinate, -Y is above screen (top of screen is zero)
for x in range(2)] # 20 cubes
cubes1 = [[
randint(1, 1000), # X coordinate
randint(-1500, -150)] # Y coordinate, -Y is above screen (top of screen is zero)
for x in range(2)] # 20 cubes
cubes2 = [[
randint(1, 1000), # X coordinate
randint(-1500, -550)] # Y coordinate, -Y is above screen (top of screen is zero)
for x in range(2)] # 20 cubes
cubes3 = [[
randint(1, 1000), # X coordinate
randint(-1500, -450)] # Y coordinate, -Y is above screen (top of screen is zero)
for x in range(2)] # 20 cubes
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.quit()
sys.exit()
keys = pygame.key.get_pressed()
spriterect.x += (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * speed
spriterect.y = 600
screen.blit(background_image, (0, 0))
screen.blit(sprite1, spriterect)
for cb in cubes:
cb[1] += .25 # cube moves down 2 pixels
screen.blit(icon2, cb) # draw cube
if cb[1] > 800: # if cube passed bottom of screen
cb[1] = -100 # move to above screen
cb[0] = randint(1, 1000)
for cb in cubes1:
cb[1] += .25 # cube moves down 2 pixels
screen.blit(icon3, cb) # draw cube
if cb[1] > 800: # if cube passed bottom of screen
cb[1] = -100 # move to above screen
cb[0] = randint(1, 1000) # random X position
for cb in cubes2:
cb[1] += .25 # cube moves down 2 pixels
screen.blit(icon4, cb) # draw cube
if cb[1] > 800: # if cube passed bottom of screen
cb[1] = -100 # move to above screen
cb[0] = randint(1, 1000) # random X position
for cb in cubes3:
cb[1] += .25 # cube moves down 2 pixels
screen.blit(icon5, cb) # draw cube
if cb[1] > 800: # if cube passed bottom of screen
cb[1] = -100 # move to above screen
cb[0] = randint(1, 1000) # random X position
pygame.display.flip()
Use a pygame.Rect object and colliderect() to find a collision between a rectangle and an object.
pygame.Surface.get_rect.get_rect() returns a rectangle with the size of the Surface object, that always starts at (0, 0) since a Surface object has no position. The position of the rectangle can be specified by a keyword argument:
while running:
# [...]
for cb in cubes:
cb[1] += .25 # cube moves down 2 pixels
screen.blit(icon2, cb) # draw cube
icon_rect = icon2.get_rect(topleft = cb)
if cb[1] > 800 or icon_rect.colliderect(spriterect):
cb[1] = -100 # move to above screen
cb[0] = randint(1, 1000)
# [...]
However, you can simplify your code:
icon_list = [icon2, icon3, icon4, icon5]
cube_lists = [[[
randrange(screen.get_width() - icon.get_width()),
randint(-1500, -350)]
for x in range(2)]
for icon in icon_list]
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
keys = pygame.key.get_pressed()
spriterect.x += (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * speed
spriterect.y = 600
screen.blit(background_image, (0, 0))
screen.blit(sprite1, spriterect)
for icon, cubes in zip(icon_list, cube_lists):
for cb in cubes:
cb[1] += .25 # cube moves down 2 pixels
screen.blit(icon, cb) # draw cube
icon_rect = icon.get_rect(topleft = cb)
if cb[1] > screen.get_height() or icon_rect.colliderect(spriterect):
cb[:] = randrange(screen.get_width() - icon.get_width()), -800
pygame.display.flip()
pygame.quit()
sys.exit()
Please note, the application loop terminates if running is False. There is no point in setting running = False and to call pygame.quit() and sys.exit() in the event loop. Let the loop run to the end and call pygame.quit() and sys.exit() after the event loop.
This question already has answers here:
How to make smooth movement in pygame
(2 answers)
How can i make a block follow another block in pygame [duplicate]
(2 answers)
Closed 2 years ago.
So I followed the answers in another question asked on StackOverflow but it seems that I have missed something. I went ahead after reading the answer and copied the code and adjusted it to my variables and class names.
The following is the error code that Idle gives me:
Traceback (most recent call last):
File "D:\Programme (x86)\Python\Games\Zombie Game\Zombie Game_Test1.py", line 133, in <module>
Zombie.move_towards_Char(Char)
TypeError: move_towards_Char() missing 1 required positional argument: 'Char'
This is where I looked:
How to make an enemy follow the player in pygame?
import pygame
import turtle
import time
import math
import random
import sys
import os
pygame.init()
WHITE = (255,255,255)
GREEN = (0,255,0)
RED = (255,0,0)
BLUE = (0,0,255)
BLACK = (0,0,0)
BGColor = (96,128,56)
ZColor = (225,0,0)
PColor = (0,0,255)
MOVE = 2.5
size = (1920, 1080)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Zombie Game")
class Char(pygame.sprite.Sprite):
def __init__(self, color, pos, radius, width):
super().__init__()
self.image = pygame.Surface([radius*2, radius*2])
self.image.fill(WHITE)
self.image.set_colorkey(WHITE)
pygame.draw.circle(self.image, color, [radius, radius], radius, width)
self.rect = self.image.get_rect()
def moveRightP(self, pixels):
self.rect.x += pixels
pass
def moveLeftP(self, pixels):
self.rect.x -= pixels
pass
def moveUpP(self, pixels):
self.rect.y -= pixels
pass
def moveDownP(self, pixels):
self.rect.y += pixels
pass
class Zombie(pygame.sprite.Sprite):
def __init__(self2, color, pos, radius, width):
super().__init__()
self2.image = pygame.Surface([radius*2, radius*2])
self2.image.fill(WHITE)
self2.image.set_colorkey(WHITE)
pygame.draw.circle(self2.image, color, [radius, radius], radius, width)
self2.rect = self2.image.get_rect()
self2.rect.center = pos
def move_towards_Char(self2, Char):
dx, dy = self2.rect.x - Char.rect.x, self2.rect.y - Char.rect.y
dist = math.hypot(dx, dy)
dx, dy = dx / dist, dy / dist
self2.rect.x += dx * self2.speed
self2.rect.y += dy * self2.speed
def moveRightZ(self2, pixels):
self2.rect.x += pixels
pass
def moveLeftZ(self2, pixels):
self2.rect.x -= pixels
pass
def moveUpZ(self2, pixels):
self2.rect.y -= pixels
pass
def moveDownZ(self2, pixels):
self2.rect.y += pixels
pass
all_sprites_list = pygame.sprite.Group()
playerChar = Char(PColor, [0, 0], 15, 0)
playerChar.rect.x = 960
playerChar.rect.y = 505
all_sprites_list.add(playerChar)
carryOn = True
clock = pygame.time.Clock()
zombie_list = []
zombie_rad = 15
zombie_dist = (200, 900)
next_zombie_time = pygame.time.get_ticks() + 10000
zombie_list = []
zombie_rad = 15
zombie_dist = (200, 900)
next_zombie_time = 10000
while carryOn:
for event in pygame.event.get():
if event.type==pygame.QUIT:
carryOn=False
elif event.type==pygame.KEYDOWN:
if event.key==pygame.K_x:
carryOn=False
keys = pygame.key.get_pressed()
if keys[pygame.K_a]:
playerChar.moveLeftP(MOVE)
if keys[pygame.K_d]:
playerChar.moveRightP(MOVE)
if keys[pygame.K_w]:
playerChar.moveUpP(MOVE)
if keys[pygame.K_s]:
playerChar.moveDownP(MOVE)
current_time = pygame.time.get_ticks()
if current_time > next_zombie_time:
next_zombie_time = current_time + 2000
on_screen_rect = pygame.Rect(zombie_rad, zombie_rad, size[0]-2*zombie_rad, size[1]-2*zombie_rad)
zombie_pos = (-1, -1)
while not on_screen_rect.collidepoint(zombie_pos):
dist = random.randint(*zombie_dist)
angle = random.random() * math.pi * 2
p_pos = (playerChar.rect.centerx, playerChar.rect.centery)
zombie_pos = (p_pos[0] + dist * math.sin(angle), p_pos[1] + dist * math.cos(angle))
new_pos = (random.randrange(0, size[0]), random.randrange(0, size[1]))
new_zombie = Zombie(RED, zombie_pos, zombie_rad, 0)
zombie_list.append(new_zombie)
screen.fill(BGColor)
screen.blit(playerChar.image,playerChar.rect)
for zombie in zombie_list:
screen.blit(zombie.image,zombie.rect)
pygame.display.flip()
clock.tick(60)
pygame.quit()
The major issue is, that you do the zombie movement calculations with integral data types. If the movement of a zombie is 1 pixel and the movement is diagonal, then the x and y component of the movement is < 1. Using an integral data type, this may results in 0 movement, because of truncating to int. Note the members of pygame.Rect are integral values.
You've to switch to floating point values to solve the issue. Use pygame.math.Vector2 to do the calculations.
Add a member pos of type Vector2 to the class Zombie which stores the floating point position of the zombie:
class Zombie(pygame.sprite.Sprite):
def __init__(self2, color, pos, radius, width):
super().__init__()
self2.image = pygame.Surface([radius*2, radius*2])
self2.image.fill(WHITE)
self2.image.set_colorkey(WHITE)
pygame.draw.circle(self2.image, color, [radius, radius], radius, width)
self2.rect = self2.image.get_rect()
self2.speed = 1
self2.pos = pygame.Vector2(pos[0], pos[1])
# [...]
Add a new method draw to the class Zombie, which draws (blit) a zombie at the position pos:
class Zombie(pygame.sprite.Sprite):
# [...]
def draw(self2):
self2.rect.center = (int(round(self2.pos.x)), int(round(self2.pos.y)))
screen.blit(self2.image, self2.rect)
Do the calculation of the movement of the zombie based on Vector2. Ensure that the distance between the player and the zombie is greater than 0 and that the zombie does not step over of the position of the player (min(len, self2.speed)):
class Zombie(pygame.sprite.Sprite):
# [...]
def move_towards_Char(self2, Char):
deltaVec = pygame.Vector2(Char.rect.center) - self2.pos
len = deltaVec.length()
if len > 0:
self2.pos += deltaVec/len * min(len, self2.speed)
Call the methods move_towards_Char and draw for each zombie, in the main loop of the application:
while carryOn:
for event in pygame.event.get():
if event.type==pygame.QUIT:
carryOn=False
elif event.type==pygame.KEYDOWN:
if event.key==pygame.K_x:
carryOn=False
keys = pygame.key.get_pressed()
if keys[pygame.K_a]:
playerChar.moveLeftP(MOVE)
if keys[pygame.K_d]:
playerChar.moveRightP(MOVE)
if keys[pygame.K_w]:
playerChar.moveUpP(MOVE)
if keys[pygame.K_s]:
playerChar.moveDownP(MOVE)
current_time = pygame.time.get_ticks()
if current_time > next_zombie_time:
next_zombie_time = current_time + 2000
on_screen_rect = pygame.Rect(zombie_rad, zombie_rad, size[0]-2*zombie_rad, size[1]-2*zombie_rad)
zombie_pos = (-1, -1)
while not on_screen_rect.collidepoint(zombie_pos):
dist = random.randint(*zombie_dist)
angle = random.random() * math.pi * 2
p_pos = (playerChar.rect.centerx, playerChar.rect.centery)
zombie_pos = (p_pos[0] + dist * math.sin(angle), p_pos[1] + dist * math.cos(angle))
new_pos = (random.randrange(0, size[0]), random.randrange(0, size[1]))
new_zombie = Zombie(RED, zombie_pos, zombie_rad, 0)
zombie_list.append(new_zombie)
# update all the positions of the zombies
for zombie in zombie_list:
zombie.move_towards_Char(playerChar)
screen.fill(BGColor)
screen.blit(playerChar.image,playerChar.rect)
# draw all the zombies
for zombie in zombie_list:
zombie.draw()
pygame.display.flip()
clock.tick(60)
L{Zombie.move_towards_Char} is a self method. You need to create object of Zombie class passing the required args mentioned in L{Zombie.init}.
Something like below:
zm = Zombie(color=<color>, pos=<pos>, radius=<radius>, width=<width>)
zm.move_towards_Char(Char)
So I'm trying to make a game where there are 2 objects and am trying to make it so that my program knows when the 2 objects collide.
if players[0].mask.overlap(players[1].mask, offset):
print("collided")`
My objects both rotate clockwise and counterclockwise and have masks both for the head and the body. (I have 2 images which are both the same size but one contains only the head and another contains only the body, in order to use mask.from_surface().)
def turn(self, event):
self.surface = self.ojSurface
if event == 0:
self.deg -= self.turnSpd
elif event == 1:
self.deg += self.turnSpd
self.surface = pygame.transform.rotate(self.surface, self.deg)
self.newPos = (self.pos[0] - self.surface.get_width() / 2, self.pos[1] - self.surface.get_height() / 2)
self.mask = pygame.mask.from_surface(self.surface)
However, what I find is when I try to check for collision when they are rotated the mask isn't in the same place as the image. It should update the mask every frame through the move function which is called every loop
def move(self):
self.pos[0] = pygame.mouse.get_pos()[0]
self.pos[1] = pygame.mouse.get_pos()[1]
self.newPos = (self.pos[0] - self.surface.get_width() / 2, self.pos[1] - self.surface.get_height() / 2)
self.mask = pygame.mask.from_surface(self.surface)
Faulty Hit Detection
If you want to peruse what I have so far here it is:
import pygame
class Player:
def __init__(self, pos,surface,screen):
self.pos = [pos[0],pos[1]]
self.newPos = (pos[0] - surface.get_width()/2, pos[1] - surface.get_height()/2)
self.deg = 0
self.surface = surface
self.ojSurface = surface
self.screen = screen
self.mask = pygame.mask.from_surface(self.surface)
def turn(self, event):
self.surface = self.ojSurface
#clockwise
if event == 0:
self.deg -= 0.1
#counter clockwise
elif event == 1:
self.deg += 0.1
self.surface = pygame.transform.rotate(self.surface, self.deg)
#resetting pos and mask
self.newPos = (self.pos[0] - self.surface.get_width() / 2, self.pos[1] - self.surface.get_height() / 2)
self.mask = pygame.mask.from_surface(self.surface)
def move(self):
self.pos[0] = pygame.mouse.get_pos()[0]
self.pos[1] = pygame.mouse.get_pos()[1]
self.newPos = (self.pos[0] - self.surface.get_width() / 2, self.pos[1] - self.surface.get_height() / 2)
self.mask = pygame.mask.from_surface(self.surface)
def draw(self):
self.screen.blit(self.surface, self.newPos)
screenRes = (640,480)
screen = pygame.display.set_mode(screenRes)
closed = False
players = [Player((320,240),pygame.image.load("body.png"), screen),Player((480,240),pygame.image.load("body.png"), screen)]
controls = [[pygame.K_a,pygame.K_s],[pygame.K_k,pygame.K_l]]
while not closed:
screen.fill((0, 0, 0))
keys = pygame.key.get_pressed()
offset = (int(players[0].newPos[0] - players[1].newPos[0]), int(players[0].newPos[1] - players[1].newPos[1]))
#collision
if players[0].mask.overlap(players[1].mask, offset):
print("collided")
#controls
for i in range(len(players)):
if keys[controls[i][0]]:
players[i].turn(0)
if keys[controls[i][1]]:
players[i].turn(1)
players[i].draw()
players[0].move()
pygame.display.update()
for event in pygame.event.get():
# standard quit
if event.type == pygame.QUIT:
pygame.display.quit()
closed = True
pygame.quit()
You're calculating the offset in the wrong order. Subtract players[0]'s position from players[1]'s position:
offset = (
int(players[1].newPos[0] - players[0].newPos[0]),
int(players[1].newPos[1] - players[0].newPos[1]),
)
So I've reviewed your GitHub code, and while I'm not completely sure (I skimmed your code, looking for certain methods being called), I believe that you're not updating your masks every frame. Somewhere in your code, you'll have to update the masks every frame/loop, allowing for the collision to work properly. Try to update all the masks, the head, body and the one in the Player class. Hope this helps!