get_rect() over an image gets shifted in pygame - python

I'm trying to do a drag-and-drop mechanic in pygame and I'm being partly successful (thanks to answers to questions like this one and tutorials like this other one). The mechanic I'm using goes as follows: I update in every loop the position of the image once the event of pressing the button is detected (and only if the mouse is over the image). To do so, I created a rectangle object by just calling image.get_rect(), but it seems that this rectangle is shifted, with the center of the image laying in the bottom right of the rectangle. I annex both the code an the result:
import pygame, sys
from pygame.locals import *
FPS = 60
fpsClock = pygame.time.Clock()
def main():
pygame.init()
DS = pygame.display.set_mode((400, 400), 0, 32)
pygame.display.set_caption('Drag-n-drop that cat')
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
catImg = pygame.image.load('cat.png') # I load the image
catImgRectObj = catImg.get_rect() # I create the rect object
catx = 200
caty = 200
catImgRectObj.center = [catx, caty]
IsMousePressed = False
while True:
lastPos = catImgRectObj.center
DS.fill(WHITE)
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == MOUSEBUTTONDOWN:
IsMousePressed = True
elif event.type == MOUSEBUTTONUP:
IsMousePressed = False
if IsMousePressed and isMouseOverObj(catImgRectObj):
catImgRectObj.center = pygame.mouse.get_pos() #I update the center
else:
catImgRectObj.center = lastPos
pygame.draw.rect(DS, BLACK, catImgRectObj) #draw the rect object
DS.blit(catImg, catImgRectObj.center) #draw the cat.
pygame.display.update()
fpsClock.tick(FPS)
def isMouseOverObj(Obj):
return Obj.collidepoint(pygame.mouse.get_pos())
if __name__ == '__main__':
main()

Use
DS.blit(catImg, catImgRectObj)
instead of
DS.blit(catImg, catImgRectObj.center)
to draw the cat.
The catImgRectObj rect already describes where the cat image is, and if you use catImgRectObj.center to blit it on the screen, but shift its top left corner to the center of the desired area.
Also, I would use something like this:
import pygame, sys
from pygame.locals import *
FPS = 60
fpsClock = pygame.time.Clock()
def main():
pygame.init()
DS = pygame.display.set_mode((400, 400), 0, 32)
pygame.display.set_caption('Drag-n-drop that cat')
catImg = pygame.image.load('cat.png').convert_alpha()
catMask = pygame.mask.from_surface(catImg)
catImgRectObj = catImg.get_rect(center=(200, 200))
IsMousePressed = False
while True:
DS.fill(pygame.color.THECOLORS['white'])
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == MOUSEBUTTONDOWN and isMouseOverObj(catMask, catImgRectObj):
IsMousePressed = True
elif event.type == MOUSEBUTTONUP:
IsMousePressed = False
elif event.type == MOUSEMOTION and IsMousePressed:
catImgRectObj.move_ip(event.rel)
DS.blit(catImg, catImgRectObj)
pygame.display.update()
fpsClock.tick(FPS)
def isMouseOverObj(mask, rect):
mouse_pos = pygame.mouse.get_pos()
rel_pos = (mouse_pos[0] - rect.left, mouse_pos[1] - rect.top)
return rect.collidepoint(mouse_pos) and mask.get_at(rel_pos)
if __name__ == '__main__':
main()
to make the collision detection pixel perfect, simplify the code a bit, and to prevent the jumping once you click on the cat.

Related

Pygame screen doesn't seem to refresh

I've been trying to make a Chrome Dino Game, however, I'm struggling with this problem:
On every frame, it should draw a new one at the new position and delete the previous one to make it look as if it's moving. HOWEVER, it remains at its previous position and a new image appears on its next position. I did write the pygame.display.update() code at the end of my maintop.
In the last time I ran into a similar problem, I managed to make it work by drawing a background image, but this time, it doesn't work.
following are my codes:
import pygame
import os
from random import randint
import schedule
pygame.init()
assets = os.path.join(os.path.dirname(__file__), "Assets")
screen_size = (screen_width, screen_height) = (1280, 720)
screen = pygame.display.set_mode(screen_size)
clock = pygame.time.Clock()
fps = 120
bg = pygame.image.load(os.path.join(assets, "IMG_15.png"))
ground = 700
running = True
spacebaridx = 0
gamestart = False
tick_on_start = 0
obs1 = pygame.image.load(os.path.join(assets, "colourmat/light_green.png"))
pygame.transform.scale(obs1, (100, 200))
obs2 = pygame.image.load(os.path.join(assets, "colourmat/light_green.png"))
pygame.transform.scale(obs2, (120, 200))
obs3 = pygame.image.load(os.path.join(assets, "colourmat/light_green.png"))
pygame.transform.scale(obs3, (150, 200))
ls_obs = []
def create_obs():
k = randint(1, 3)
if k == 1:
info = {"type":1, "img":obs1, "x":screen_width, "y":ground - 200, "tox":2}
ls_obs.append(info)
if k == 2:
info = {"type":2, "img":obs2, "x":screen_width, "y":ground - 200, "tox":2}
ls_obs.append(info)
else:
info = {"type":3, "img":obs3, "x":screen_width, "y":ground - 200, "tox":2}
ls_obs.append(info)
schedule.every(3).seconds.do(create_obs)
while running:
dt = clock.tick(fps)
if gamestart == True:
game_ticks = pygame.time.get_ticks() - tick_on_start
schedule.run_pending()
else:
game_ticks = pygame.time.get_ticks()
for event in pygame.event.get():
if event.type == pygame.QUIT: running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
if spacebaridx == 0: # Press space to start / to tell whether it's the first press
spacebaridx += 1
gamestart = True
tick_on_start = pygame.time.get_ticks()
else:
pass # Jump
for o in ls_obs:
o["x"] += o["tox"] * -1
screen.blit(bg, (0, 0))
for o in ls_obs:
screen.blit(o["img"], (o["x"], o["y"]))
pygame.display.update()
pygame.quit()
This issue is occurring because you aren't clearing the display within each frame. In pygame, in order to clear the display, we need to use the fill method. So in your code, at the top of your game loop before the event loop, add screen.fill((0, 0, 0)). This will fill your screen in the color black. Don't worry, the black won't be shown if you draw the background on top of it. Now, when you add a new image, the previous images won’t be displayed.
Modified Game Loop
while running:
screen.fill((0, 0, 0))
dt = clock.tick(fps)
if gamestart == True:
game_ticks = pygame.time.get_ticks() - tick_on_start
schedule.run_pending()
else:
game_ticks = pygame.time.get_ticks()
for event in pygame.event.get():
if event.type == pygame.QUIT: running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
if spacebaridx == 0: # Press space to start / to tell whether it's the first press
spacebaridx += 1
gamestart = True
tick_on_start = pygame.time.get_ticks()
else:
pass # Jump
for o in ls_obs:
o["x"] += o["tox"] * -1
screen.blit(bg, (0, 0))
for o in ls_obs:
screen.blit(o["img"], (o["x"], o["y"]))
pygame.display.update()
pygame.quit()

Make pygame draw cubes wherever the user clicks

I want to make a kind of "level editor" as an exercise.
I've got this code:
import pygame
running = True
pygame.init()
screen = pygame.display.set_mode((800, 500))
class Cube:
def update(self):
self.cx, self.cy = pygame.mouse.get_pos()
self.square = pygame.Rect(self.cx, self.cy, 50, 50)
def draw(self):
pygame.draw.rect(screen, (255, 0, 0), self.square)
cube = Cube()
drawing_cube = False
drawing_cube2 = False
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
cube.update()
drawing_cube = True
screen.fill((0, 255, 0))
if drawing_cube:
cube.draw()
pygame.display.flip()
pygame.quit()
However, it doesn't create multiple squares; it just re-locates the already created square.
It's doing exactly what you told it to do: update the one and only Cube object in the game, and then redraw the screen and that one object. If you want multiple cubes, you have to create each one. Perhaps something like this:
cube_list = [] # List of all cubes
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN: # Make a new cube
cube = Cube()
cube.update()
cube_list.append(cube)
drawing_cube = True
screen.fill((0, 255, 0))
if drawing_cube: #New cube made; draw them all.
for cube in cube_list:
cube.draw()
pygame.display.flip()
Now, as an exercise for the student, can you simplify this so that it merely adds the most recent cube to the existing screen, instead of redrawing the entire game area for each new cube?
Thanks, Prune! but for anyone that is reading this after me, here is the full code:
import pygame
cube_list = []
running = True
pygame.init()
screen = pygame.display.set_mode((800, 500))
class Cube:
def update(self):
self.cx, self.cy = pygame.mouse.get_pos()
self.square = pygame.Rect(self.cx, self.cy, 50, 50)
def draw(self):
pygame.draw.rect(screen, (255, 0, 0), self.square)
cube = Cube()
drawing_cube = False
drawing_cube2 = False
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN: # Make a new cube
cube = Cube()
cube.update()
cube_list.append(cube)
drawing_cube = True
screen.fill((0, 255, 0))
if drawing_cube: #New cube made; draw them all.
for cube in cube_list:
cube.draw()
pygame.display.flip()
pygame.quit()
Cheers!
P.S I've marked your answer as accepted

Is it Possible to Place An Image In Pygame When The Mouse Button Has Been Clicked?

So, I have an issue that I just cant seem to figure out with searching or my own knowledge.
Basically, I have a program that makes an image (in this case ball_r.gif) follow the mouse cursor. (program is below)
import pygame as pg
pg.init()
# use an image you have (.bmp .jpg .png .gif)
image_file = "ball_r.gif"
black = (0,0,0)
sw = 800
sh = 800
screen = pg.display.set_mode((sw, sh))
pg.display.set_caption('testprogram')
image = pg.image.load(image_file).convert()
start_rect = image.get_rect()
image_rect = start_rect
running = True
while running:
event = pg.event.poll()
keyinput = pg.key.get_pressed()
if keyinput[pg.K_ESCAPE]:
raise SystemExit
elif event.type == pg.QUIT:
running = False
elif event.type == pg.MOUSEMOTION:
image_rect = start_rect.move(event.pos)
screen.fill(black)
screen.blit(image, image_rect)
pg.display.flip()
Basically, what I want to be able to do, is when the left mouse button is clicked, place that image where the cursor was clicked - But the catch is that I need to be able to place as many as I want AND still have the image follow the cursor.
I hope this is possible...
_MouseBatteries
The key is to create another Surface object that has the stamped images. I've provided the working code. I've also cleaned it up a bit.
import pygame as pg
pg.init()
# use an image you have (.bmp .jpg .png .gif)
image_file = "ball_r.gif"
black = (0,0,0)
sw = 800
sh = 800
screen = pg.display.set_mode((sw, sh))
pg.display.set_caption('testprogram')
image = pg.image.load(image_file).convert()
start_rect = image.get_rect()
image_rect = start_rect
running = True
stamped_surface = pg.Surface((sw, sh))
while running:
event = pg.event.poll()
keyinput = pg.key.get_pressed()
if keyinput[pg.K_ESCAPE]:
raise SystemExit
elif event.type == pg.QUIT:
running = False
elif event.type == pg.MOUSEMOTION:
image_rect = start_rect.move(event.pos)
elif event.type == pg.MOUSEBUTTONDOWN:
stamped_surface.blit(image, event.pos)
screen.fill(black)
screen.blit(stamped_surface, (0, 0))
screen.blit(image, image_rect)
pg.display.flip()

How to draw objects that can be dragged and droped on the screen using pygame?

I am trying to draw 5 rectangles all of which I can drag and drop across the screen. I am using pygame. I managed to draw 1 rectangle that I can drag and drop but I can't do it with 5. This is my code:
import pygame
from pygame.locals import *
from random import randint
SCREEN_WIDTH = 1024
SCREEN_HEIGHT = 768
BLACK = ( 0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
screen_rect = screen.get_rect()
pygame.display.set_caption("Moving circles")
rectangle = pygame.rect.Rect(20,20, 17, 17)
rectangle_draging = False
clock = pygame.time.Clock()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
if rectangle.collidepoint(event.pos):
rectangle_draging = True
mouse_x, mouse_y = event.pos
offset_x = rectangle.x - mouse_x
offset_y = rectangle.y - mouse_y
elif event.type == pygame.MOUSEBUTTONUP:
if event.button == 1:
rectangle_draging = False
elif event.type == pygame.MOUSEMOTION:
if rectangle_draging:
mouse_x, mouse_y = event.pos
rectangle.x = mouse_x + offset_x
rectangle.y = mouse_y + offset_y
screen.fill(WHITE)
pygame.draw.rect(screen, RED, rectangle)
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
I guess this is the most important part:
pygame.draw.rect(screen, RED, rectangle)
Every time I try drawing 5 of them I can't drag any of them. Does anyone have a solution for this?
You can create a list of rectangles and a selected_rect variable which points to the currently selected rect. In the event loop check if one of the rects collides with the event.pos, then set the selected_rect to the rect under the mouse cursor and move it.
I'm using a pygame.math.Vector2 for the offset to save a few lines in the example.
import sys
import pygame as pg
from pygame.math import Vector2
pg.init()
WHITE = (255, 255, 255)
RED = (255, 0, 0)
screen = pg.display.set_mode((1024, 768))
selected_rect = None # Currently selected rectangle.
rectangles = []
for y in range(5):
rectangles.append(pg.Rect(20, 30*y, 17, 17))
# As a list comprehension.
# rectangles = [pg.Rect(20, 30*y, 17, 17) for y in range(5)]
clock = pg.time.Clock()
running = True
while running:
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
elif event.type == pg.MOUSEBUTTONDOWN:
if event.button == 1:
for rectangle in rectangles:
if rectangle.collidepoint(event.pos):
offset = Vector2(rectangle.topleft) - event.pos
selected_rect = rectangle
elif event.type == pg.MOUSEBUTTONUP:
if event.button == 1:
selected_rect = None
elif event.type == pg.MOUSEMOTION:
if selected_rect:
selected_rect.topleft = event.pos + offset
screen.fill(WHITE)
for rectangle in rectangles:
pg.draw.rect(screen, RED, rectangle)
pg.display.flip()
clock.tick(30)
pg.quit()
sys.exit()

Pygame pixel array gives weird colors and freezes immidiately

I am trying to scroll a world map horizontally using numpy.roll() on
a pixels2d object in pygame.
This is the picture I am using, but in .bmp:
This is what I get:
I tried changing the axis of np.roll(), but it didn't seem to affect the result much.
Am I doing something horribly wrong?
Here is my code:
import pygame as pg, numpy as np
pg.init()
world_map = pg.image.load('Miller.bmp')
screen = pg.display.set_mode(world_map.get_size())
world_map = pg.surfarray.pixels2d(world_map)
while 1:
np.roll(world_map,1)
screen.blit(pg.surfarray.make_surface(world_map), (0,0))
pg.display.flip()
pg.time.wait(1000)
Here is the adapted code with the help of this repository from furas:
import pygame as pg
clock = pg.time.Clock()
pg.init()
world_map = pg.image.load('Miller.bmp')
w, h = world_map.get_size()
screen = pg.display.set_mode(world_map.get_size())
FPS = 60
offset = 0
is_running = True
while is_running:
for event in pg.event.get():
if event.type == pg.QUIT:
is_running = False
elif event.type == pg.KEYDOWN:
if event.key == pg.K_ESCAPE:
is_running = False
offset += 1
if offset >= w:
offset = 0
screen.blit(world_map, (0,0), (offset, 0, w, h))
screen.blit(world_map, (w-offset,0), (0, 0, offset, h))
pg.display.flip()
clock.tick(FPS)
pg.guit()

Categories

Resources