I am creating a sidescroller shooter game in which the enemies(drones)spawn contantly on the right side of the screen. The recently created enemies are appended to a list drones Each enemy goes towards the left of the screen. Once they are out of the screen, they get removed from the list.
I also want to remove the drones when they collide with the player. The following code works properly as long as there are multiple objects in the drones list, but when the list has only one item (so one drone is on the screen) that drone does not get deleted upon collision.
I have no idea why the first list item cannot be destroyed.
drones = []
class Drone
#other call methods
def hit(self):
del drones[drones.index(self)]
def generate_enemy():
global drones
if len(drones) < 20:
if (random.randint(1,100) == 1):
drones.append(Drone(screenWidth, random.randint(300,500))
def main():
global drones
while True:
#main loop stuff happening
if condition == True:
generate_enemy():
#main loop stuff happening
if player and (len(drones) > 0):
for i in range(len(drones)-1):
if drones[i].hitbox.colliderect(player.hitbox):
drones[i].hit()
main()
The "correct" way to do this in pygame is to let your classes inherit from Sprite, then use a Group instead of a simple list to store your enemies, and using pygame.sprite.spritecollide for collision detection with the dokill argument set to True.
Related
So the question is simple:
Given a Surface, let's call it screen and x,y coordinates, can I get anything that lays at that coordinates on that Surface?
For example, let's say we have typical, Player attack, and if the attack reach the Enemy position x,y then enemy dies.
So given this simple app (is an example only not a real app)
import pygame as pg
from pygame.math import Vector2
# pygame constants
CLOCK = pg.time.Clock()
WIN_SIZE = (1280, 640)
# pygame setup
pg.init()
# screen
window = pg.display.set_mode(WIN_SIZE, 0, 32)
background = pg.Surface(WIN_SIZE)
player = pg.Surface(Vector2(12, 64))
player_rect = player.get_rect(topleft=Vector2(150, 150))
player_attack = False
player.fill((102, 255, 178))
player_attack_range = 20 # player can hit at min 20 pixel from target
enemy = pg.Surface(Vector2(12, 64))
enemy_rect = player.get_rect(topleft=Vector2(175, 150))
enemy.fill(pg.Color("green"))
while True:
background.fill((0, 0, 0)) # screen clear
# Render enemy
attacked = False
if player_attack:
# !!!!! HERE !!!!!
# Now we check if the playuer is close enough to the enemy, so we MUST know the enemy pos
distance_x = abs(player_rect.x - enemy_rect.x)
if distance_x > player_attack_range:
attacked = True
enemy.fill(pg.Color("red"))
if not attacked:
enemy.fill(pg.Color("green"))
background.blit(enemy, enemy_rect.topleft)
# Render player
background.blit(player, player_rect.topleft)
# Events
for event in pg.event.get():
if event.type == pg.QUIT or (
event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE): # x button and esc terminates the game!
exit(1)
# ............. Mouse ............. #
if event.type == pg.MOUSEBUTTONDOWN:
if event.button == 1:
player_attack = True
if event.type == pg.MOUSEBUTTONUP:
if event.button == 1:
player_attack = False
pg.display.update() # 2) Update the game
window.blit(background, (0, 0)) # 3) Repaint the screen
CLOCK.tick(60) # 4) Wait 60 Frames
When is attacked
Now I always seen it done this way more or less:
distance_x = abs(player_rect.x - enemy_rect.x)
if distance_x > player_attack_range:
attacked = True
enemy.fill(pg.Color("red"))
With this example, I'm not pointing out the code implementation but the fact that, the player must know the target position and then check whether or not the target is hit
But what I want to know, let's say I don't know the enemy position, and the player just attacks, is there a way that we can get what's currently on the surface at the attack range?
So do something like
attacked_area_x = abs(player_rect.x + player_attack_range) # only care of x coords
rects_or_surfaces_in_area = background.what_have_we_got_here(Vector(attacked_area, 0))
for r in rects_or_surfaces_in_area:
print("Hit!")
Update
So By checking MDN documentation of Game Development MDN I actually find a game algorithm / Technique that is similar (but concept is the same) of my solution.
Is called the Broad Phase
From the documentation:
road phase should give you a list of entities that could be colliding. This can be implemented with a spacial data structure that will give you a rough idea of where the entity exists and what exist around it. Some examples of spacial data structures are Quad Trees, R-Trees or a Spacial Hashmap.
So yes, it seems one of many good approach to solve this problem.
So, after some research and thanks to Rabbid76 and his answer here How do I detect collision in pygame? which covers in details the most common collisions in Pygame, it seems that what I was looking for natively is just not possible.
Maybe is normal, I'm also new to game development and maybe what I want to do just doesn't make any sense, but I bet it does.
The scenario I'm facing is, just one player with a sword hitting, so I asked my self, why should I need to know prior what objects lie on the sword path and check if is hit, instead, do the hit and request to the parent screen "what object are in the sword path"? , which, is for sure faster because we don't need to know what object that have collision are (and avoid a for loop and check for each react/surface).
So, let's say there are many many object that have collision and a player may hit it, it would be way faster do don't know what' there but request it instead to the surface, so basically the surface should be aware of its children / objects.
I tried a bit the Surface.subsurface() and the Surface.get_parent() but couldn't make it work, anyway still, in a surface area we may have many thinks like:
draws, Rect, Surfaces, Sprites, Imgs etc...
I have only 2 solutions in my mind:
Map the objects coordinates
This only really works if, the entities are static, if so, then we could create a dict with key x:y coordinates and then check if the sword is in within a certain x:y and exist in the dict, then the entity is hit.
But with entity then moves by themself, is a nigtmare and will have a worst problem than before, you would need to update the keys at each frame and so on..
Make it 'distance' sensible
So, this could apply to each entity that is moving and may or may not hit something that has a collision. But staying on the example context, let's say we are iterating thourgh entities / object that at each frame are updating their position.
We could check how distant are from the player, let's say 2 chunks away (or the sword lenght) and collect them in a list
Then we that list, we check if, once the sword is updated, is hitting anything close by.
Still, pretty sure there are better ways (without changing or extending pygame its self).
And maybe, by extending pygame, there may be a way to implement a 'surface aware' class that when we request a specific Rect area, it tells us what's there.
I am making a poker game where I want to be able to customize the amount of players there are. I am trying to make a function that would move the dealer position to the next player at the table. I have a list of player objects and each have the boolean is_dealer. In the function I want to be able to make the boolean true for the next player on the list and make it false for the current player I am iterating through. My problem is that I don't know how to get the last player in the list to pass the position to the first player in the list.
def move_positions(self):
for people in range(number_of_players):
if self.players[people].is_dealer==True:
self.players[people].is_dealer= False
self.players[people+1].is_dealer=True
players is my list of player objects.
I would recommend that, instead of finding the current dealer each time you want to advance the dealer, you simply have a variable that keeps track of who the current dealer is. That being said, this snippet doesn't do that: It finds the current dealer, and then sets the next player to be the dealer, wrapping around if necessary:
def move_positions(self):
dealer_index = next(index for index, player in enumerate(self.players) if player.is_dealer)
self.players[dealer_index].is_dealer = False
self.player[(dealer_index+1)%number_of_players].is_dealer = True
Exit the loop as soon as you find the dealer. Handle the case where the last player is the dealer. Also, remove comparison operator with boolean.
for people in range(number_of_players):
if self.players[people].is_dealer:
self.players[people].is_dealer = False
self.players[(people+1)%number_of_players].is_dealer = True
break
I am trying to make an explosion appear and then disappear. My problem is it will either appear, and stay there, or not appear at all.
This is what I have so far:
#Throwing a grenade
if event.key == pygame.K_e and grenadeNum > 0:
Grenade = Explosive([Player.rect.centerx, Player.rect.centery])
for i in range(4, 30):
Grenade.move()
screen.fill([105, 105, 105])
screen.blit(Grenade.image, Grenade.rect)
screen.blit(Gun.image, Gun.rect)
screen.blit(Cliper.image, Cliper.rect)
screen.blit(Bullet.image, Bullet.rect)
screen.blit(Player.image, Player.rect)
screen.blit(BOOM.image, BOOM.rect)
screen.blit(ammo_text, textpos1)
screen.blit(clip_text, textpos2)
screen.blit(nade_text, textpos3)
pygame.display.update()
grenadeNum = grenadeNum - 1
explosion_sound.play()
hide = False
clock.tick(4)
BOOM = Explosion([Grenade.rect.centerx, Grenade.rect.centery])
screen.blit(BOOM.image, BOOM.rect)
hide = True
if hide == False:
BOOM = Explosion([Grenade.rect.centerx, Grenade.rect.centery])
else:
BOOM = Explosion([-100, -100])
You are blitting and waiting inside the event loop.
Any actions will be suspended while waiting.
The solution to this is to seperate the game logic from input.
Since you are throwing a grenade, you should only throw the grenade, and then later increment a counter for the grenade explosion. After enought time passes, you can then remove the grenade sprite from the game, and replace it with an explosion. I can see you already have a clock object, so just call tick, and accumulate that until you think it's enough. You could have a time field in the grenade class that will decide when the grenade will explode.
It's useful to keep all sprites in a list, so you can then call draw() and update() methods for all of them.
A little suggestion: A simple pygame module should look like this:
createObjects() #initialization, loading resources etc.
while(True):
delta = c.tick() #delta for the amount of miliseconds that passed since last loop
drawAll() #draws all active sprites
updateAll(delta) #moves, checks for collisions, etc
getInput() #changes the states of objects, calls functions like shoot,open etc.
So throwing a grenade would create a new sprite that will be drawn and updated like any other sprite.
I'm making a 2-d game using Pygame.
I want to add particle effects into the game I'm working on. I want to do things like spawn smoke, fire, blood, etc. I'm curious is there an easy way to do this? I don't really know even where to start.
I just need a base case I could expand upon..
Pls Help.
You might want to just make a class made of rects that go up and randomly go to the right or left each time it is updated for the smoke. Then make a ton of them whenever you want it. I'll try to make an example code below, but I can't guarntee it will work. You can make similar classes for the other particle effects.
class classsmoke(pygame.Rect):
'classsmoke(location)'
def __init__(self, location):
self.width=1
self.height=1
self.center=location
def update(self):
self.centery-=3#You might want to increase or decrease this
self.centerx+=random.randint(-2, 2)#You might want to raise or lower this as well
#use this to create smoke
smoke=[]
for i in range(20):
smoke.append(classsmoke(insert location here))
#put this somewhere within your game loop
for i in smoke:
i.update()
if i.centery<0:
smoke.remove(i)
else:
pygame.draw.rect(screen, GREY, i)
Another option would be to make the class just a tuple, like this:
class classsmoke():
'classsmoke(location)'
def __init__(self, location):
self.center=location
def update(self):
self.center[1]-=3
self.center[0]+=random.randint(-2, 2)
#to create smoke
smoke=[]
for i in range(20):
smoke.append(classsmoke(insert location here))
#put inside game loop
for i in smoke:
i.update()
if i.centery<0:
smoke.remove(i)
else:
pygame.draw.rect(screen, GREY, (i.center[0], i.center[1], 1, 1))
Or, to avoid classes completly:
#to create smoke:
smoke=[]
for i in range(20):
smoke.append(insert location here)
#put within your game loop
for i in smoke:
i[1]-=3
i[0]+=random.randint(-2, 2)
if i[1]<0:
smoke.remove(i)
else:
pygame.draw.rect(screen, GREY, (i[0], i[1], 1, 1))
Pick your preference, and do something similar for other particle effects.
Check the Library for particle effects PyIgnition
I have tried a for loop with the blit and draw methods and using different variables for " PlayerSprite " and " Treegroup "
for PlayerSprite in Treegroup:
surface.blit(PlayerSprite,(random.randrange(100,500),random.randrange(100,600)))
also tried
SPRITES=[]
for Sprites in range(10):
Sprites= PlayerSprite
SPRITES.append(Sprites)
all I get are errors
screen=pygame.display.set_mode((640,480))
background1=pygame.image.load("C:\Pygame-Docs\examples\data\Random Map.bmp")
class Tree1(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image=pygame.image.load('C:\Pygame-Docs\examples\data\Tree 1.bmp')
self.image=self.image.convert()
self.rect=self.image.get_rect()
self.rect.centerx=random.randrange(10,100)
self.rect.centery=random.randrange(10,100)
# Makes a group of trees
Howmanytrees=random.randrange(5,10)
Trees=[]
for tree in range(Howmanytrees):
trees=Tree1()
Trees.append(trees)
# Howmany groups
for Treegroup in range(10):
Treegroup=Trees
# Places groups
PlayerSprite=pygame.sprite.Group(Treegroup)
# keeps loop ( game ) going until canceled
keepgoing=True
while keepgoing:
for event in pygame.event.get():
if event.type==pygame.QUIT:
keepgoing=False
# actually draws screen
screen.blit(background1,(0,0))
PlayerSprite.draw(screen)
pygame.display.flip()
This code only displays 5 to 10 trees " Trees=[] "
and nothing else. I have worked on this problem for over a week , read many tutorials, looked on many websites, nothing seems to work. I must be overlooking or missing somethig. I thought this would be easy!
Thanks so much!
As far as I understand what you want to achieve, the below code should help you. I kept it very very simple regarding python syntax, as you seems to be a newbie (for experienced programmers: yes, what I wrote below is horrible python code, but I believe the OP can understand it and it may help).
The key is that if you want to get several groups of trees, you must have a loop within a loop. The inner loop put the trees inside the group, and the outer loop put several groups. Of course you can (and should) certainly hide the inner loop behind some function or class.
# Howmany groups ? say 3
trees_groups = []
number_of_groups = 3
for _ in range(number_of_groups):
# Choose a base position for my group of trees
base_x = random.randrange(0,530)
base_y = random.randrange(0,370)
# Makes a group of trees
trees=[]
number_of_trees = random.randrange(5,10)
for _ in range(number_of_trees):
one_tree = Tree1()
trees.append(one_tree)
for tree in trees:
tree.rect.centerx += base_x
tree.rect.centery += base_y
trees_groups.append(tree)
# Places groups
PlayerSprite=pygame.sprite.Group(trees_groups)
Some after notes:
And as other posters said, you should not use capitalized variables as you do. The python usage is to keep them for classes
Also looping using range when the base variant is not used is bad practice. I emphasized this by using underline as a variable name for the loop variant when it is not used.
I would use randint and move_ip to get what you want. Here is a code snippet from my own game that works just as well:
self.rect.move_ip(random.randint(minX, maxX), random.randint(minY, maxY))
the four variables minX, maxX, minY, maxY form a rectangle where the sprite can be placed. In your case, the trees would be placed along the entire screen, but with a reduced max X and Y range so trees won't clip through the bottom of the screen.
Also, use a Group class to store your trees rather than a list. A list stops the spawning of multiple trees, while a Group does. Here is how to call it:
Treegroup = pygame.sprite.Group
and to add a sprite to the group:
Treegroup.add(Tree1(screen))
Make sure the class itself has screen in its init, like so:
def __init__(self, screen)
Once that's done, your code should look something like this:
for Treegroup in range(10):
Treegroup.add(Tree(screen))
[...]
class Tree(pygame.sprite.Sprite):
def __init__(self, screen):
pygame.sprite.Sprite.__init__(self)
self.image, self.rect = load_image('tree.png', -1)
self.rect.move_ip(random.randint(0, 800), random.randint(0, 600))
self.area = screen.get_rect()
It doesn't really make much sense to me.
for tree in range(Howmanytrees):
trees=Tree1()
Trees.append(trees)
Your loop here is doing nothing at all. tree should be a number between 0 and Howmanytrees. Again, the following block isn't indented so it's not part of the loop. Even so, the block still wouldn't work. Also, you're confusing yourself and us with variable names. Trees is the object? trees is the list? Don't do this. Seriously.
No idea what the following code is up to.
# Howmany groups
for Treegroup in range(10):
Treegroup=Trees
Create your SpriteGroup passing in the aforementioned trees list? Or am I missing something :) TreeGroup = Trees 10 times is just going to do that 10 times. You are not using the loop variant. The only thing that modifies during the loop. Even so, this entire block of code is useless.
while keepgoing:
for event in pygame.event.get():
if event.type==pygame.QUIT:
keepgoing=False
This is going to cause a nice infinite loop. It is evaluating the keepgoing variable constantly. This will never get set to false unless the user quits, but also it will never display anything on the screen. Lose this. Use this instead:
for event in pygame.event.get():
if event.type == QUIT:
return
This will not cause an infinite loop as there are only so many events to be processed per tick. Then the program will loop around and do the process or updating, rendering and getting input again.