0

This is my first question!

I finished making a simple space invaders game in Python turtle graphics and noticed an annoying problem: the more objects I have on my screen, the slower the program runs.

My friend told me that I need to use multi-threading so that all the commands will run concurrently, and that way, the game will run smooth.

I added only the relevent code for my problem which is to move two enemy invaders from side to side of the screen. I think this will be enough to help me through this.

With this code, the enemies get stuck in their own place a couple of miliseconds every now and then. It's very noticable, what should I do?

one_enemy = turtle.Turtle()
one_enemy.shape("Invader.gif")
one_enemy.penup()
one_enemy.speed(0)
x = random.randint(-200, 200)
y = random.randint(100, 200)
one_enemy.setposition(x, y)

two_enemy = turtle.Turtle()
two_enemy.shape("Invader.gif")
two_enemy.penup()
two_enemy.speed(0)
x = random.randint(-200, 200)
y = random.randint(100, 200)
two_enemy.setposition(x, y)

def move_enemy_horizontally(enemy, direction):
    while True:
        while direction == "right":
            x = enemy.xcor()
            x += enemyspeed
            enemy.setx(x)
            if enemy.xcor() > 288:
                y = enemy.ycor()
                y -= 50
                enemy.sety(y)
                direction = "left"
        while direction == "left":
            x = enemy.xcor()
            x -= enemyspeed
            enemy.setx(x)
            if enemy.xcor() < -288:
                y = enemy.ycor()
                y -= 50
                enemy.sety(y)
                direction = "right"

t = threading.Thread(target=move_enemy_horizontally, args=(one_enemy, direction))
t.start()
t2 = threading.Thread(target=move_enemy_horizontally, args=(two_enemy, direction))
t2.start()
cdlane
  • 40,441
  • 5
  • 32
  • 81
  • 2
    https://stackoverflow.com/questions/16119991/how-to-speed-up-pythons-turtle-function-and-stop-it-freezing-at-the-end – eatmeimadanish Oct 31 '18 at 19:17
  • 1
    As an aside, I think it should be noted that a slowness caused by too many sprites on screen will actually make your game a closer emulation of the original Space Invaders. That was how the original game achieved the increasing difficulty as the game progressed. Fewer sprites on screen made the game run faster, which made the sprites move faster, which made the game harder as you went along. Long story short, one of your options is to call your bug a feature and ship it. – mypetlion Oct 31 '18 at 19:20
  • Yes, I understand that the classic kind worked like this but I want to make it a bit more "modern", it looks like the linked answer will solve my problem. –  Oct 31 '18 at 19:23
  • A Barrier (https://docs.python.org/3/library/threading.html#barrier-objects) might be what you need, but I think the best you could do is use it before the draw code and hope that it helps. It's a good learning exercise, but using multithreading in Python won't actually speed up your execution, because Python can't actually do true multithreading. So it would be best to stick to other optimizations. – Humphrey Winnebago Oct 31 '18 at 22:41

1 Answers1

0

I'm surprised your code works at all -- when I finish it off and run it, only one turtle moves. Perhaps it's a difference between Windows and Unix or some such. Regardless, my belief has been that you can't do tkinter/turtle graphics in any thread but the main one. So, I've developed this approach which allows the secondary threads to calculate things about their turtle but ultimately pass the graphics command onto the primary thread:

from turtle import Screen, Turtle
from random import randint
from threading import Thread, active_count
from queue import Queue

QUEUE_SIZE = 1
ENEMY_SPEED = 3

def move_enemy_horizontally(enemy, direction):
    x, y = enemy.position()

    while True:
        while direction == "right":

            if x > 288:
                y -= 50
                actions.put((enemy.sety, y))
                direction = "left"
            else:
                x += ENEMY_SPEED
                actions.put((enemy.setx, x))

        while direction == "left":
            if x < -288:
                y -= 50
                actions.put((enemy.sety, y))
                direction = "right"
            else:
                x -= ENEMY_SPEED
                actions.put((enemy.setx, x))

def process_queue():
    while not actions.empty():
        action, argument = actions.get()
        action(argument)

    if active_count() > 1:
        screen.ontimer(process_queue, 100)

actions = Queue(QUEUE_SIZE)

x, y = randint(-200, 200), randint(100, 200)

direction = "right"

for dy in range(2):
    for dx in range(2):
        enemy = Turtle("turtle", visible=False)
        enemy.speed('fastest')
        enemy.setheading(270)
        enemy.penup()
        enemy.setposition(x + dx * 60, y + dy * 100)
        enemy.showturtle()

        Thread(target=move_enemy_horizontally, args=(enemy, direction), daemon=True).start()

    direction = ["left", "right"][direction == "left"]

screen = Screen()

process_queue()

screen.mainloop()

I'm not saying that threads are the answer to your problem, nor that they won't slow down as you add more enemies. Another approach might be to treat the enemies more as a block, not complete individuals, and use timer events within the graphics library instead of threads.

cdlane
  • 40,441
  • 5
  • 32
  • 81