Perplexityy
Perplexityy

Reputation: 569

Inaccurate object movement with mouse in Pygame

I'm attempting to make a near carbon copy of this game:

https://yppedia.puzzlepirates.com/Carpentry

It uses pentominos, which are objects made of 5 blocks. I have a Piece class, that stores each of these blocks in a list. When I click on a Block to move it, I'm also moving every other block that shares the same parent Piece object, so I can drag the entire piece around with my mouse.

The problem I'm having is that when I click one of these blocks, the piece moves away from my cursor. The dragging is fine, but I want it to follow the cursor more precisely.

I am using a Mouse class so I can implement simple collisions between mouse clicks and the Blocks, but I think this is the cause of my problems.

Edit: I could probably resolve this by hardcoding changes to the x and y positions of each block, but ideally I'd prefer a more modular solution because I think I'm misunderstanding how the mouse position works in pygame.

import pygame
import sys
import collections
import random

pygame.init()
FPS = 25
fpsClock = pygame.time.Clock()

# -----------SCREEN----------------#
WIN_WIDTH = 680  # width of window
WIN_HEIGHT = 500  # height of window
DISPLAY = (WIN_WIDTH, WIN_HEIGHT)  # variable for screen display
DEPTH = 32  # standard
FLAGS = 0  # standard
screen = pygame.display.set_mode(DISPLAY, FLAGS, DEPTH)
pygame.display.set_caption('Carpentry')
# ---------------------------------#

# ---------------colours-----------#
WOOD = (182, 155, 76)
RED = (255, 0, 0)
BLACK = (0, 0, 0)
# ---------------------------------#

blocks = pygame.sprite.Group()
pieces = []

class Block(pygame.sprite.Sprite):
    def __init__(self, x, y, parent):
        super().__init__()
        self.image = pygame.Surface([15, 15])
        self.colour = WOOD
        self.parent = parent
        self.image.fill(self.colour)
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.original_x = x
        self.original_y = y
        self.drag = False

class Mouse(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.Surface([15, 15])
        self.colour = RED
        self.image.fill(self.colour)
        self.rect = self.image.get_rect()
        self.rect.x = pygame.mouse.get_pos()[0]
        self.rect.y = pygame.mouse.get_pos()[1]

    def update_mouse(self):
        self.rect.x = pygame.mouse.get_pos()[0]
        self.rect.y = pygame.mouse.get_pos()[1]

class Piece:
    def __init__(self):
        self.blocks = []
        self.c = 0

    def insert(self, template):
        for direction in template:
            self.blocks.append([])
            x = 0
            y = 0
            for line in direction:
                for character in line:
                    if character == "O":
                        my_block = Block(x, y, self)
                        self.blocks[self.c].append(my_block)
                        blocks.add(my_block)

                    x += 15
                y += 15
                x = 0
            self.c += 1

J_TEMPLATE = [['..O..',
               '..O..',
               '..O..',
               '.OO..',
               '.....']]
templates = {}

my_piece = Piece()
my_piece.insert(J_TEMPLATE)
pieces.append(my_piece)

mouse = Mouse()
while True:
    screen.fill(BLACK)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        elif event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1:
                bool = pygame.sprite.spritecollide(mouse, blocks, False)
                if len(bool) > 0:
                    target = bool[0]
                    target.drag = True
                    for block in blocks:
                        if block.parent == target.parent: #if blocks are part of the same piece
                            block.drag = True
                else:
                    for block in blocks:
                        block.drag = False
    mouse.update_mouse()
    for block in blocks:
        if block.drag == True:
            block.rect.x = mouse.rect.x + block.original_x
            block.rect.y = mouse.rect.y + block.original_y

    blocks.draw(screen)
    pygame.display.update()
    fpsClock.tick(FPS)

Upvotes: 1

Views: 295

Answers (1)

skrx
skrx

Reputation: 20478

I'd do it in this way: If a block was clicked, assign its parent piece to a variable and calculate the offsets for the blocks like so: block.offset = block.rect.topleft - Vec(event.pos). At the top of the file you need to import from pygame.math import Vector2 as Vec. Now you can check for pygame.MOUSEMOTION events and move the blocks to the new event.pos + block.offset. The Mouse class is not needed anymore.

selected = None

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        elif event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1:
                if selected:  # If a piece was selected ...
                    selected = None  # Deselect it.
                else:
                    # Find a colliding block.
                    collided_sprites = [block for block in blocks
                                        if block.rect.collidepoint(event.pos)]
                    if collided_sprites:  # If we clicked a block ...
                        # Select the parent of the block.
                        selected = collided_sprites[0].parent
                        # Calculate the offsets for the blocks.
                        for block in selected.blocks[0]:
                            block.offset = block.rect.topleft - Vec(event.pos)
        elif event.type == pygame.MOUSEMOTION:
            if selected:
                # Update the block positions by adding their offsets to the mouse pos.
                for block in selected.blocks[0]:
                    block.rect.topleft = event.pos + block.offset

    screen.fill(BLACK)
    blocks.draw(screen)
    pygame.display.update()
    fpsClock.tick(25)

Upvotes: 1

Related Questions