Search code examples
pythonpython-3.6collisionturtle-graphics

Collisions aren't registering with Python Turtles


I'm creating a simple 2D shooter following an online tutorial, and the enemy sprites (except 1, there are 5 total) are not abiding by my collision code. Everything works except the bullet object that the player controls simply passes right through the enemy sprites. Error messages I received:

Traceback (most recent call last):
      File "C:\Python\Python36\pygame projects\space invaders.py", line 163, in <module>
        bullet.hideturtle()
      File "C:\Python\Python36\lib\turtle.py", line 2322, in hideturtle
        self.pen(shown=False)
      File "C:\Python\Python36\lib\turtle.py", line 2459, in pen
        self._update()
      File "C:\Python\Python36\lib\turtle.py", line 2660, in _update
        self._update_data()
      File "C:\Python\Python36\lib\turtle.py", line 2646, in _update_data
        self.screen._incrementudc()
      File "C:\Python\Python36\lib\turtle.py", line 1292, in _incrementudc
        raise Terminator
    turtle.Terminator

If you skim through the code half way you'll find the Pythagoras theory I used for the collision math, unsure if it's correct or not:

enemies = []
number_of_enemies = 5
for i in range(number_of_enemies):
    enemies.append(turtle.Turtle())

for enemy in enemies:
    enemy.color("green")
    enemy.shape("circle")
    enemy.penup()
    enemy.speed(0)
    x = random.randint(-200, 200)
    y = random.randint(100, 250)
    enemy.setposition(x, y)

enemyspeed = 2

def isCollision(t1, t2):
    distance = math.sqrt(math.pow(t1.xcor() - t2.xcor(), 2)+ math.pow(t1.ycor() - t2.ycor(), 2))
    if distance < 15:
        return True
    else:
        return False



while True:

for enemy in enemies:
    x = enemy.xcor()
    x += enemyspeed
    enemy.setx(x)

    if enemy.xcor() > 280:
        for e in enemies:
            y = enemy.ycor()
            y -= 40
            enemy.sety(y)
        enemyspeed *= -1

    if enemy.xcor() < -280:
        for e in enemies:
            y = enemy.ycor()
            y -= 40
            enemy.sety(y)
        enemyspeed *= -1

    if bulletstate == "fire":
        y = bullet.ycor()
        y += bulletspeed
        bullet.sety(y)

    if isCollision(bullet, enemy):
        #reset the bullet
        bullet.hideturtle()
        bulletstate = "ready"
        bullet.setposition(0, -400)
        #reset the enemy
        x = random.randint(-200, 200)
        y = random.randint(100, 250)
        enemy.setposition(x, y)

    if isCollision(player, enemy):
        player.hideturtle()
        enemy.hideturtle()
        print ("Get To The Doctors! You're Riddled!")
        break

Apologies if the answer is blatantly obvious. I would be grateful for the time anyone spends explaining the error. Others have posted similar questions, but every one has examples which apply to them and I'm still at the stage where it is tricky for me to visualise others' examples. Any help would greatly be appreciated.


Solution

  • I think your primary issue is you're updating the bullet between every enemy update instead of once for all the the enemy updates. This creates a skew between the visual and what's really happening. Also, you don't need to define the Pythagorean formula, turtles already know it! Below is my rework of your code to address the above and rework the the coding style and efficiency a bit:

    from turtle import Turtle, Screen
    from random import randint
    
    NUMBER_OF_ENEMIES = 5
    ENEMY_JUMP = 40
    
    BULLET_SPEED = 20
    PLAYER_SPEED = 15
    
    SAFETY_DISTANCE = 15
    
    GALLERY_WIDTH, GALLERY_HEIGHT = 560, 550
    GALLERY_BORDER = 80
    
    # player movement
    def move_left():
        x = player.xcor() - PLAYER_SPEED
    
        if x < -GALLERY_WIDTH / 2:
            x = -GALLERY_WIDTH / 2
    
        player.setx(x)
    
    def move_right():
        x = player.xcor() + PLAYER_SPEED
    
        if x > GALLERY_WIDTH / 2:
            x = GALLERY_WIDTH / 2
    
        player.setx(x)
    
    def fire_bullet():
        global bulletstate
    
        if bulletstate == 'ready':
            bulletstate = 'fire'
            bullet.setposition(player.position())
            bullet.forward(BULLET_SPEED / 2)
            bullet.showturtle()
    
    def isCollision(t1, t2):
        return t1.distance(t2) < SAFETY_DISTANCE
    
    def move():
        global enemy_speed, bulletstate
    
        screen.tracer(False)
    
        for enemy in enemies:
            x = enemy.xcor() + enemy_speed
    
            enemy.setx(x)
    
            if x > GALLERY_WIDTH / 2:
                for e in enemies:
                    y = e.ycor() - ENEMY_JUMP
                    e.sety(y)
    
                enemy_speed *= -1
    
            elif x < -GALLERY_WIDTH / 2:
                for e in enemies:
                    y = e.ycor() - ENEMY_JUMP
                    e.sety(y)
    
                enemy_speed *= -1
    
            if isCollision(player, enemy):
                player.hideturtle()
                enemy.hideturtle()
                print("Get To The Doctors! You're Riddled!")
                screen.update()
                return
    
            if isCollision(bullet, enemy):
                # reset the bullet
                bullet.hideturtle()
                bullet.setposition(0, -GALLERY_HEIGHT)
                bulletstate = 'ready'
    
                # reset the enemy
                enemy.hideturtle()
                x = randint(GALLERY_BORDER - GALLERY_WIDTH / 2, GALLERY_WIDTH / 2 - GALLERY_BORDER)
                y = randint(GALLERY_HEIGHT / 2 - 175, GALLERY_HEIGHT / 2 - 25)
                enemy.setposition(x, y)
                enemy.showturtle()
    
        if bulletstate == 'fire':
            bullet.forward(BULLET_SPEED)
    
            # check to see if the bullets has gone to the top
            if bullet.ycor() > GALLERY_HEIGHT / 2:
                bullet.hideturtle()
                bulletstate = 'ready'
    
        screen.tracer(True)
    
        screen.ontimer(move, 25)
    
    screen = Screen()
    screen.setup(GALLERY_WIDTH + GALLERY_BORDER, GALLERY_HEIGHT + GALLERY_BORDER)
    screen.bgcolor('black')
    
    player = Turtle('turtle', visible=False)
    player.speed('fastest')
    player.color('red')
    player.penup()
    player.setheading(90)
    player.sety(-250)  # need to define this
    player.showturtle()
    
    enemies = []
    
    enemy_speed = 1
    
    for _ in range(NUMBER_OF_ENEMIES):
        enemy = Turtle('circle', visible=False)
        enemy.speed('fastest')
        enemy.color('green')
        enemy.penup()
    
        x = randint(GALLERY_BORDER - GALLERY_WIDTH / 2, GALLERY_WIDTH / 2 - GALLERY_BORDER)
        y = randint(GALLERY_HEIGHT / 2 - 175, GALLERY_HEIGHT / 2 - 25)  # define these!
        enemy.setposition(x, y)
        enemy.showturtle()
    
        enemies.append(enemy)
    
    # create the player's spunk shots
    bullet = Turtle('triangle', visible=False)
    bullet.speed('fastest')
    bullet.shapesize(0.5)
    bullet.color('white')
    bullet.penup()
    bullet.setheading(90)
    
    # define bullet state
    # ready - ready to fire
    # fire - bullet is firing
    bulletstate = 'ready'
    
    screen.onkey(move_left, 'Left')
    screen.onkey(move_right, 'Right')
    screen.onkey(fire_bullet, 'space')
    
    screen.listen()
    
    move()
    
    screen.mainloop()
    

    I guessed at the missing pieces. See how this plays for you.