python - Bullets shooting from wrong position after player moved in small "Space Invaders" game - Stack Overfl

时间: 2025-01-06 admin 业界

I'm a beginner in Python and trying to code a game such as Space Invaders.

I created everything but when I move my character and fire, the bullet will remain shooting from the character's original position.

I need it the be shot from the character's actual position at every time.

main.py

from turtle import Turtle, Screen
from player import Player
from bullet import Bullet, BULLET_SPEED

turtle = Turtle()


screen = Screen()
screen.setup(width=800, height=800)
screen.title("Shmup Test")
screen.bgcolor("black")

player = Player()
bullet = Bullet()


screen.listen()
screen.onkey(key="Up", fun=player.go_up)
screen.onkey(key="Down", fun=player.go_down)
screen.onkey(key="Left", fun=player.go_left)
screen.onkey(key="Right", fun=player.go_right)

screen.onkey(key="space", fun=bullet.fire_bullet)

while True:
    bullet.forward(BULLET_SPEED)

player.py

from turtle import Turtle, Screen

MOVE_DISTANCE = 10

screen = Screen()


class Player(Turtle):

    def __init__(self):
        super().__init__()
        self.image = "penguin.gif"
        screen.register_shape(self.image)
        self.shape(self.image)
        self.penup()
        self.speed(0)
        self.setposition(0, -180)
        self.setheading(90)

    def go_up(self):
        new_y = self.ycor() + MOVE_DISTANCE
        if new_y > 330:
            new_y = 330
        self.goto(self.xcor(), new_y)

    def go_down(self):
        new_y = self.ycor() - MOVE_DISTANCE
        if new_y < -330:
            new_y = -330
        self.goto(self.xcor(), new_y)

    def go_left(self):
        new_x = self.xcor() - MOVE_DISTANCE
        if new_x < -330:
            new_x = -330
        self.goto(new_x, self.ycor())

    def go_right(self):
        new_x = self.xcor() + MOVE_DISTANCE
        if new_x > 330:
            new_x = 330
        self.goto(new_x, self.ycor())

bullet.py

from turtle import Turtle, Screen
from player import Player

BULLET_SPEED = 10

self = Turtle()
player = Player()
screen = Screen()


class Bullet(Turtle):

    def __init__(self):
        super().__init__()
        self.color("red")
        self.shape("circle")
        self.penup()
        self.speed(0)
        self.setposition(player.xcor(), player.ycor())
        self.setheading(90)
        self.shapesize(.5, .5)
        self.hideturtle()

    def fire_bullet(self):
        x_pos = player.xcor()
        y_pos = player.ycor()
        self.setposition(x_pos, y_pos)
        self.showturtle()

I'm a beginner in Python and trying to code a game such as Space Invaders.

I created everything but when I move my character and fire, the bullet will remain shooting from the character's original position.

I need it the be shot from the character's actual position at every time.

main.py

from turtle import Turtle, Screen
from player import Player
from bullet import Bullet, BULLET_SPEED

turtle = Turtle()


screen = Screen()
screen.setup(width=800, height=800)
screen.title("Shmup Test")
screen.bgcolor("black")

player = Player()
bullet = Bullet()


screen.listen()
screen.onkey(key="Up", fun=player.go_up)
screen.onkey(key="Down", fun=player.go_down)
screen.onkey(key="Left", fun=player.go_left)
screen.onkey(key="Right", fun=player.go_right)

screen.onkey(key="space", fun=bullet.fire_bullet)

while True:
    bullet.forward(BULLET_SPEED)

player.py

from turtle import Turtle, Screen

MOVE_DISTANCE = 10

screen = Screen()


class Player(Turtle):

    def __init__(self):
        super().__init__()
        self.image = "penguin.gif"
        screen.register_shape(self.image)
        self.shape(self.image)
        self.penup()
        self.speed(0)
        self.setposition(0, -180)
        self.setheading(90)

    def go_up(self):
        new_y = self.ycor() + MOVE_DISTANCE
        if new_y > 330:
            new_y = 330
        self.goto(self.xcor(), new_y)

    def go_down(self):
        new_y = self.ycor() - MOVE_DISTANCE
        if new_y < -330:
            new_y = -330
        self.goto(self.xcor(), new_y)

    def go_left(self):
        new_x = self.xcor() - MOVE_DISTANCE
        if new_x < -330:
            new_x = -330
        self.goto(new_x, self.ycor())

    def go_right(self):
        new_x = self.xcor() + MOVE_DISTANCE
        if new_x > 330:
            new_x = 330
        self.goto(new_x, self.ycor())

bullet.py

from turtle import Turtle, Screen
from player import Player

BULLET_SPEED = 10

self = Turtle()
player = Player()
screen = Screen()


class Bullet(Turtle):

    def __init__(self):
        super().__init__()
        self.color("red")
        self.shape("circle")
        self.penup()
        self.speed(0)
        self.setposition(player.xcor(), player.ycor())
        self.setheading(90)
        self.shapesize(.5, .5)
        self.hideturtle()

    def fire_bullet(self):
        x_pos = player.xcor()
        y_pos = player.ycor()
        self.setposition(x_pos, y_pos)
        self.showturtle()
Share Improve this question edited yesterday ggorlen 56.5k7 gold badges109 silver badges148 bronze badges asked yesterday Needle_KaneNeedle_Kane 311 silver badge1 bronze badge New contributor Needle_Kane is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
Add a comment  | 

2 Answers 2

Reset to default 3

The Bullet class uses a separate instance of the Player class to determine from where to shoot.

So it does not notice if the Player instance used in main.py has moved.

You should use only one instance of Player that is shared between main.py and Bullet. A typical way to achieve that would be to create it in main.py and pass it as argument to Bullet.

There are several changes needed:

In main.py:

player = Player()
bullet = Bullet()

becomes

player = Player()
bullet = Bullet(player)

In bullet.py:

player = Player()

class Bullet(Turtle):
    def __init__(self):
        super().__init__()

becomes

class Bullet(Turtle):
    def __init__(self, player):
        super().__init__()
        self.player = player

and

    def fire_bullet(self):
        x_pos = player.xcor()
        y_pos = player.ycor()

becomes

    def fire_bullet(self):
        x_pos = self.player.xcor()
        y_pos = self.player.ycor()

This answer nicely identifies the cause of your main problem and provides a quick fix, but beyond that, there are a number of serious design flaws that will make it difficult for you to move forward with this game in any sensible capacity.

I'll defer to other posts below for deeper explanations, but suffice to say your event loop won't let you move multiple turtles in real-time very easily. You'll likely want to disable the internal turtle event loop with screen.tracer(0) and use a custom event loop that will let you run only one screen update per frame, after repositioning all entities. This effectively lets you move multiple entities at once, enabling real-time animation. Without that, each turtle will need to complete its movement in a single-threaded manner before the next turtle can move, which is basically a non-starter.

Here's the suggested rewrite:

from turtle import Turtle, Screen


class Player:
    def __init__(self):
        self.speed = 10
        self.bullets = []
        self.pen = Turtle()
        self.pen.penup()

    def update(self):
        for bullet in self.bullets:
            bullet.move()

    def forward(self):
        self.pen.forward(self.speed)

    def left(self):
        self.pen.left(self.speed)

    def right(self):
        self.pen.right(self.speed)

    def fire(self):
        pen = self.pen
        bullet = Bullet(pen.xcor(), pen.ycor(), pen.heading())
        self.bullets.append(bullet)


class Bullet:
    def __init__(self, x, y, heading):
        self.speed = 15
        self.pen = pen = Turtle()
        pen.penup()
        pen.goto(x, y)
        pen.shape("circle")
        pen.setheading(heading)
        pen.shapesize(0.3, 0.3)

    def move(self):
        self.pen.forward(self.speed)


screen = Screen()
screen.tracer(0)
player = Player()
frame_delay_ms = 1000 // 30

actions = dict(
    Left=player.left,
    Right=player.right,
    Up=player.forward,
    space=player.fire,
)
keys_pressed = set()

def bind(key):
    screen.onkeypress(lambda: keys_pressed.add(key), key)
    screen.onkeyrelease(lambda: keys_pressed.remove(key), key)

for key in actions:
    bind(key)

def tick():
    for action in list(keys_pressed):
        actions[action]()

    player.update()
    screen.update()
    screen.ontimer(tick, frame_delay_ms)

screen.listen()
tick()
screen.exitonclick()

I've kept this in a single file for simplicity, but if you do break it out again, don't instantiate anything in your class files in the global scope. All instantiation will happen in the shared main.py file, other than bullets, which the player imports and instantiates when shot (you could make the bullet list global if you want, but attaching bullets to players makes sense--the bullets belong to a particular player).

See these threads for motivation for the changes:

  • How to bind several key presses together in turtle graphics?
  • How to create a subclass in python that is inherited from turtle Module

Note that this still isn't a perfect design--turtles can't be de-allocated, so you'll need an object pool for your bullets to recycle them when they hit an object or leave the screen. The game will slow down as you shoot more bullets, which continue moving forever once shot.

You'll probably also want a cooldown period for each shot to avoid too much spamming.

Turtle is generally not a good tool for making real-time games, but this should give you an acceptable real-time game set up for educational purposes.