Give time to a bomb before to explode on pygame - python

I am doing a kind of bomberman, and I am trying to do that the bomb explodes after a while. Sorry if this question already exists. I have been looking for any answer but I didnt find.
This should be like:
1. I put a bomb somewhere
2. The bomb waits 5 seconds
3. The bomb explodes
I dont know how to give the 5 seconds before to explode.
class bomb(object):
def __init__(self, aposX, aposY, bombRange = 5):
self.posX = aposX
self.posY = aposY
self.bombRange = bombRange
self.timeToExplode = 5000
pygame.draw.circle(ventana,(200,0,0),(self.posX,self.posY),20)
def update(self):
pygame.draw.circle(ventana,(200,0,0),(self.posX,self.posY),20)
#Here should wait 5 seconds and then call the explde method
self.explode()
def explode(self):
pygame.draw.line(ventana,(200,0,0),(self.posX,self.posY),(self.posX+20+(40*self.bombRange),self.posY),40)
pygame.draw.line(ventana,(200,0,0),(self.posX,self.posY),(self.posX-20-(40*self.bombRange),self.posY),40)
pygame.draw.line(ventana,(200,0,0),(self.posX,self.posY),(self.posX,self.posY+20+(40*self.bombRange)),40)
pygame.draw.line(ventana,(200,0,0),(self.posX,self.posY),(self.posX,self.posY-20-(40*self.bombRange)),40)
I hope you can help me.I am going to appreciate that.

Here's a little example with the dt variant. I pass the dt to the update method where I use it to decrement the timer attribute. In the main loop I just draw the lines of the explosion if the timer is below 0. To remove the instances I put the exploded bombs into a set which I subtract from the bomb_set that contains all bomb instances.
import pygame
class Bomb(object):
def __init__(self, aposX, aposY, bombRange=5):
self.posX = aposX
self.posY = aposY
self.bombRange = bombRange
self.timeToExplode = 3000
def update(self, dt):
# Subtract the passed time `dt` from the timer each frame.
self.timeToExplode -= dt
def explode(self, screen):
pygame.draw.line(screen,(200,0,0),(self.posX,self.posY),(self.posX+20+(40*self.bombRange),self.posY),40)
pygame.draw.line(screen,(200,0,0),(self.posX,self.posY),(self.posX-20-(40*self.bombRange),self.posY),40)
pygame.draw.line(screen,(200,0,0),(self.posX,self.posY),(self.posX,self.posY+20+(40*self.bombRange)),40)
pygame.draw.line(screen,(200,0,0),(self.posX,self.posY),(self.posX,self.posY-20-(40*self.bombRange)),40)
def draw(self, screen):
pygame.draw.circle(screen,(200,0,0),(self.posX,self.posY),20)
def main():
screen = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()
bomb_set = set() # This set holds the bomb instances.
done = False
while not done:
# Get the passed time since last clock.tick call.
dt = clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
bomb_set.add(Bomb(*event.pos))
# Game logic.
to_remove = set()
# Update bombs. Pass the `dt` to the bomb instances.
for bomb in bomb_set:
bomb.update(dt)
# Add old bombs to the to_remove set.
if bomb.timeToExplode <= -3000:
to_remove.add(bomb)
# Remove bombs fromt the bomb_set.
if to_remove:
bomb_set -= to_remove
# Draw everything.
screen.fill((30, 30, 30))
for bomb in bomb_set:
bomb.draw(screen)
# I'm just drawing the explosion lines each
# frame when the time is below 0.
if bomb.timeToExplode <= 0:
bomb.explode(screen)
pygame.display.flip()
if __name__ == '__main__':
pygame.init()
main()
pygame.quit()

Related

How to not initialize the def init every time

this code is by tech with tim but i am trying to tweak it, i am trying to make the rotation velocity vary everytime.
import pygame
import time
import math
from utility import scale_image, blit_rotate_center
rotation_speed=2
Joystickx=0
GRASS = scale_image(pygame.image.load("Sprites/grass.png"),2.5)
TRACK = scale_image(pygame.image.load("Sprites/track.png"),0.7)
TRACK_BORDER = scale_image(pygame.image.load("Sprites/track-border.png"),0.7)
Car = scale_image(pygame.image.load("Sprites/F1.xcf"),0.1)
FINISH=pygame.image.load("Sprites/finish.png")
WIDTH, HEIGHT= TRACK.get_width(), TRACK.get_height()
WIN = pygame.display.set_mode((WIDTH,HEIGHT))
pygame.display.set_caption("F1")
FPS = 60
class AbstractCar:
def __init__(self,max_vel,rotation_vel):
self.img=self.IMG
self.max_vel=max_vel
self.vel = 0
self.rotation_vel = rotation_vel
self.angle = 90
self.x, self.y = self.START_POS
def rotate(self, left=False, right=False):
if left:
self.angle += self.rotation_vel
print(self.angle)
elif right:
self.angle -= self.rotation_vel
print(self.angle)
def draw(self):
blit_rotate_center(WIN, self.img,(self.x, self.y), self.angle)
class PlayerCar(AbstractCar):
IMG = Car
START_POS = (180,200)
def draw(win,images, player_car):
for img, pos in images:
win.blit(img,pos)
player_car.draw()
pygame.display.update()
run=True
clock = pygame.time.Clock()
images = [(GRASS, (0,0)),(TRACK,(0,0))]
def rotate():
global rotation_speed
global Joystickx
rotation_speed+=2*Joystickx
rotation_speed*=0.9
while run:
clock.tick(FPS)
rotate()
player_car = PlayerCar(4, rotation_speed)
draw(WIN, images, player_car)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
break
keys = pygame.key.get_pressed()
if keys[pygame.K_a]:
player_car.rotate(left=True)
Joystickx=-1
elif keys[pygame.K_d]:
player_car.rotate(right=True)
Joystickx=1
else:
Joystickx=0
pygame.quit()
however, every time the class is ran, the code will set self.angle to 90, i do not want that to happen, what can i do to not let the def init run as that will set the self.angle to 94 instead remembering the last self.angle
This is OOP (Object Oriented Programming) thing. I really suggest you to look it up, it's not an easy topic at first, but it's also not that hard to understand everything in it, it's just the first time is hard.
So, the __init__ is the constructor of your AbstractCar class. This will always run when you make a new object from this class.
The simple way to fix this is to place the line where you define your car a bit above, outside the while loop and to keep the rotation_vel updated, we make a new method in the AbstractCar class and call it instead:
class AbstractCar:
def set_rotation_vel(self, new_rotation_vel):
self.rotation_vel = new_rotation_vel
[...]
player_car = PlayerCar(4, rotation_speed)
while run:
clock.tick(FPS)
rotate()
player_car.set_rotation_vel(rotation_speed)
draw(WIN, images, player_car)

How to call a particular object's method and have affect only on that object which is a value of a key of a dictionary in pygame?

I am a new learner of programming. I am practiceing python and writing a litle game using pygame. I have ten circle on the pygame window and each have a counter. I want to increase the counter by 1 when it's circle is clicked. I first created group using .sprite.Group(), but I could not get the desired result. Because then the counter does not even get update. So I create two list, one for the circle and one for the counter. And for each circle in the circle list I created a dictionary taking the circle as the key and each counter in the circle list is the value of the circle. Now when the circle got clicked then the all counter gets updated, not the counter that the circle holds. But goal is to get the specific counter updated for it's circle.
(hole == circle)
dig_hole.py(This is the main file.)
import pygame
import sys
from pygame.sprite import Group
from counter import Counter
from hole import Hole
from settings import Settings
class DigHole:
def __init__(self):
pygame.init()
self.settings = Settings()
self.screen = pygame.display.set_mode((self.settings.screen_width, self.settings.screen_height))
pygame.display.set_caption("Dig Hole")
pygame.display.set_icon(Hole(self).image)
self.count = Counter(self)
self.counter_group = list()
self.holes = list()
self.dict = dict()
self._create_holes()
self.hole = Hole(self)
self.mouse_pos = (0, 0)
def run_dig_hole(self):
while True:
self._check_events()
self._update_screen()
def _check_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_q:
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
self.mouse_pos = pygame.mouse.get_pos()
self._check_hole_clicked_events(self.mouse_pos)
def _check_hole_clicked_events(self, mouse_pos):
for key in self.dict:
if key.rect.collidepoint(mouse_pos):
self.dict[key].count_clock += 1
self.dict[key].prep_counter()
self.count.prep_counter()
def _create_holes(self):
for row_number in range(2):
for hole_number in range(5):
self._create_hole(row_number, hole_number)
for hole in self.holes:
counter = Counter(self)
counter.counter_rect.midbottom = hole.rect.midtop
self.counter_group.append(counter)
for hole in self.holes:
for counter in self.counter_group:
self.dict[hole] = counter
def _create_hole(self, row_number, hole_number):
hole = Hole(self)
hole_width, hole_height = hole.rect.size
available_space_x = self.settings.screen_width - (2 * hole_width)
available_space_y = self.settings.screen_height - (2 * hole_height)
hole.x =(((available_space_x // 5) - hole_width) // 2) + (available_space_x // 5) * hole_number
hole.rect.x = hole.x
hole.rect.y = 2 * hole.rect.height + (available_space_y - (4 * hole_height)) * row_number
self.holes.append(hole)
def _update_screen(self):
self.screen.fill(self.settings.bg_color)
for key in self.dict:
key.draw()
for key in self.dict:
self.dict[key].counter_rect.midbottom = key.rect.midtop
self.dict[key].show_counter()
self.count.show_counter()
pygame.display.flip()
if __name__ == '__main__':
dh = DigHole()
dh.run_dig_hole()
hole.py
import pygame
from pygame.sprite import Sprite
class Hole():
def __init__(self, dh):
# super().__init__()
self.screen = dh.screen
self.image = pygame.image.load("images/circle.bmp")
self.rect = self.image.get_rect()
self.rect.x = self.rect.width
self.rect.y = self.rect.height
self.x = float(self.rect.x)
def draw(self):
self.screen.blit(self.image, self.rect)
counter.py
import pygame.font
from pygame.sprite import Sprite
class Counter():
def __init__(self, dh):
# super().__init__()
self.screen = dh.screen
self.screen_rect = self.screen.get_rect()
self.settings = dh.settings
self.count_clock = 0
self.text_color = (30, 30, 30)
self.font = pygame.font.SysFont(None, 48)
self.prep_counter()
def prep_counter(self):
counter_str = str(self.count_clock)
self.counter_image = self.font.render(counter_str, True, self.text_color, self.settings.bg_color)
self.counter_rect = self.counter_image.get_rect()
self.counter_rect.right = self.screen_rect.right - 20
self.counter_rect.top = 20
def show_counter(self):
self.screen.blit(self.counter_image, self.counter_rect)
Thank you.This is the window of the progeam. Here all circles are gets update but one is clicked.
The issue is that, in _create_holes, you set the counters of each circle to be the same Counter object.
for hole in self.holes:
for counter in self.counter_group:
self.dict[hole] = counter
Unrolling the inner loop, this is the same as
for hole in self.holes:
self.dict[hole] = self.counter_group[0]
self.dict[hole] = self.counter_group[1]
self.dict[hole] = self.counter_group[2]
...
self.dict[hole] = self.counter_group[-1]
The first assignments are all immediately overwritten, so this code is setting every self.dict value to self.counter_group[-1]. What you want to do instead is
for hole, counter in zip(self.holes, self.counter_group):
self.dict[hole] = counter
which iterates over both self.holes and self.counter_group simultaneously. In your case, you can actually rewrite this as
self.dict = dict(zip(self.holes, self.counter_group))
which is nicer.
I’m not sure, but I think you intend self.count to be a total. If this is the case, it won’t quite work: you’re missing a line from _check_hole_clicked_events. It should look like this:
def _check_hole_clicked_events(self, mouse_pos):
for key in self.dict:
if key.rect.collidepoint(mouse_pos):
self.dict[key].count_clock += 1
self.dict[key].prep_counter()
self.count.count_clock += 1
self.count.prep_counter()
As a side note, I noticed you also wrote list() and dict() to create empty lists and dicts. It’s more efficient and idiomatic just to write literals ([] and {}).

python pygame load image to Initialize screen very slowly

I write a script to implementate the continuous rains,the function is totally correct.
Only one issue is when i run the script is it really slow to initialize screen, it almost takes 6-8 seconds then i can see the image on the screen.
i search some reasons for why cause this issue such as useconvert_alpha() to load the image, but nothing changed, it is still very slowly
So what caused this issue???
here's the code:
#!/usr/bin/python
import sys
import pygame as p
from random import randint
#13-4
class Setting():
def __init__(self,width,height):
self.w=width
self.h=height
self.screen=p.display.set_mode((self.w,self.h),p.RESIZABLE,0)
self.speed = 1 #rain speed
p.display.set_caption("EXE 13-4")
class Rain(p.sprite.Sprite):
def __init__(self):
super().__init__()
pic=p.image.load("../image/rain.jpg").convert_alpha()
self.image=p.transform.smoothscale(pic,(100,100))
self.rect=self.image.get_rect()
self.rect.x=(self.rect.width)/2
self.y=float(self.rect.y) # store the rain rect in temp y
def create(self,setting,rains):
spacex=setting.w-2*self.rect.x #calculate the space to put the rain
numbers=int(spacex/(2*self.rect.x)) #calculate the numbera of every column
rows=int(setting.h/(self.rect.height)) #calculate the rows
for row in range(rows):
for number in range(numbers): #store the whole rain into Group rains
rain=Rain()
rain.number=number
rain.row=row
rain.rect.x =rain.rect.x+rain.rect.width*number
rain.y = rain.rect.y+2*rain.rect.height*row
rain.rect.y =rain.y
rains.add(rain)
def check_edge(setting,rains):
for rain in rains.sprites(): #if any rain reach the bottom of screen restart them to the top
if rain.rect.y == setting.h:
rain.y=0
rain.rect.y=rain.y
def update(self,setting):
self.y += setting.speed
self.rect.y= self.y
def blit(setting,rains):
rains.update(setting)
rains.draw(setting.screen)
def game():
p.init()
setting=Setting(1200,800)
rain=Rain()
rains=p.sprite.Group()
rain.create(setting,rains)
while True:
for event in p.event.get():
if event.type == p.QUIT:
sys.exit()
elif event.type == p.KEYDOWN:
if event.key == p.K_ESCAPE:
sys.exit()
setting.screen.fill((0,0,255))
Rain.check_edge(setting,rains)
Rain.blit(setting,rains)
p.display.flip()
game()
What you actually do is to load the same image again and again for each instance of Rain.
Load the image once and use the same image for all the raindrops. e.g. use a class attributes for the raindrop image (Rain.raindrop_pic). Furthermore, avoid to do convert_alpha() and transform.smoothscale multiple times:
class Rain(p.sprite.Sprite):
raindrop_pic = p.transform.smoothscale(
p.image.load("../image/rain.jpg").convert_alpha(), (100,100))
def __init__(self):
super().__init__()
self.image = Rain.raindrop_pic
# [...]

How to make a wave timer in pygame

So, I'm totally new to programming (been doing it for a couple of months) and decided to try coding a game.
On that note, a big thanks to Chris Bradfield for his series of tutorials in pygame coding, they are absolutely great!
However, now that I'm done with the tutorials and need to work on my own, I've come across a problem. I'm making a top-down shooter and making it wave-based. So, when zombies in one wave die, I want to show a timer that counts down until the next wave begins. I THINK I'm down the right path atm, let me show you what I'm working with.
def new(self)
'''
self.timer_flag = False
self.x = threading.Thread(target=self.countdown, args=(TIME_BETWEEN_WAVES,))
'''
def countdown(self, time_between_waves):
self.wave_timer = time_between_waves
for i in range(TIME_BETWEEN_WAVES):
while self.timer_flag:
self.wave_timer -=
time.sleep(1)
def update(self)
'''
self.countdown_has_run = False
if len(self.mobs) == 0:
self.timer_flag = True
if not self.countdown_has_run:
self.countdown_has_run = True
self.x.start()
'''
Now, I also draw my timer when the timer_flag is True, but it doesn't decrement, so I assume the problem lies somewhere in calling/starting the threaded countdown function?
Also, it's my first time posting here, so please let me know what to do to format better etc for you to be able to help
Don't bother with threads. No need to make your live complicated.
Usually, you use a Clock anyway in your game (if not, you should start using it) to limit the framerate, and to ensure that your world moves at a constant rante (if not, you should start doing it).
So if you want to trigger something in, say, 5 seconds, just create a variable that holds the value 5000, and substract the time it took to process your last frame (which is returned by Clock.tick):
clock = pygame.time.Clock()
dt = 0
timer = 5000
while True:
...
timer -= dt
if timer <= 0:
do_something()
dt = clock.tick(60)
I hacked together a simple example below. There, I use a simple class that is also a Sprite to draw the remaining time to the screen. When the timer runs out, it calls a function that creates a new wave of zombies.
In the main loop, I check if there's no timer running and no zombies, and if that's the case, a new timer is created.
Here's the code:
import pygame
import pygame.freetype
import random
# a dict that defines the controls
# w moves up, s moves down etc
CONTROLS = {
pygame.K_w: ( 0, -1),
pygame.K_s: ( 0, 1),
pygame.K_a: (-1, 0),
pygame.K_d: ( 1, 0)
}
# a function that handles the behaviour a sprite that
# should be controled with the keys defined in CONTROLS
def keyboard_controlled_b(player, events, dt):
# let's see which keys are pressed, and create a
# movement vector from all pressed keys.
move = pygame.Vector2()
pressed = pygame.key.get_pressed()
for vec in (CONTROLS[k] for k in CONTROLS if pressed[k]):
move += vec
if move.length():
move.normalize_ip()
move *= (player.speed * dt/10)
# apply the movement vector to the position of the player sprite
player.pos += move
player.rect.center = player.pos
# a function that let's a sprite follow another one
# and kill it if they touch each other
def zombie_runs_to_target_b(target):
def zombie_b(zombie, events, dt):
if target.rect.colliderect(zombie.rect):
zombie.kill()
return
move = target.pos - zombie.pos
if move.length():
move.normalize_ip()
move *= (zombie.speed * dt/10)
zombie.pos += move
zombie.rect.center = zombie.pos
return zombie_b
# a simple generic sprite class that displays a simple, colored rect
# and invokes the given behaviour
class Actor(pygame.sprite.Sprite):
def __init__(self, color, pos, size, behavior, speed, *grps):
super().__init__(*grps)
self.image = pygame.Surface(size)
self.image.fill(color)
self.rect = self.image.get_rect(center=pos)
self.pos = pygame.Vector2(pos)
self.behavior = behavior
self.speed = speed
def update(self, events, dt):
self.behavior(self, events, dt)
# a sprite class that displays a timer
# when the timer runs out, a function is invoked
# and this sprite is killed
class WaveCounter(pygame.sprite.Sprite):
font = None
def __init__(self, time_until, action, *grps):
super().__init__(grps)
self.image = pygame.Surface((300, 50))
self.image.fill((3,2,1))
self.image.set_colorkey((3, 2, 1))
self.rect = self.image.get_rect(topleft=(10, 10))
if not WaveCounter.font:
WaveCounter.font = pygame.freetype.SysFont(None, 32)
WaveCounter.font.render_to(self.image, (0, 0), f'new wave in {time_until}', (255, 255, 255))
self.timer = time_until * 1000
self.action = action
def update(self, events, dt):
self.timer -= dt
self.image.fill((3,2,1))
WaveCounter.font.render_to(self.image, (0, 0), f'new wave in {int(self.timer / 1000) + 1}', (255, 255, 255))
if self.timer <= 0:
self.action()
self.kill()
def main():
pygame.init()
screen = pygame.display.set_mode((600, 480))
screen_rect = screen.get_rect()
clock = pygame.time.Clock()
dt = 0
sprites_grp = pygame.sprite.Group()
zombies_grp = pygame.sprite.Group()
wave_tm_grp = pygame.sprite.GroupSingle()
# the player is controlled with the keyboard
player = Actor(pygame.Color('dodgerblue'),
screen_rect.center,
(32, 32),
keyboard_controlled_b,
5,
sprites_grp)
# this function should be invoked once the timer runs out
def create_new_wave_func():
# let's create a bunch of zombies that follow the player
for _ in range(15):
x = random.randint(0, screen_rect.width)
y = random.randint(-100, 0)
Actor((random.randint(180, 255), 0, 0),
(x, y),
(26, 26),
zombie_runs_to_target_b(player),
random.randint(2, 4),
sprites_grp, zombies_grp)
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
# no timer, no zombies => create new timer
if len(wave_tm_grp) == 0 and len(zombies_grp) == 0:
WaveCounter(5, create_new_wave_func, sprites_grp, wave_tm_grp)
sprites_grp.update(events, dt)
screen.fill((80, 80, 80))
sprites_grp.draw(screen)
pygame.display.flip()
dt = clock.tick(60)
if __name__ == '__main__':
main()
It looks to me you are lacking the mechanism to check how many mobs are left on screen. I imagine it could be something like this:
class CountdownClock:
def __init__(self):
self.start_no = 1
self.time_between_waves = 5
self.t = threading.Thread(target=self.check_mobs_left)
self.t.start()
def check_mobs_left(self):
self.mobs = ["mob" for _ in range(randint(2, 7))] #generate 2-7 mobs per level
print (f"Wave {self.start_no} : Total {len(self.mobs)} mobs found!")
while self.mobs:
print (f"Still {len(self.mobs)} mobs left!")
time.sleep(1)
del self.mobs[-1] #simulate mob kill - remove this line from your actual setting
self.next_wave(self.time_between_waves)
self.time_between_waves +=2 #increased time for each wave
def next_wave(self,time_between_waves):
self.time_left = time_between_waves
print(f"Wave {self.start_no} cleared!")
self.start_no += 1
while self.time_left:
print (f"Next wave in...{self.time_left}")
self.time_left -=1
time.sleep(1)
self.t = threading.Thread(target=self.check_mobs_left)
self.t.start()
a = CountdownClock()
You will have this up constantly without the need to call the method every round and set flag and stuff.

Pygame key holding not working

import pygame, sys, math
class Cam:
def __init__(self, pos=(0,0,0), rot=(0,0)):
self.pos = list(pos)
self.rot = list(rot)
def update(self, dt, key):
s = dt*10
if key[pygame.K_r]: self.pos[1]-=s
if key[pygame.K_f]: self.pos[1]+=s
if key[pygame.K_w]: self.pos[2]+=s
if key[pygame.K_s]: self.pos[2]-=s
if key[pygame.K_a]: self.pos[0]-=s
if key[pygame.K_d]: self.pos[0]+=s
pygame.init()
w,h = 400,400; cx,cy=w//2, h//2
screen = pygame.display.set_mode((w,h))
clock = pygame.time.Clock()
verts=(-1,-1,-1),(1,-1,-1),(1,1,-1),(-1,1,-1),(-1,-1,1),(1,-1,1),(1,1,1),(-1,1,1)
edges = (0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),(0,4),(1,5),(2,6),(3,7)
cam = Cam((0,0,-5))
while True:
dt = 0.1
print dt
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
screen.fill((255,255,255))
for edge in edges:
points = []
for x,y,z in (verts[edge[0]],verts[edge[1]]):
x-=cam.pos[0]
y-=cam.pos[1]
z-=cam.pos[2]
f=200/z
x,y = x*f, y*f
points = points + [(cx+int(x), cy+int(y))]
pygame.draw.line(screen, (0,0,0), points[0], points[1], 1)
pygame.display.flip()
key = pygame.key.get_pressed()
cam.update(dt, key)
This is my code. Pressing WASDRF should move the camera around constantly while holding the key, but no. It doesn't. I have to press the key each time I want to move one pixel. Can someone explain why holding the key doesn't work? Judging by all the other questions I've found, this should work.
Just realized my mistake so I thought i would post the answer myself. I put the entire rest of the code after the even for loop IN the event for loop meaning it would only update one every time an event like a key press happened. I needed to unindent everything past sys.exit()

Categories