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
# [...]
Related
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)
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()
Why I get different results when I use method update of group sprite objects during drawing to the screen. When thru calling method I get only one row of sprites and when I run for loop and move y axis of every sprite by one I get the whole swarm (several rows) moving down.
Here is the code
drop.py
import pygame
from pygame.sprite import Sprite
class Drop(Sprite):
"""Class that represent single drop in rain"""
def __init__(self, settings, screen):
"""Initialize drop and sets its starting position"""
super().__init__()
self.screen = screen
self.settings = settings
#Load drop image and sets its rect attribute
self.image = pygame.image.load('images/drop.bmp')
self.rect = self.image.get_rect()
#Start each drop on top-left on the screen
self.rect.x = self.rect.width
self.rect.y = self.rect.height
#store drop exact position
self.y = float(self.rect.y)
def update(self):
self.y += 1
self.rect.y = self.y
I have all functions stored in separate file. Just to keep it less cluttered. Where in function update_screen() is in comments described in code the problem I do not understand why makes difference looping thru sprites and that way changing y axis and getting whole group of them moving, but when I use method of sprite object .update() they are in group I get only partial group moving on screen.
func_rain.py
import pygame
import sys
from drop import Drop
from time import sleep
def create_rain(settings, screen, drops):
"""Create a fleet of drops."""
#Create an drop and find the number of drops in a row and number rows
drop = Drop(settings, screen)
number_drops_x = get_number_drops_x(settings, drop.rect.width)
number_rows = get_number_rows(settings, drop.rect.height)
for row_number in range(number_rows):
for drop_number in range (number_drops_x):
create_drop(settings, screen, drops, drop_number, row_number)
def get_number_drops_x(settings, drop_width):
"""Determine the number of drops that fit in a row."""
available_space_x = settings.screen_width - 2 * drop_width
number_drops_x = int(available_space_x / (2 * drop_width))
return number_drops_x
def get_number_rows(settings, drop_height):
"""Determine the number of rows of drops that fit on the screen."""
available_space_y = (settings.screen_height - drop_height)
number_rows = int(available_space_y / (2 * drop_height))
return number_rows
def create_drop(settings, screen, drops, drop_number, row_number):
"""Create drop and place it in the row"""
drop = Drop(settings, screen)
drop_width = drop.rect.width
drop.x = drop_width + 2 * drop_width * drop_number
drop.rect.x = drop.x
drop.rect.y = drop.rect.height + 2 * drop.rect.height * row_number
drops.add(drop)
def check_keydown_events(event):
"""Respond to key presses"""
if event.key == pygame.K_q:
sys.exit()
def check_events():
"""Respond to keypress and mouse events"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown_events(event)
def update_screen(settings, screen, drops):
"""Update images on the screen and flip to the new screen."""
# Redraw the screen during each pass through the loop.
screen.fill(settings.bg_color)
#This loop
for drop in drops:
drop.rect.y += 1
"""Here is the problem!!!!!! if I use for loop just above I get whole group
consisting from 5 rows of drops (image has resolution 50*62 pix). But when
I use drops.update() commented below (no for loop) I get on screen only
one row of drops."""
#drops.update()
drops.draw(screen)
pygame.display.flip()
rain_settings.py
class Settings():
"""A class to store all ran settings"""
def __init__(self):
#Screen settings
self.screen_width = 1200
self.screen_height = 800
self.bg_color = (100, 100, 100)
And last main file.
rain.py
import pygame
from rain_settings import Settings
import func_rain as fr
from pygame.sprite import Group
def run_rain():
"""Initialize pygame and screen object"""
pygame.init()
settings = Settings()
screen = pygame.display.set_mode(
(settings.screen_width, settings.screen_height))
drops = Group()
fr.create_rain(settings, screen, drops)
while True:
fr.check_events()
fr.update_screen(settings, screen, drops)
run_rain()
I would really appreciate If someone can explain me what I'm doing wrong. I can't figure it out. please
So I've been playing around and I get the movement of sprites thru class method update to work.
def update(self):
self.rect.y += 1
Altho it still remains to me a mystery why directly changing the self.rect.y works and indirect change on y attribute don't.
I need to create a fighting game that gives prompts and accepts input through text, such as a raw input and then performs the animation, while still have the characters animated, e.g. moving back and forth in a ready to fight stance. How would I go about this?
Please note that this is not going to be your typical answer. StackOverflow is to help after all that you can do on your part when you are stuck, it's not meant as a place to come for code, but since I'm assuming other people new to programming will also be confused on things such as these. So I'm going to write some code, and some psuedo code, just so that you get the just of what you would do in such a scenario.
# TODO put your imports up here
pygame.init()
clock = pygame.time.Clock()
gameSurface = pygame.display.set_mode((600, 400)) # 2/3 aspect ratio
FPS = 40 # Set to your own Frames per second
class Animator:
def __init__(self, surface, rows, cols, time_between_frames, on_finish):
self.images = []
self.current_image = 0
self.time_between_frames = time_between_frames # time animator waits before changing surface
self.current_time # tracks time for frames to change
self.on_finish = on_finish # function to call when animation finishes
surf_width = (surface.get_width() / cols) # calculate width
surf_height = (surface.get_height() / rows) # calculate height
for x in range(cols):
for y in range(rows):
surf = pygame.Surface(surface.get_size()) # temp surface
from_rect = pygame.Rect(x * surf_width, y * surf_height, surf_width, surf_height) # rect to blit from
surf.blit(surface, (0,0), from_rect) # draw to temp surface
self.images.append(surf) # add temp surface to the images list
def update(delta):
self.current_time += delta # update current time
if (self.current_time >= self.time_between_frames): # if time to switch surfaces
self.current_time -= self.time_between_frames # take away time from current time
self.current_image += 1 # change image
if self.current_image >= len(self.images): # if current image would throw an out of bounds exception
self.current_image = 0 # reset the current image to the first
def get_frame(self):
return self.images[self.current_image]
class Player:
resting = 0
abdomenKick = 1
def __init__(self, x, y):
self.x = x
self.y = y
self.action = Player.resting
self.restingAnimation = Animation(pygame.image.load("resting.png"), 2, 3, 500)
self.abdomenKickAnimation = Animation(pygame.image.load("abdomenKick.png"), 4, 6, 50)
self.currentAnimation = self.restingAnimation
def update(self, delta):
self.currentAnimation.update(delta)
def draw(self, surface):
surface.blit(self.currentAnimation.get_frame(), (self.x, self.y))
def abdomenKick(self):
self.currentAnimation = self.restingAnimation
class Game:
def __init__(self):
self.player = Player()
def update(self, delta):
self.player.update(delta)
def draw_screen(self, surface):
self.player.draw(surface)
def gameLoop():
game = Game()
while True:
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == A:
game.player.abdomenKick() #Or whatever move you have
game.update(clock.get_rawtime())
game.draw_screen()
clock.tick(FPS)
So here is just a brief showcase you can call it of what this might look like.
How do you make this code work? Just have pyglet installed and change "fireball.png" with the name of an image stored in the directory where you saved this code to a file.
import pyglet
class Fireball(pyglet.sprite.Sprite):
def __init__(self, batch):
pyglet.sprite.Sprite.__init__(self, pyglet.resource.image("fireball.png"))
# replace "fireball.png" with your own image stored in dir of fireball.py
self.x = 10 # Initial x coordinate of the fireball
self.y = 10 # Initial y coordinate of the fireball
class Game(pyglet.window.Window):
def __init__(self):
pyglet.window.Window.__init__(self, width = 315, height = 220)
self.batch_draw = pyglet.graphics.Batch()
self.fps_display = pyglet.clock.ClockDisplay()
self.fireball = []
def on_draw(self):
self.clear()
self.fps_display.draw()
self.batch_draw.draw()
if len(self.fireball) != 0: # Allow drawing of multiple
for i in range(len(self.fireball)): # fireballs on screen
self.fireball[i].draw() # at the same time
def on_key_press(self, symbol, modifiers):
if symbol == pyglet.window.key.A:
self.fireball.append(Fireball(batch = self.batch_draw))
pyglet.clock.schedule_interval(func = self.update, interval = 1/60.)
print "The 'A' key was pressed"
def update(self, interval):
for i in range(len(self.fireball)):
self.fireball[i].x += 1 # why do fireballs get faster and faster?
if __name__ == "__main__":
window = Game()
pyglet.app.run()
This code creates a black background screen, where the fps are displayed and a fireball is shot along the x direction from the position (10, 10) whenever you press the A key.
You will notice that the more fireballs you shoot, the faster all fireballs will start to go.
Questions:
Why do the fireballs go faster and faster each time I press A ?
How should I stop the fireballs from accelerating each time I press A ?
The fireball goes faster and faster because every time you press the A you add another call of self.update to the scheduler. So self.update is called more and more times each time resulting in more updates of the position. To fix that move the line below to the __init__().
pyglet.clock.schedule_interval(func = self.update, interval = 1/60.)