I am trying to program a top-down shooter game in Python and I'm a problems with controlling and removing bullets. To summarize, I have a class for the ship the player controls and a class for the bullets the ship produces when the player fires. The bullets propel upward on the screen and once they exit the playing view, they are removed. The problem is that when bullets get removed, something goes wrong. I'm not sure what it is (I think it's the for loop that handles moving and deleting all the bullets) but I get this weird misalignment with the left-side bullets like when the game has to remove a bullet, it skips (just for a single frame) over moving the left bullet just below the top of the screen while the right bullet remains unaffected. The
Here's an image for reference.
Here is the bullet class:
class Bullet():
def __init__(self, gun_pos):
self.image = pygame.image.load('Images/Bullet.png').convert()
self.rect = self.image.get_rect(midbottom = gun_pos)
play_surface.blit(self.image, self.rect)
def move(self):
self.rect.move_ip(0, -8)
def draw(self):
play_surface.blit(self.image, self.rect)
This part handles firing. If the player presses the 'fire' key and if the player isn't reloading, then the game produces two bullets at the top right and left corners of the player ship.
if not player.reloading and firing:
bullet = Bullet(player.rect.topleft)
bullets.append(bullet)
bullet = Bullet(player.rect.topright)
bullets.append(bullet)
player.reloading = 4
Here is the portion of the main loop that handles moving bullets and removing them from the game when they exit the top of the main surface.
while running:
for bullet in bullets:
bullet.move()
if bullet.rect.bottom < 0:
bullets.remove(bullet)
continue
If I split the for-loop into two for-loops with one loop handling moving the bullets and another checking if they need to be removed, there's no misalignment but I'm curious why bunching the loops together causes the problem.
Here is the part of the code that handles drawing the graphics, including the player and the enemies:
pygame.draw.rect(play_surface, (0, 0, 0), SCREEN_RECT)
player.draw()
for enemy in enemies:
enemy.draw()
for bullet in bullets:
bullet.draw()
pygame.display.flip()
I've implemented an enemy and I'm having a similar problem when bullets hit the enemy. If two bullets hit at once, then the bullet on the right will not be removed until the next frame.
you should use the pygame Sprites & Groups.
delete your move and draw method.
make a update method in your bullet class.
def update(self):
self.rect.move_ip(0, -8)
if self.rect.bottom < 0: self.kill()
make a spritegroup for your bullets, and add your bullets to it.
the bulletGroup.update() method call the update() method for each bullet.
bulletGroup = pygame.sprite.Group()
bulletGroup.add(bullets)
while True:
# do your stuff
bulletGroup.update()
# do your other stuff
pygame.display.flip()
Related
I am learning python, I have started on a simple pong game. I have the code to draw two paddles on both sides of the screen the paddles don't appear just a blank screen in the output. Here is the code:
import turtle
#creating a window for pong
win = turtle.Screen()
win.title("PONG")
win.bgcolor("white")
win.setup(width=800, height=600)
win.tracer(0)
#creating paddle A
paddle_a = turtle.Turtle()
paddle_a.speed(0)
paddle_a.color("black")
paddle_a.shape("square")
paddle_a.shapesize(stretch_wid=5, stretch_len=1)
paddle_a.penup()
paddle_a.goto(-350,0)
#creating paddle B
paddle_b = turtle.Turtle()
paddle_b.speed(0)
paddle_b.color("black")
paddle_b.shape("square")
paddle_b.shapesize(stretch_wid=5, stretch_len=1)
paddle_b.penup()
paddle_b.goto(350,0)
#ball
ball = turtle.Turtle()
ball.speed(0)
ball.color("black")
ball.shape("square")
ball.penup()
ball.goto(0,0)
win.exitonclick()
PLEASE TELL ME WHERE I AM WRONG!!
If you remove win.tracer(0), the drawn shapes appear. It's unintelligible what you want to achieve with tracer(0), since according to the documentation this means only each zeroth regular screen update is really performed, which is nonsense per se.
This is my code to make a rocket move around in a turtle screen:
import turtle
s = turtle.Screen()
image = "rocket.gif"
s.addshape(image)
turtle.shape(image)
s.bgcolor("black")
def fd():
turtle.fd(5)
def bk():
turtle.bk(10)
def lt():
turtle.lt(5)
def rt():
turtle.rt(5)
turtle.penup()
turtle.speed(0)
turtle.home()
s.listen()
s.onkeypress(fd, "Up")
s.onkeypress(bk, "Down")
s.onkeypress(lt, "Left")
s.onkeypress(rt, "Right")
When I press the up button the rocket moves forward, and when I press the right button, the rocket image does not turn, but when it moves forward again the direction of the movement does change.
I read this at the help() section at Python3 IDLE:
Image shapes do not rotate when turning the turtle, so they do not display the heading of the turtle!
But, I do not understand is this for turtle.rt() or turtle.setheading()?
Even if it is for both, how do I rotate the image?
Note: Surprisingly it works with
trinket.io
We can make a shape appear to rotate by telling a turtle to switch between different related shapes.
use setshape (setsh) and setheading (seth).For more details Visit http://mia.openworldlearning.org/howdoi/ftn.htm
I'm very new to threading am and still trying to get my head around how to code most of it. I am trying to make what is effectively a text editor-type input box and so, like every text editor I know, I need a cursor-bar thing to indicate the location at which the text is being typed to. Thus I also want to be able to flicker/blink the cursor, which i thought would also prove good practice for threading.
I have a class cursor that creates a rectangle on the canvas based on the bounding box of my canvas text, but I then need to change it's location as more characters are typed; stop the thread and instantaneously hide the cursor rectangle when the user clicks outside of the input box; and lastly restart the thread/a loop within the thread (once again, sharing a variable) - the idea here being that the cursor blinks 250 times and after then, disappears (though not necessary, I thought it would make a good learning exercise).
So assuming that I have captured the events needed to trigger these, what would be the best way to go about them? I have some code, but I really don't think it will work, and just keeps getting messier. My idea being that the blinking method itself was the thread. Would it be better to make the whole class a thread instead? Please don't feel restricted by the ideas in my code and feel free to improve it. I don't think that the stopping is working correctly because every time I alt+tab out of the window (which i have programmed to disengage from the input box) the Python shell and tkinter GUI stop responding.
from tkinter import *
import threading, time
class Cursor:
def __init__(self, parent, xy):
self.parent = parent
#xy is a tuple of 4 integers based on a text object's .bbox()
coords = [xy[2]] + list(xy[1:])
self.obj = self.parent.create_rectangle(coords)
self.parent.itemconfig(self.obj, state='hidden')
def blink(self):
blinks = 0
while not self.stop blinks <= 250:
self.parent.itemconfig(self.obj, state='normal')
for i in range(8):
time.sleep(0.1)
if self.stop: break
self.parent.itemconfig(self.obj, state='hidden')
time.sleep(0.2)
blinks += 1
self.parent.itemconfig(self.obj, state='hidden')
def startThread(self):
self.stop = False
self.blinking = threading.Thread(target=self.blink, args=[])
self.blinking.start()
def stopThread(self):
self.stop = True
self.blinking.join()
def adjustPos(self, xy):
#I am not overly sure if this will work because of the thread...
coords = [xy[2]] + list(xy[1:])
self.parent.coords(self.obj, coords)
#Below this comment, I have extracted relevant parts of classes to global
#and therefore, it may not be completely syntactically correct nor
#specifically how I initially wrote the code.
def keyPress(e):
text = canvas.itemcget(textObj, text)
if focused:
if '\\x' not in repr(e.char) and len(e.char)>0:
text += e.char
elif e.keysym == 'BackSpace':
text = text[:-1]
canvas.itemconfig(textObj, text=text)
cursor.adjustPos(canvas.bbox(textObj))
def toggle(e):
if cursor.blinking.isAlive(): #<< I'm not sure if that is right?
cursor.stopThread()
else:
cursor.startThread()
if __name__=="__main__":
root = Tk()
canvas = Canvas(root, width=600, height=400, borderwidth=0, hightlightthickness=0)
canvas.pack()
textObj = canvas.create_text(50, 50, text='', anchor=NW)
root.bind('<Key>', keyPress)
cursor = Cursor(canvas, canvas.bbox(textObj))
#Using left-click event to toggle thread start and stop
root.bind('<ButtonPress-1', toggle)
#Using right-click event to somehow restart thread or set blinks=0
#root.bind('<ButtonPress-3', cursor.dosomething_butimnotsurewhat)
root.mainloop()
If there is a better way to do something written above, please also tell me.
Thanks.
I have a class that represents a bit of text that can be drawn to the screen. This class is intended to be used in relation to a Window object that, when it is drawn, passes a subsurface of the surface it was given to the draw() function of the text element.
The window draws, but the text does not. Nor does it draw when I invoke the TextElement's draw() function directly. I've examined the code with a debugger, and the blit is definitely being done. I've also tried switching the font to "Arial" instead of letting Pygame grab the default.
import pygame
#Background and edge color of windows
global bgcolor
global bordercolor
global txtcolor
#Pygame font object that most text will be drawn in
global mainfont
#Defaults; code that imports us can change these.
#Black background with white borders & text
bgcolor=(0,0,0)
txtcolor=(255,255,255)
bordercolor=(255,255,255)
#This will give us whatever font Pygame is set up to use as default
if not __name__== "__main__":
#Font module needs to be initialized for this to work; it should be if we're imported, but won't if
#we're being executed directly.
mainfont=pygame.font.SysFont(None,12)
else:
mainfont=None
class Window:
"""The base UI class. Is more-or-less a way to draw an empty square onto the screen. Other things are derived from this.
Also usable in its own right as a way to manage groups of more complex elements by defining them as children of
a basic window."""
def __init__(self, rect):
self.rect=rect
#'children' of a window are drawn whenever it is drawn, unless their positions are outside the area of the window.
self.children=[]
def draw(self, surface):
"Draw this window to the given surface."
pygame.draw.rect(surface, bgcolor, self.rect)
#Draw non-filled rect with line width 4 as the border
pygame.draw.rect(surface, bordercolor, self.rect, 4)
self._draw_children(surface)
def _draw_children(self,surface):
"Call draw() on each of the children of this window. Intended to be called as part of an element's draw() function."
for thechild in self.children:
#We use a subsurface because we only want our child elements to be able to access the area inside this window.
thechild.draw(surface.subsurface(self.rect))
class TextElement(str):
"""A bit of static text that can be drawn to the screen. Intended to be used inside a Window, but can be drawn
straight to a surface. Immutable; use DynamicTextElement for text that can change and move."""
def __new__(cls,text,font,rect=pygame.Rect((0,0),(0,0))):
self=super().__new__(cls,text)
self.image=font.render(text,True,txtcolor)
self.rect=rect
return self
def __repr__(self):
return str.join("",("TextElement('",self,"')"))
def draw(self,surface):
self.image.blit(surface,self.rect.topleft)
class DynamicTextElement():
"""A bit of text whose text can be changed and that can be moved. Slower than TextElement."""
def __init__(self,text,font,rect):
self.text, self.font, self.rect = text, font, rect
def __repr__(self):
return str.join("",("DynamicTextElement('",self.text,"')"))
def draw(self,surface):
image=self.font.render(self.text,True,txtcolor)
image.blit(surface,self.rect.topleft)
if __name__ == '__main__':
pygame.init()
mainfont=pygame.font.SysFont(None,12)
screen=pygame.display.set_mode((800,600))
mywindow=Window(pygame.Rect(150,150,600,300))
mytext=TextElement("Hello world! This is static, immutable text!",mainfont,pygame.Rect((200,200),(100,100)))
mydyntext=DynamicTextElement("And this is dnyamic, movable text!",mainfont,pygame.Rect((200,230),(100,100)))
print(mytext.image)
mywindow.children.append(mytext)
clock=pygame.time.Clock()
while True:
pygame.event.pump()
clock.tick(60)
screen.fill((55,55,55))
mywindow.draw(screen)
mytext.draw(screen)
pygame.display.flip()
Any ideas?
You use
self.image.blit(surface, self.rect.topleft)
but should be
surface.blit(self.image, self.rect.topleft)
surface and self.image in wrong places.
You tried to draw surface on text.
You have the same problem in DynamicTextElement()
BTW: I use Python 2 so I had to use str
self = str.__new__(cls,text)
in place of super()
self = super().__new__(cls,text)
Im really new to Python and recently I've been working on creating a small, space invaders style game in pygame. I've almost reached the end however, I want to make it so if the enemy ships (block) collide with my ship (player), a collision is detected, removing my both ships and displaying a short "Game Over" message.
So far I have the code for detecting when a bullet and an enemy ship collides, I have rewritten this for if my ship and an enemy ship collides but this code only works if I fire no shots, I also have to be moving from side to side for the collision to be detected (head on collisions do nothing) once the collision is detected and both ships disappear I am still able to fire bullets from the position at which the collision was detected. I have no idea why this happens. If anyone could help me out Id definitely appreciate it.
Heres the code in question:
for i in range(15):
block = Block(BLACK)
block.rect.x = random.randrange(screen_width)
block.rect.y = random.randrange(55) # change to 155 collisions fixed
block_list.add(block)
all_sprites_list.add(block)
for i in range(1):
player = Player()
player.rect.y = 480
player_list.add(player)
all_sprites_list.add(player)
...
for player in player_list:
player_hit_list = pygame.sprite.spritecollide(block, player_list, True)
for player in player_hit_list:
gameover.play()
player_list.remove(player)
all_sprites_list.remove(player)
block_list.remove(block)
all_sprites_list.remove(block)
for bullet in bullet_list:
block_hit_list = pygame.sprite.spritecollide(bullet, block_list, True)
for block in block_hit_list:
explosion.play()
bullet_list.remove(bullet)
all_sprites_list.remove(bullet)
score += 10
UPDATE
I have now managed to get the collision to detect properly, however I am still able to fire once the ships have disappeared (due to collision) is there any way I can hide the bullet once the collision has taken place?
Heres my updated code:
for i in range(15):
block = Block(BLACK)
block.rect.x = random.randrange(screen_width)
block.rect.y = random.randrange(55) # change to 155 collisions fixed
block_list.add(block)
all_sprites_list.add(block)
for i in range(1):
player = Player()
player.rect.y = 480
player_list.add(player)
all_sprites_list.add(player)
...
for player in player_list:
block_hit_list = pygame.sprite.spritecollide(player, block_list, True)
for block in block_hit_list:
gameover.play()
player_list.remove(player)
all_sprites_list.remove(player)
block_list.remove(block)
all_sprites_list.remove(block)
for bullet in bullet_list:
block_hit_list = pygame.sprite.spritecollide(bullet, block_list, True)
for block in block_hit_list:
explosion.play()
bullet_list.remove(bullet)
all_sprites_list.remove(bullet)
score += 10
Since you are working with groups, you might want to use this function, for handling collisions between groups:
Would be something like this (I haven't tried your code)
pygame.sprite.groupcollide(bullet_list, block_up_list, True, True, collided = None)
With both arguments True you remove both from the list. When you learn how to use groupcollide, you are going to notice that it's very useful.
Anyways, look for the function description in the pygame documentation and see some examples.
Hope that helps ;)