1

I'm learning programming and trying to write my own simple pgzero game. Attempts to make a smooth fly movement in def update (): to random coordinates. Tried animate, unfortunately failed, ended up using randint and time.sleep (1). The fly jumps from place to place with no fluidity. I have no idea what to do with it. Can someone help me solve the problem?

import pgzrun
from pgzero.builtins import  Actor, animate, keys
from random import Random, randint
import time
import os

WIDTH = 800
HEIGHT = 600
TITLE = 'Zabij Muchę'
ICON = 'data/mucha.png'

tlo = Actor('background')

mucha = Actor('fly')
x=randint(150, 650)
y=randint(100,500)
mucha.x = x
mucha.y = y
muchaSpeed = 2
muchaLife = True

zabitych = 0
pudlo = 0

def killed_fly():
    killed = screen.draw.text('Mucha zabita!', (280, 350), color=(255,0 ,0), fontsize=60, alpha=0.8)
    
def draw_score():
    screen.draw.text('Trafione:', (5,10), color=(0, 128, 128))
    screen.draw.text(str(zabitych), (100,10), color=(0, 128, 0))

    screen.draw.text('Pudło:', (5,30), color=(0, 128, 128))
    screen.draw.text(str(pudlo), (100,30), color=(0, 128, 0))

def on_mouse_down(pos):
    global zabitych
    global pudlo
    if mucha.collidepoint(pos):
        update()
        zabitych += 1
        mucha.image=('fly-swatter')
        time.sleep(2)
        muchaLife = False        
    else:
        pudlo += 1
        
def update():
    if muchaLife == True:
        time.sleep(1)
        x_los = randint(150, 650)
        y_los = randint(100,500)
        mucha.x = x_los
        mucha.y = y_los
        
        

def draw():
    tlo.draw()
    mucha.draw()

    draw_score()

pgzrun.go()
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
Slavo Heys
  • 33
  • 5
  • you runs `update()` only `if mucha.collidepoint(pos)` so maybe it doesn't move it in other situations and this can makes problem. And if you want smooth moves in one direction then you should select much smaller values `move_x`, `move_y` and how many repeate it - it would be good if it would gives `sin(angle)*move_x + cos(angle) * move_y = muchaSpeed` - and repeate `mucha.x += move_x *delta_time`, `mucha.y += move_y *delta_time`, `repeate -= 1` in every `update` until `repeate` will be zero (PL: Powodzenia :) ) – furas Sep 20 '21 at 11:40
  • you could also use `mucha.x += randint(-5, 5)` `mucha.y += randint(-5, 5)` (without `sleep(1)`) and it will fly better then before. – furas Sep 20 '21 at 11:49
  • Remove all calls of `time.sleep()`. Update depending on [`pygame.time.get_ticks()`](https://www.pygame.org/docs/ref/time.html). – Rabbid76 Sep 20 '21 at 14:02
  • as @Rabbid76 already said - don't use `sleep()` (and any long running code) in game - because it blocks other code. The same is when you use some GUI module/framework. You may use functions from `pygame.time` (like in normal `PyGame`) - but I found it has also [Clock.sheduler](https://pygame-zero.readthedocs.io/en/stable/builtins.html#clock) which can be useful - ie. with `animate()` – furas Sep 20 '21 at 15:56
  • 1
    in previous comment I used text `as @Rabbid76 already said` to inform OP that I agree with your opinion. And that I want to add extra information to your comment. It wasn't information directly to you. – furas Sep 20 '21 at 16:22
  • @furas I apologize. I didn't read carefully (tired). Anyway, I agree, [`Clock.sheduler`](https://pygame-zero.readthedocs.io/en/stable/introduction.html#clock) is the way to go in [Pygame Zero](https://pygame-zero.readthedocs.io/en/stable/index.html). – Rabbid76 Sep 20 '21 at 16:26
  • 1
    @Rabbid76 no problem, I assumed it can be one of two problems (1) English is not my native language (and probably it is not your native language) and there could be some mistake in my English text (2) you didn't read full comment - there is so many questions and comments every day so sometimes I don't read full text (or don't read carefully) and I expected you have the same (I see your answers in many PyGame questions). – furas Sep 21 '21 at 16:28

3 Answers3

1

I solved the problem like this:

import pgzrun
import pygame
from pgzero.builtins import  Actor, animate, keys
from random import Random, randint
import time
import os

WIDTH = 800
HEIGHT = 600
TITLE = 'Zabij Muchę'
ICON = 'data/mucha.png'

tlo = Actor('background')

mucha = Actor('fly')
x_los=int(randint(150, 650))
y_los=int(randint(100,500))
mucha.x = x_los
mucha.y = y_los

muchaSpeed = 0.05
muchaLife = True

zabitych = 0
pudlo = 0
runda = 100

def on_mouse_down(pos):
    global zabitych
    global pudlo
    global muchaLife
    if mucha.collidepoint(pos):
        update()
        zabitych += 10
        muchaLife = False
        mucha.image=('fly-swatter')
        pygame.time.wait(5)
                   
def update():
    global x_los
    global y_los
    global runda
    global muchaSpeed
    i = int(randint(0, 3))
    t = 0
    if muchaLife == True:
        if i == 0:
            while t <= runda:
                x_los += muchaSpeed
                y_los += muchaSpeed
                mucha.x = x_los
                mucha.y = y_los
                pygame.time.wait(1)
                t += 1
        elif i == 1:
            while t <= runda:
                x_los -= muchaSpeed
                y_los -= muchaSpeed
                mucha.x = x_los
                mucha.y = y_los
                pygame.time.wait(1)
                t += 1
        elif i == 2:
            while t <= runda:
                x_los -= muchaSpeed
                y_los += muchaSpeed
                mucha.x = x_los
                mucha.y = y_los
                pygame.time.wait(1)
                t += 1
        elif i == 3:
            while t <= runda:
                x_los += muchaSpeed
                y_los -= muchaSpeed
                mucha.x = x_los
                mucha.y = y_los
                pygame.time.wait(1)
                t += 1

def draw():
    tlo.draw()
    mucha.draw()
    draw_score()
    if muchaLife == False:
        killed_draw()

def killed_draw():
    screen.draw.text('Mucha zabita!', (280, 350), color=(255,0 ,0), fontsize=60, alpha=0.8)
    
def draw_score():
    screen.draw.text('Punkty:', (5,10), color=(0, 128, 128))
    screen.draw.text(str(zabitych), (100,10), color=(0, 128, 0))

pgzrun.go()

I would also like to divide the game into levels of difficulty. Unfortunately, I have no idea how to do it, or where to read about the basics of the game levels.

The next problem is to replace the mouse marker with a crosshair, for example, I didn't know how to do it anywhere. Thank you in advance for your help.

Slavo Heys
  • 33
  • 5
1

I start writing this answer yesterday but meanwhile you resolve your problem :) But I put my code and maybe it will useful for someone. It shows how to use scheduler and animate

BTW: I speak Polish but I renamed all variables to English (and convert text) because it is preferred - see more in PEP 8 -- Style Guide for Python Code


The main problem is that you use sleep() which blocks all code and it can't other elements.

In games and GUI framework you shouldn't use sleep() and long running code - it may need to special non-blocking functions for sleep - like pygame.time.get_ticks() - or it needs to run code in separated thread (but it can give other problems).


I never before used pgzero but I created code (without sleep) which moves fly randomly using small steps x += random.randint(-5, 5) y += random.randint(-5, 5). I used Clock.scheduler() instead of sleep to restart/respawn fly few seconds after die.

In this version fly make many small random moves.

import pgzrun
from pgzero.builtins import Actor, animate, keys
import random
import time
import os

# --- constants ---

WIDTH = 800
HEIGHT = 600
TITLE = 'KILL FLY!'
#ICON = 'data/fly.png'

# --- functions ---

def killed_fly():
    screen.draw.text('Fly killed!', (280, 350), color=(255,0 ,0), fontsize=60, alpha=0.8)
    
def draw_score():
    screen.draw.text('Killed:', (5,10), color=(0, 128, 128))
    screen.draw.text(str(killed), (100,10), color=(0, 128, 0))

    screen.draw.text('Missed:', (5,30), color=(0, 128, 128))
    screen.draw.text(str(missed), (100,30), color=(0, 128, 0))

def respawn():
    fly.x = random.randint(150, WIDTH-150)
    fly.y = random.randint(100, HEIGHT-100)
    fly.life = True
    fly.image = 'fly'

def on_mouse_down(pos):
    global killed
    global missed
    
    if fly.collidepoint(pos):
        killed += 1
        fly.life = False        
        fly.image = 'fly-swatter'
        clock.schedule(respawn, 1.0)
    else:
        missed += 1
        
def update():
    if fly.life:
        fly.x += random.randint(-5, 5)
        fly.y += random.randint(-5, 5)

        # keep inside window
        if fly.left < 0:
           fly.left = 0
        elif fly.right > WIDTH:
           fly.right = WIDTH

        if fly.top < 0:
           fly.top = 0
        elif fly.bottom > HEIGHT:
           fly.bottom = HEIGHT
                
def draw():
    background.draw()
    fly.draw()
    draw_score()
    if not fly.life:
       killed_fly()

# --- main ---

killed = 0
missed = 0

background = Actor('background')

fly = Actor('fly')
#fly.speed = 2

respawn() # (re)set some values at start

pgzrun.go()

Next I created version which uses animate() to move fly and I don't need code in update(). Function animate() uses on_finished=... to run this function again so it make next move.

import os
import random
import pgzrun
from pgzero.builtins import  Actor, animate, keys

# --- constants ---

WIDTH = 512
HEIGHT = 512
TITLE = 'KILL FLY!'
#ICON = 'data/fly.png'

# --- functions ---

def killed_fly():
    text_killed = screen.draw.text('Fly killed!', (280, 350), color=(255,0 ,0), fontsize=60, alpha=0.8)
    
def draw_score():
    screen.draw.text('Killed:', (5,10), color=(0, 128, 128))
    screen.draw.text(str(killed), (100,10), color=(0, 128, 0))

    screen.draw.text('Missed:', (5,30), color=(0, 128, 128))
    screen.draw.text(str(missed), (100,30), color=(0, 128, 0))

def respawn():
    """reset some values before every respawn"""
    fly.x = random.randint(150, WIDTH-150)
    fly.y = random.randint(100, HEIGHT-100)
    fly.life = True
    fly.image = 'fly'
    animate_fly()
    
def animate_fly():
    global anim
    
    new_x = random.randint(5, WIDTH-5)
    new_y = random.randint(5, HEIGHT-5)
    anim = animate(fly, pos=(new_x, new_y), duration=3, on_finished=animate_fly)
    
def on_mouse_down(pos):
    global killed
    global missed
    
    if fly.collidepoint(pos):
        
        if anim:
            anim.stop()
            
        killed += 1
        fly.life = False        
        fly.image = 'fly-swatter'
        clock.schedule(respawn, 1.0)
    else:
        missed += 1
        
def update():
    pass
            
def draw():
    background.draw()
    fly.draw()
    draw_score()
    if not fly.life:
       killed_fly()

# --- main ---

killed = 0
missed = 0
anim = None

background = Actor('background')

fly = Actor('fly')
fly.speed = 2

respawn()   # st

pgzrun.go()

EDIT:

Version which uses clock.scheduler to change level every 5 seconds. It adds new fly to list and update speed for all flies.

To make it simpler I created class Fly(Actor) to have all properties and functions inside class.

import os
import random
import pgzrun
from pgzero.builtins import  Actor, animate, keys

# --- constants ---

WIDTH = 512
HEIGHT = 512
TITLE = 'KILL FLY!'

# --- classes ---

class Fly(Actor):

    def __init__(self, *args, speed=2, **kwargs):
        super().__init__(*args, **kwargs)
        self.speed = speed
        self.reset()
        
    def reset(self):
        """reset some values before every respawn"""
        self.x = random.randint(150, WIDTH-150)
        self.y = random.randint(100, HEIGHT-100)
        self.life = True
        self.image = 'fly'
        self.animate()

    def animate(self):
        new_x = random.randint(5, WIDTH-5)
        new_y = random.randint(5, HEIGHT-5)
        
        distance = self.distance_to((new_x, new_y))
        duration = (distance/self.speed)/50
        
        self.anim = animate(self, pos=(new_x, new_y), duration=duration, on_finished=self.animate)
        
    def check_collision(self, pos):
        if self.collidepoint(pos) and self.life:
            
            if self.anim:
                self.anim.stop()

            self.life = False        
            self.image = 'fly-swatter'
            clock.schedule(self.reset, 1.0)

            return True
        
        else:
            return False                
    
# --- functions ---

def killed_fly():
    screen.draw.text('Fly killed!', (280, 350), color=(255,0 ,0), fontsize=60, alpha=0.8)
    
def draw_score():
    screen.draw.text('Killed:', (5,10), color=(0, 128, 128))
    screen.draw.text(str(killed), (100,10), color=(0, 128, 0))

    screen.draw.text('Missed:', (5,30), color=(0, 128, 128))
    screen.draw.text(str(missed), (100,30), color=(0, 128, 0))

    screen.draw.text('Level:', (WIDTH-105,10), color=(0, 128, 128))
    screen.draw.text(str(level), (WIDTH-45,10), color=(0, 128, 0))
    
def on_mouse_down(pos):
    global killed
    global missed

    hit = False    
    
    for fly in flies:
        if fly.check_collision(pos):
            killed += 1
            hit = True
            
    if not hit:
        missed += 1
    
#def on_key_down(key):
#    global paused
#    
#    if key == keys.SPACE:
#        paused = not paused    
            
def update():
    pass
            
def draw():
    background.draw()
    
    for fly in flies:
        fly.draw()
        
    draw_score()

    #if paused:
    #    screen.draw.text('PAUSE', center=(WIDTH//2, HEIGHT//2), color=(0, 0, 0), fontsize=150)
                    
    #if not fly.life:
    #   killed_fly()

def level_up():
    global level
    global speed
    
    # level number 
    level += 1
    
    # bigger speed for flies
    speed += .5
    
    # add new fly with new speed (it will automaticaly run `animate` with this speed
    flies.append(Fly('fly', speed=speed))
    
    # change speed for other flies
    for fly in flies:
        fly.speed = speed
        
    # run it again after 5 seconds
    clock.schedule(level_up, 5.0)
    
# --- main ---

#paused = False

level  = 1   # current level
speed  = 2   # current speed

killed = 0
missed = 0

background = Actor('background')

# create list with only one fly
flies = [
  Fly('fly'),
]

# update level after 5 seconds
clock.schedule(level_up, 5.0)

pgzrun.go()

BTW: I tried to add function Pause when you press Space but it seems animate() doesn't have method to pause it - it would need to create own animate().

enter image description here

(recorded with OBS and converted to animated .gif with ffmpeg


Images for those who want to run it.

images/background.png

enter image description here

(image Lenna from Wikipedia)

images/fly.png

enter image description here

images/fly-swatter.png

enter image description here

(fly created as .svg in free Inkscape and exported to .png)

furas
  • 134,197
  • 12
  • 106
  • 148
  • @SlavoHeys PyGame jest chyba najstarszym modułem do gier w Pythonie więc można oczekiwać najwięcej informatcji w internecie. Do tego opiera się na bibliotece C/C++ [SDL](http://libsdl.org/) `Simple DirectMedia Layer` , która jest popularna do tworzenia gier np. w sklepie [Steam](https://store.steampowered.com/) więc jak znasz PyGame to łatwiej się przesiąść na SDL i C/C++ i pisać do tego sklepu - pisanie bezpośrednio w Pythonie raczej nie jest popularne. Ale jak nazwa `simple` oznacz też, że ma tylko podstawowe funkcje i wiele trzeba samemu dopisać, np. `mainloop` (event loop), animacje, GUI. – furas Sep 23 '21 at 18:44
  • @SlavoHeys modul `PGZero` dodaje kilka rzeczy aby ułatwić pisanie - np. Animations - ale ukrywa podstawowe funkcje SDL. Więcej funkcji (np. GUI) można jednak dostać w module [Arcade](https://api.arcade.academy/en/latest/), która jest oparty na [pyglet](http://pyglet.org/) i OpenGL. Jeśli jednak chcesz pisać na wiele urządzeń i systemów to raczej zmień język bo Python słabo tu się nadaje. Np. [GodotEngine](https://godotengine.org/) potrafi tworzyć na wiele systemów i posiada język skryptowy podobny do Pythona. – furas Sep 23 '21 at 18:51
0

For changing states in the game (like state menu/paused/game) I recommend you using python-statemachine. https://pypi.org/project/python-statemachine/

If you encounter the problem which is not in pgzero documentation

https://pygame-zero.readthedocs.io/en/stable/

Easily said: If there is something pgzero cannot do, you have to go a layer down, that means you need to use pygame ( pgzero is based on pygame, so pygame syntax should work well if you have pgzero imported, but if not just import pygame as well.)

for example, I have changed mouse cursor with this pygame.mouse.set_cursor(pygame.cursors.broken_x) based on this documentation https://www.pygame.org/docs/ref/mouse.html#pygame.mouse.set_cursor

pygame.mouse.set_cursor(pygame.cursors.broken_x)

pygame itself offers a seweral cursors, but for a custom one, I guess you have to google liitle more.