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 ;)
Related
I'm having difficulty with one small part of my code. I'm modifying a player's HP while they're in combat, but when exiting combat the HP resets to 100. This is odd because the enemy's HP is preserved. By using print(id(player.hp)) I can see that a new instance of player.hp is being created, I just don't know why. I've tried googling the issue but I can't find an example that fits my problem. I'll try to only include the pertinent parts of the code below (there's a lot of superfluous writing that isn't necessary here), but if I need to include everything I will.
class Alleyway(Scene):
room_items = []
room_characters = []
room_enemies = ["hobo"]
def __init__(self):
self.fresh_arrival = True
self.north = False
self.east = True
self.south = True
self.west = False
self.enemy = Hobo()
def fight(self, player, weapon): #Lots of what makes this fun is edited out
while True:
if self.enemy.hp > 0:
print("Do you attack or flee?")
words = input("> ").lower()
if words == "flee":
map_ = Map("alleyway")
game = Engine(map_)
game.play()
if words == "attack":
miss = randint(1,4)
if miss == 1:
print("Too bad, you miss!")
else:
print("You got him!")
self.enemy.hp -= weapon.damage
print("Hobo's HP: {}".format(max(self.enemy.hp, 0)))
if self.enemy.hp > 0:
print("The hobo takes a swing at you.")
hobomiss = randint(1,5)
if hobomiss == 1:
print("He missed!")
else:
print("Ouch, he got you!")
player.hp -= self.enemy.damage
print("Your HP: {}".format(max(player.hp, 0)))
else:
print("No idea what you mean. Attack the man or flee.")
else:
print("You defeat the hobo!")
print()
print("Dirty shoes and shank added to inventory.")
print()
shoes = DirtyShoes()
shank = Shank()
player.inventory.append(shoes)
player.inventory.append(shank)
self.room_enemies.remove("hobo")
map_ = Map("alleyway")
game = Engine(map_)
game.play()
When fight() is called, an instance of Player() is sent in as an argument, which has an instance variable of self.hp = 100.
This is still a work in progress so please gloss over the fact that it doesn't exit when the player dies and other silly things like that (:
Now, when the player flees in the middle of the fight, my goal is for the enemy's HP to be preserved, the player's HP to be preserved, and the items to be added to the player's inventory. So far, the enemy's HP is accurately preserved and everything else in my code regarding the enemy's HP and his alive/dead status work great when combat is exited. My issue is with player.hp -= enemy.damage, player.inventory.append(shoes), and player.inventory.append(shank). None of them work. The player.hp displays correctly during the fight and decreases as expected, however once the player exits combat the HP is reset to 100 and there is nothing added to the inventory. I've tried player.hp = player.hp - enemy.damage for the HP issue and it still creates a new variable. I'm just not sure what's going on since the enemy portions are working fine.
If more information is needed please let me know. And if there are any obvious ways my code can be improved I'm totally open to suggestions.
Got it! I was defining player = Player() inside of a method, so it only created the instance for that method (I think). So I defined it outside of the method and now the modified values stick!
I am currently working on scales using tkinter. I have gotten the code to work except one thing. I have bound keys to the motion of a servo motor. When I press the keys however the scale does not follow what the key bindings do.How can I get the scale to follow the key bindings?
Please see code below
....GPIO setup code above not shown...
def fMin(event):
iDCServo = 2.5
pServo.ChangeDutyCycle(iDCServo) #this is pwm code for the servo motor
def fMin2(event):
iDCServo = 7.5
pServo.ChangeDutyCycle(iDCServo)
def fMax(event):
iDCServo = 12.5
pServo.ChangeDutyCycle(iDCServo)
def fMax2(event):
iDCServo = 7.5
pServo.ChangeDutyCycle(iDCServo)
def fOperation():
global guiSliderServo1, iLoop
while True:
win = Tk()
win.wm_title(">>>Servo Slider<<<")
win.geometry("800x100+0+0")
guiSliderServo1 = Scale(win, from_=-45, to_=45, orient=HORIZONTAL, length_=700, sliderlength_=10, tickinterval_=5, command=fSliderServo1)
guiSliderServo1.set(0)
guiSliderServo1.grid(row=0)
guiSliderServo1.pack(side=TOP)
guiSliderServo1.bind('<Key-q>', fMin)
guiSliderServo1.bind('<KeyRelease-q>', fMin2)
guiSliderServo1.bind('<Key-e>', fMax)
guiSliderServo1.bind('<KeyRelease-e>', fMax2)
guiSliderServo1.focus_set()
guiButtonExit = Button(win, text="Exit Slider", command=quit)
guiButtonExit.pack(side=BOTTOM)
win.mainloop()
...there is some remaining code regarding looks, functions and imports not shown, not sure, but probably would just clutter the real question.
Thank you
The while True hogs the computer so there is no time left for Tkinter to update the widget. You should be able to just delete the while True in the above code and be fine because Tkinter's mainloop() essentially does the same thing, i.e. continually checks for a keypress. Also mixing grid and pack, you use both, yields unknown results. Settle on one and use it. Note that this code does not change the scale, and the Button command should be win.quit.
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 am trying to make a simon says game and am having trouble collecting the users choice to compare against the computer's pattern. I am using four buttons to select the colors that flash on the screen. The problem I am having is my program continues to chug along even if my user hasn't selected a color yet, despite my best efforts to stop it, what can I do to stop the program? Here is my code...
Sequence[]
PosColors = ["Yellow","Red","Blue","Green"]
PlayerChoice[]
Score = 0
def Game(Sequence, PlayerChoice,Score):
Score += 1
PlayerChoice = []
Number = randint(0,3)
Sequence.append(PosColors[Number])
for i in Sequence:
RunTime = Score * 1000
Gui.after(1000, blink, rect, SquareCanvas , i)
RunTime = (Score + 1) * 1000
Gui.after(2000, blink, rect, SquareCanvas , White)
X = len(Sequence)
Index = 0
Color = " "
while Color != " ":
BlueButton.config(command = partial(setSelection,NeonBlue))
RedButton.config(command = partial(setSelection,NeonRed))
YellowButton.config(command = partial(setSelection,NeonYellow))
GreenButton.config(command = partial(setSelection,NeonGreen))
Color = getSelection()
print(Color)
while Color != " ":
PlayerTurn(Sequence,PlayerChoice,Score,Index)
X -= 1
Index +=1
def setSelection(Color):
if Color == NeonBlue:
return "NeonBlue"
elif Color == NeonRed:
return "NeonRed"
elif Color == NeonGreen:
return "NeonGreen"
elif Color == NeonYellow:
return "NeonYellow"
def getSelection():
return TheColor
def PlayerTurn(Sequence, PlayerChoice,Score,Index):
PlayerChoice.append(Color)
print(PlayerChoice)
Label1.config(text = 'Well done! \nYou advance to the next round!')
I am planning on passing this through a checker and to loop it until there is an error but I need to get past this front loop first... I know my logic works as I created it using just lists and the command line before moving on the the graphical portion I just need to know how to get the program to stop to collect the user guess, especially as the length of the pattern gets larger. Originally I had the command function included later where I build my Buttons but this placement seems to get as close as possible to what I am looking for. Any help is appreciated thank you
You're going to need to rethink your main program logic. Tkinter is designed such that you should never have your own infinite loop. Tkinter already has an infinite loop -- the mainloop() method of the root window. You can't write logic that waits for input from the user. You have to think of the GUI as in a perpetual state of waiting, and you need set up handlers (button callbacks or event bindings) to react to events.
You also need to understand that a button command can't "return" anything. "Can't" is a bit strong since it obviously can, but there's nothing waiting for the result -- no place to return it to. You need to design your button function such that it sets a global or instance variable.
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()