Click and Drag Pygame Glitches

Click and Drag Pygame Glitches

Postby MangoJuice » Mon Oct 21, 2013 8:05 am

Hi Everyone. I have been working towards making a game and I've been hitting a few brick walls.
I was working on making the particles on screen be able to be clicked and dragged. They click and drag just fine, but when one is dragged beyond the bounds of the screen, that's when I witness a glitch.
I did some print statements and I have observed that the sprites are being removed from the group (and they do get deleted on the wall collision check under other circumstances no problem).
I have a problem if the user happens to click and drag the image to the bounds of the screen. The image will collide with the wall and the program would recognize it. It will still stay on the mouse like it is still in the dragged state, and even though it has been removed from the group. The sprite will continue to follow the cursor even after MOUSEBUTTONUP, unless the user clicks on another sprite. The new sprite will replace the existing one and will act normally unless the user drags the sprite to the window bounds, or if I did a sprite delete like in a collision detection test. I thought this was a minor error until I was trying to do a collision detection (not shown in code).

I have been trying to figure this out, but it's a little hard for me to debug. I hope my explanation makes sense.

This is the partial code that runs just fine, isolating the trouble area:
Code: Select all
import random
import pygame, sys
pygame.init()

screenSize = WIDTH, HEIGHT = [800, 600]
screen = pygame.display.set_mode(screenSize)

bgpath = "images/clouds.jpg"
bg = pygame.Surface(screen.get_size())

bg = pygame.image.load(bgpath)
sGroup = pygame.sprite.Group()
sSingle = pygame.sprite.GroupSingle()

clock = pygame.time.Clock()
minY = 20

class Shapes(pygame.sprite.Sprite):
    def __init__(self, path, speed, location):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(path)
        self.rect = self.image.get_rect()
        self.rect.topleft = location
        self.speed = speed
        self.isDragged = False

    def wallCollide(self):
        if (self.rect.right >= WIDTH/2):
            return True
        return False

    def getMouseDistance(self, mx, my):
        dx, dy = (mx - self.rect.centerx), (my - self.rect.centery)
        return ((dx) ** 2 +(dy) ** 2) ** 0.5

    def mouseOnImage(self):
        mx, my = pygame.mouse.get_pos()
        if self.getMouseDistance(mx, my) < (self.rect.width/2): # fix later
                return True
        return False

    def draw(self, mx, my):
        screen.blit(self.image, self.rect)
        pygame.display.flip()

    def drag(self, sGroup):
        mx, my = pygame.mouse.get_pos()
        self.rect.topleft = mx, my
        self.draw(mx, my)

def start():
    sPaths = ['images/s/s1.jpg', 'images/s/s2.jpg']
    for i in range(3):
        sPath = sPaths[random.randrange(0, len(sPaths))]
        sSpeed = [random.randrange(1, 7), 0]
        sLocation = [WIDTH/3, random.randrange(minY, HEIGHT - 75)]
        shapes = Shapes(sPath, sSpeed, sLocation)
        sGroup.add(shapes)

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()

            elif event.type == pygame.MOUSEBUTTONDOWN and pygame.mouse.get_pressed() == (1, 0, 0):
                for sPiece in sGroup:
                    if sPiece.mouseOnImage():
                        sSingle.add(sPiece)
                        sPiece.isDragged = True
            elif event.type == pygame.MOUSEBUTTONUP:
                for sPiece in sGroup:
                    if sPiece.isDragged == True:
                        sPiece.isDragged = False

        for s in sSingle:
            if s.isDragged:
                s.drag(sGroup)

        screen.blit(bg, (0, 0))

        for s in sGroup:
            screen.blit(s.image, s.rect)
            if s.wallCollide():
                sGroup.remove(s)

        pygame.display.flip()
start()
Last edited by Mekire on Mon Oct 21, 2013 8:15 am, edited 1 time in total.
Reason: First post lock.
MangoJuice
 
Posts: 2
Joined: Mon Oct 21, 2013 7:48 am

Re: Click and Drag Pygame Glitches

Postby Mekire » Mon Oct 21, 2013 8:33 am

I don't have your images so it is very hard for me to debug this. If you could put your project on github with all needed resources it would be much easier.

First thing I noticed though is that there are major drawing issues. Every object should only be drawn once per frame. Don't recall the draw functions just because you are dragging it; and don't update the screen until everything in that frame has finished. Draw everything once per frame and update the screen only once.

-Mek

Edit:
Hackish edit. Let me know if it runs:
Code: Select all
import random
import pygame, sys
pygame.init()

screenSize = WIDTH, HEIGHT = [800, 600]
screen = pygame.display.set_mode(screenSize)

bgpath = "images/clouds.jpg"
bg = pygame.Surface(screen.get_size())

bg = pygame.image.load(bgpath)
sGroup = pygame.sprite.Group()
sSingle = pygame.sprite.GroupSingle()

clock = pygame.time.Clock()
minY = 20

class Shapes(pygame.sprite.Sprite):
    def __init__(self, path, speed, location):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(path).convert()
##        self.image = pygame.Surface((50,50)).convert()##
##        self.image.fill((255,0,0))##
        self.rect = self.image.get_rect(topleft=location)
        self.speed = speed
        self.isDragged = False

    def wallCollide(self):
        if self.rect.right >= WIDTH/2:
            return True
        return False

    def getMouseDistance(self, mx, my):
        dx, dy = (mx - self.rect.centerx), (my - self.rect.centery)
        return ((dx) ** 2 +(dy) ** 2) ** 0.5

    def mouseOnImage(self):
        return self.rect.collidepoint(pygame.mouse.get_pos())

    def draw(self):
        screen.blit(self.image, self.rect)

    def drag(self):
        self.rect.move_ip(pygame.mouse.get_rel())


def start():
    sPaths = ['images/s/s1.jpg', 'images/s/s2.jpg']
    for i in range(3):
        sPath = sPaths[random.randrange(0, len(sPaths))]
        sSpeed = [random.randrange(1, 7), 0]
        sLocation = [WIDTH/3, random.randrange(minY, HEIGHT - 75)]
        shapes = Shapes(sPath, sSpeed, sLocation)
        sGroup.add(shapes)

    done = False
    while not done:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
            elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                for sPiece in sGroup:
                    if sPiece.mouseOnImage():
                        sSingle.add(sPiece)
                        sPiece.isDragged = True
                        pygame.mouse.get_rel()
            elif event.type == pygame.MOUSEBUTTONUP:
                for sPiece in sGroup:
                    sPiece.isDragged = False

        for s in sSingle:
            if s.isDragged:
                s.drag()

        screen.blit(bg, (0, 0))
##        screen.fill(0)##

        for s in sGroup:
            s.draw()
            if s.wallCollide():
                sGroup.remove(s)

        clock.tick(60)
        pygame.display.flip()


if __name__ == "__main__":
    start()
    pygame.quit()
    sys.exit()
User avatar
Mekire
 
Posts: 976
Joined: Thu Feb 07, 2013 11:33 pm
Location: Amakusa, Japan

Re: Click and Drag Pygame Glitches

Postby MangoJuice » Sun Oct 27, 2013 1:01 am

Thank you for the help. It 'worked for the most part until I added a method to have the items move across the screen. The only way around it was to change the dragging method. It works fine now. I'm in the process of doing other things to it.
Here is the link to the github repo like you've asked.

I have a few questions. I originally used mouse.getpressed(1, 0, 0) to see if the user had left clicked down. I remember having the program work differently than if I had used MOUSEBUTTONDOWN instead. Specifically, the program took it as something literal. If the button pressed is the left click, I could get away with pressing down anywhere on the screen and then being able to drag my mouse over to the particle (while left click still activated) which would satisfy the condition of being dragged. What is the different between MOUSEBUTTONDOWN and mouse.getpressed(1, 0, 0)?

I don't quiet get what Rect.move_ip() does based on the documentation's definition of it and the part it says for rect.collidepoint() where it says "A point along the right or bottom edge is not considered to be inside the rectangle." Is that part saying that if an object is on the border of the other rectangle's right or bottom side it would not be considered? If that object was along the border of the top or left side, would the method consider the two rectangles as collided with one another?
MangoJuice
 
Posts: 2
Joined: Mon Oct 21, 2013 7:48 am

Re: Click and Drag Pygame Glitches

Postby Mekire » Sun Oct 27, 2013 11:58 am

Hmm...

The function:
Code: Select all
pygame.mouse.get_pressed()
tells you the state of all the mouse buttons. It can be called at any time. Quite often though it is not the correct tool. If you wanted to drag any sushi with you as you moused over them, while holding down the mouse though; yes, you could use this.
Code: Select all
pygame.MOUSEBUTTONDOWN
On the other hand is an event. We can catch it in our event loop; check what button it is that triggered the event; and then act appropriately.

Next. The difference between pygame.Rect.move() and pygame.Rect.move_ip(). The second one operates in place. This means that the act of calling it changes our original rect. Take a look at the following:
Code: Select all
>>> a = pygame.Rect(0,0,50,50)
>>> b = a.move(20,5)
>>> a
<rect(0, 0, 50, 50)>
>>> b
<rect(20, 5, 50, 50)>
>>>
Here you can see that a has not been changed.

On the other hand, look here:
Code: Select all
>>> a = pygame.Rect(0,0,50,50)
>>> a.move_ip(20,5)
>>> a
<rect(20, 5, 50, 50)>
>>>
Our original rectangle has been changed "in place".

Next I would like to mention your images. Jpgs are generally a bad choice; you should generally use pngs. Jpgs ruin the pallets of images. I edited your images a bit and made some minor changes so your program will use transparency. Replace your images with the attached images and test with the following code:
Code: Select all
import os
import sys
import random
import pygame

pygame.init()
os.environ["SDL_VIDEO_CENTERED"] = '1'
clock = pygame.time.Clock() # fps clock
font = pygame.font.Font(None, 50)

SCREEN_SIZE = WIDTH, HEIGHT = (800, 600)
pygame.display.set_caption('Benttoru Food Game')
SCREEN = pygame.display.set_mode(SCREEN_SIZE)
SCREEN_RECT = SCREEN.get_rect()


BG_PATH = os.path.join("images","cloudBG.jpg")
BACKGROUND = pygame.image.load(BG_PATH).convert()

SUSHI_PATHS = [os.path.join("images","sushi","sushi1.png"),
              os.path.join("images","sushi","sushi2.png")]
sushiGroup = pygame.sprite.Group()
sushiSingle = pygame.sprite.GroupSingle()

clock = pygame.time.Clock()
minY = 50

class Benttoru(pygame.sprite.Sprite):
    def __init__(self, path, speed):
        pygame.sprite.Sprite.__init__(self) #call Sprite initializer
        self.image = pygame.image.load(path) # load sprite from path/file loc.
        self.image.set_colorkey((255, 0, 255))
        self.rect = self.image.get_rect() # get bounds of image
        self.speed = speed

    def setLocation(self, location):
        self.rect.topleft = location

    def charMove(self):
        if not SCREEN_RECT.contains(self.rect):
            self.speed[0] *= -1
            self.image = pygame.transform.flip(self.image, True, False)
        self.rect.move_ip(self.speed)

    def checkCollision(self, sushi):
        return pygame.sprite.collide_rect(self, sushi)


class EatingBird(pygame.sprite.Sprite):
    def __init__(self, path, speed):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(path).convert()
        self.image.set_colorkey((255,0,255))
        self.rect = self.image.get_rect()
        self.speed = speed

    def setLocation(self, location):
        self.rect.topleft = location

    def charMove(self):
        if not SCREEN_RECT.contains(self.rect):
            self.speed[1] *= -1
        self.rect.move_ip(self.speed)

    def checkCollision(self, sushi):
        return pygame.sprite.collide_rect(self, sushi)


class Sushi(pygame.sprite.Sprite):
    def __init__(self, path, speed, location):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(path)
        self.image.set_colorkey((255,0,255))
        self.rect = self.image.get_rect(topleft=location)
        self.speed = speed
        self.is_dragged = False

    def sushiMove(self):
        if not self.is_dragged:
            self.rect.move_ip(self.speed)

    def hasCollidedWithWall(self):
        return not SCREEN_RECT.contains(self.rect)

    def mouseOnImage(self,position):
        return self.rect.collidepoint(position)

    def draw(self):
        SCREEN.blit(self.image, self.rect)

    def setDrag(self):
        self.rect.move_ip(pygame.mouse.get_rel())

#--------- Outside function to create new sushi ----------
def sushiCreate():
    sushiPath = random.choice(SUSHI_PATHS)
    sushiSpeed = [random.randrange(1, 7), 0] # random X speed
    sushiLocation = [0, random.randrange(minY, HEIGHT - 75)]
    newSushi = Sushi(sushiPath, sushiSpeed, sushiLocation)
    sushiGroup.add(newSushi)

#---------- Game Stuff -------------
def startGame():
    score = 0

    # dogPath = ['images/benttoru1.jpg', 'benttoru2.jpg']
    # I do a for loop in the class to
    dogPath = os.path.join('images','benttoru.png')
    benttoru = Benttoru(dogPath, [3,0])
    benttoru.setLocation([0, HEIGHT - benttoru.rect.h])

    birdPath = os.path.join('images','bird.png')
    eatingBird = EatingBird(birdPath, [0,5])
    eatingBird.setLocation([WIDTH - eatingBird.rect.w, minY])

    # eventual list of 3 holding onscreen sushi in the class(?)
    for i in range(3):
        sushiPath = random.choice(SUSHI_PATHS)
        sushiSpeed = [random.randrange(1, 7), 0] # random X speed
        sushiLocation = [0, random.randrange(minY, HEIGHT - benttoru.rect.h)]
        sushi = Sushi(sushiPath, sushiSpeed, sushiLocation)
        sushiGroup.add(sushi)

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                for sushiPiece in sushiGroup:
                    if sushiPiece.mouseOnImage(event.pos):
                        sushiSingle.add(sushiPiece)
                        sushiPiece.is_dragged = True
                        pygame.mouse.get_rel()
            elif event.type == pygame.MOUSEBUTTONUP:
                for sushiPiece in sushiGroup:
                    sushiPiece.is_dragged = False
        eatingBird.charMove()
        benttoru.charMove()

        for sushi in sushiSingle:
            if sushi.is_dragged:
                sushi.setDrag()
        SCREEN.blit(BACKGROUND, (0, 0))
        SCREEN.blit(eatingBird.image, eatingBird.rect)
        SCREEN.blit(benttoru.image, benttoru.rect)
        score_text = font.render(str(score), 1, (0, 0,0)) # Displays score
        SCREEN.blit(score_text, (WIDTH - 50, 10))

        for sushi in sushiGroup:
            sushi.draw()
            if sushi.hasCollidedWithWall():
                sushiGroup.remove(sushi)
                sushiCreate()
            elif benttoru.checkCollision(sushi):
                sushiGroup.remove(sushi)
                sushiCreate()
                score += 1
            elif eatingBird.checkCollision(sushi):
                sushiGroup.remove(sushi)
                sushiCreate()
                score = 0
            else:
                sushi.sushiMove()
        if pygame.time.get_ticks() >= 30000:
            gameOverText = font.render("Game Over", 1, (0, 0, 0))
            SCREEN.blit(gameOverText, ((WIDTH/2) - 95, HEIGHT/2))
            if score < 5:
                lose = font.render("You Lose.", 1, (0, 0, 0))
                SCREEN.blit(lose, ((WIDTH/2) - 95, (HEIGHT/2)+30))
            else:
                win = font.render("You Win.", 1, (0, 0, 0))
                SCREEN.blit(win, ((WIDTH/2) - 95, (HEIGHT/2)+30))


        pygame.display.flip()
        clock.tick(30) # wait a little before starting again
startGame()

I'll leave it at that for now. Still a lot of cleaning up to be done though if you are interested.
-Mek
Attachments
images.zip
(165.98 KiB) Downloaded 38 times
User avatar
Mekire
 
Posts: 976
Joined: Thu Feb 07, 2013 11:33 pm
Location: Amakusa, Japan


Return to Game Development

Who is online

Users browsing this forum: W3C [Linkcheck] and 2 guests