Search code examples
pythonpython-3.xpygameframe-rate

How do I move an image smoothly after clicking in pygame?


I have decided to make my game in pygame instead, and here is what I have coded so far:

from typing import Tuple
import pygame
from PIL import ImageGrab

# finding the height of the screen by taking a screenshot.
img = ImageGrab.grab()
(WIDTH, HEIGHT) = (img.size)

x = WIDTH / 2 - 470
y = HEIGHT / 2 - 400

fistx1 = x - 80
fistx2 = fistx1;

fisty1 = y + 40

fisty2 = y - 50
pygame.init()

clock = pygame.time.Clock()

RESOLUTION = (WIDTH, HEIGHT)

BACKGROUND_COLOR: Tuple[int, int, int] = (79, 205, 109)

MOVESPEED = 5

# window stuff
window = pygame.display.set_mode(RESOLUTION, flags=pygame.RESIZABLE, depth=32)
pygame.display.set_caption("The Connection")
window.fill(BACKGROUND_COLOR)

running = True

# all images for the game here
player_image = pygame.image.load("Connector.png")
player_fist1_image = pygame.image.load("Connector_hand.png")
player_fist2_image = player_fist1_image

while running:
    pressed = pygame.key.get_pressed()
    clicked = pygame.mouse.get_pressed();
    class Player():
        def __init__(self, hp, image, fist1image, fist2image):
            global fisty1, fistx1, fisty2
            self.hp = hp
            self.image = image
            self.fist1image = fist1image
            self.fist2image = fist2image

            window.blit(self.image, (x, y))
            window.blit(self.fist1image, (fistx1, fisty1))
            window.blit(self.fist2image, (fistx2, fisty2))

        def move(self, movespeed):
            global x, y, fistx1, fisty1, fisty2
            if pressed[pygame.K_a]:
                x -= movespeed
                fistx1 -= movespeed
            elif pressed[pygame.K_d]:
                x += movespeed
                fistx1 += movespeed
            elif pressed[pygame.K_w]:
                y -= movespeed
                fisty1 -= movespeed
                fisty2 -= movespeed
            elif pressed[pygame.K_s]:
                y += movespeed
                fisty1 += movespeed
                fisty2 += movespeed

        def deal_damage(self, damage):
            global x, y, fistx1, fisty1
            fistx1 -= 25
            window.fill(BACKGROUND_COLOR);
            window.blit(self.fist1image, (fistx1, fisty1));
            pygame.display.flip();

    # this is the function we use to actually call it. This is more helpful and less confusing for an idiot like me
    def actual_deal():
        main_player.deal_damage(25);


    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
            try:
                pygame.quit()
            except pygame.error:
                pass
        if event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1:
                actual_deal();

    window.fill(BACKGROUND_COLOR)

    main_player = Player(100, player_image, fist1image=player_fist1_image, fist2image=player_fist2_image)
    main_player.move(movespeed=MOVESPEED)

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

For context, the fist's have to go forward and then backward. However, the fists aren't moving smoothly, which has two problems:

  1. It doesn't look good. I want to show this to other people, so I want it to look good.

  2. When I retract the fists, it doesn't look like they moved forward in the first place. I'd have to add time.sleep in between, but I am not willing to do that.

Here is the output I am getting when I punch: Placed in a spoiler so it isn't intrusive

enter image description here

As you can see, it moves in a blocky fashion. If you want to see the output I desire, then move the character around with the WASD keys and see how smoothly the characters move. I want the same thing for the fists.

If it matters, I am using pycharm to code this, and I'm running it from Command Prompt. I also have Windows 10.

Lastly, I have tried changing the framerate by doing this:

Here are the questions I have already looked at:

clock.tick(360);

But to no avail.

I have looked at these questions:


smooth movement in pygame

https://gamedev.stackexchange.com/questions/48227/smooth-movement-pygame


Solution

  • Couple of this I can spot. Firstly, function and class definitions are supposed to be outside of main loop because defining the same thing over and over again in a loop doesn't make any sense. Second you are calling pygame.display.flip twice, which is not needed. Flip should only be called once, otherwise it causes flickering. Third, you are drawing in __init__ method and creating a new instance every frame. Usually, an instance is only made once, and there are methods of that instance to do something with that instance. So, instead of drawing in the __init__, make a new method called draw.

    Now to answer your question, it moves in blocks becasue:

    1. You are moving it 25 frames at once, so it skips 25 pixel at once and draws again in the new position.
    2. You are using pygame.MOUSEBUTTONDOWN. This function only returns once true per click. So if you hold down your mouse button, it wouldn't work because it returns True in the first frame and None after that. To continuously update the mouse state, you need to use pygame.mouse.get_pressed().

    New code with everything I mentioned above implemented (NOTE: I changed images to surfaces to make it work, so you might wanna change it to image again):

    from typing import Tuple
    import pygame
    from PIL import ImageGrab
    
    # finding the height of the screen by taking a screenshot.
    img = ImageGrab.grab()
    (WIDTH, HEIGHT) = (img.size)
    
    x = WIDTH / 2 - 470
    y = HEIGHT / 2 - 400
    
    fistx1 = x - 80
    fistx2 = fistx1;
    
    fisty1 = y + 40
    
    fisty2 = y - 50
    pygame.init()
    
    clock = pygame.time.Clock()
    
    RESOLUTION = (WIDTH, HEIGHT)
    
    BACKGROUND_COLOR: Tuple[int, int, int] = (79, 205, 109)
    
    MOVESPEED = 5
    
    # window stuff
    window = pygame.display.set_mode(RESOLUTION, flags=pygame.RESIZABLE, depth=32)
    pygame.display.set_caption("The Connection")
    window.fill(BACKGROUND_COLOR)
    
    running = True
    
    # all images for the game here
    player_image = pygame.Surface((50, 50)).convert()
    player_fist1_image = pygame.Surface((10, 10)).convert()
    player_fist2_image = player_fist1_image
    
    class Player():
        def __init__(self, hp, image, fist1image, fist2image):
            global fisty1, fistx1, fisty2
            self.hp = hp
            self.image = image
            self.fist1image = fist1image
            self.fist2image = fist2image
    
        def move(self, movespeed):
            global x, y, fistx1, fisty1, fisty2
            if pressed[pygame.K_a]:
                x -= movespeed
                fistx1 -= movespeed
            elif pressed[pygame.K_d]:
                x += movespeed
                fistx1 += movespeed
            elif pressed[pygame.K_w]:
                y -= movespeed
                fisty1 -= movespeed
                fisty2 -= movespeed
            elif pressed[pygame.K_s]:
                y += movespeed
                fisty1 += movespeed
                fisty2 += movespeed
    
        def draw(self):
            window.blit(self.image, (x, y))
            window.blit(self.fist1image, (fistx1, fisty1))
            window.blit(self.fist2image, (fistx2, fisty2))
    
        def deal_damage(self, damage):
            global x, y, fistx1, fisty1
            mousePressed = pygame.mouse.get_pressed()
            if mousePressed[0]:
                fistx1 -= 1
            window.fill(BACKGROUND_COLOR);
            window.blit(self.fist1image, (fistx1, fisty1));
            #pygame.display.flip();
    
    main_player = Player(100, player_image, fist1image=player_fist1_image, fist2image=player_fist2_image)
    
    # this is the function we use to actually call it. This is more helpful and less confusing for an idiot like me
    def actual_deal():
        main_player.deal_damage(25);
    
    while running:
        pressed = pygame.key.get_pressed()
        clicked = pygame.mouse.get_pressed();
    
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
                try:
                    pygame.quit()
                except pygame.error:
                    pass
    
        window.fill(BACKGROUND_COLOR)
        main_player.move(movespeed=MOVESPEED)
        main_player.deal_damage(50)
        main_player.draw()
    
        pygame.display.flip()
        clock.tick(60);
    

    Edit: Forgot to mention this but you are taking damage argument in deal_damage method but not using it.