I am creating a clicker game, very similar to cookie clicker. My question is, how do I increase a variable by an amount every second?
Here, prepare for a new game.
def new(self):
# set cookies/multipliers for a new game
self.cookie_count = 0
self.grandma = 10 # grandma bakes 10 cookies/second
Then, if a grandma is purchased, add 10 cookies/second to self.cookie_count for every grandma purchased. Example: If 2 grandmas are purchased, self.cookie_count += 20 cookies/second. Yet, as I have it now, everytime I purchase a grandma I just get 10 cookies.
if self.rect2.collidepoint(self.mouse_pos) and self.pressed1:
self.cookie_count += self.grandma
I know that it has something to do with time, but other than that I'm not quite sure where to start.
Instead of incrementing the cookies once per second, you can make the cookie count just how many seconds have passed since the start. This may cause problem in certain scenarios (this will complicate pausing, for example), but will work for simple games.
My Python is a tad rusty, so sorry if this isn't entirely idiomatic:
import time
self.start_time = time.time()
# When you need to know how many cookies you have, subtract the current time
# from the start time, which gives you how much time has passed
# If you get 1 cookie a second, the elapsed time will be your number of cookies
# "raw" because this is the number cookies before Grandma's boost
self.raw_cookies = time.time() - self.start_time
if self.grandma:
self.cookies += self.raw_cookies * self.grandma
else:
self.cookies += raw.cookies
self.raw_cookies = 0
This may look more complicated than just using time.sleep, but it has two advantages:
Using sleep in games is rarely a good idea. If you sleep on the animation thread, you'll freeze the entire program for the duration of the sleep, which is obviously not a good thing. Even if that's not an issue in simple games, the use of sleep should be limited for habit's sake. sleep should really only be used in tests and simple toys.
sleep isn't 100% accurate. Over time, the error of the sleep time will accumulate. Whether or not this is a problem though is entirely dependant on the application. By just subtracting the time, you know exactly (or at least with high precision) how much time has passed.
Note:
With the code above, cookies will be a floating point number, not an integer. This will be more accurate, but may not look nice when you display it. Convert it to an integer/round it before displaying it.
Having never player "Cookie Clicker" before, I may have confused the logic. Correct me if something doesn't make sense.
I'm assuming self.grandma is None/falsey if the player doesn't have the upgrade.
The way to do this in pygame is to use pygame.time.set_timer() and have an event generated every given number of milliseconds. This will allow the event to be handled in the script's main loop like any other.
Here's a somewhat boring, but runnable, example of doing something like that:
import pygame
pygame.init()
SIZE = WIDTH, HEIGHT = 720, 480
FPS = 60
BLACK = (0,0,0)
WHITE = (255,255,255)
GREEN = (0,255,0)
RED = (255,0,0)
BLUE = (0,0,255)
BACKGROUND_COLOR = pygame.Color('white')
screen = pygame.display.set_mode(SIZE)
clock = pygame.time.Clock()
font = pygame.font.SysFont('', 30)
COOKIE_EVENT = pygame.USEREVENT
pygame.time.set_timer(COOKIE_EVENT, 1000) # periodically create COOKIE_EVENT
class Player(pygame.sprite.Sprite):
def __init__(self, position):
super(Player, self).__init__()
self.cookie_count = 0
self.grandma = 10 # grandma bakes 10 cookies/second
text = font.render(str(self.cookie_count), True, RED, BLACK)
self.image = text
self.rect = self.image.get_rect(topleft=position)
self.position = pygame.math.Vector2(position)
self.velocity = pygame.math.Vector2(0, 0)
self.speed = 3
def update_cookies(self):
self.cookie_count += self.grandma # 10 cookies per grandma
if self.cookie_count > 499:
self.cookie_count = 0
text = font.render(str(self.cookie_count), True, RED, BLACK)
self.image = text
player = Player(position=(350, 220))
running = True
while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == COOKIE_EVENT:
player.update_cookies()
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
player.velocity.x = -player.speed
elif keys[pygame.K_RIGHT]:
player.velocity.x = player.speed
else:
player.velocity.x = 0
if keys[pygame.K_UP]:
player.velocity.y = -player.speed
elif keys[pygame.K_DOWN]:
player.velocity.y = player.speed
else:
player.velocity.y = 0
player.position += player.velocity
player.rect.topleft = player.position
screen.fill(BACKGROUND_COLOR)
screen.blit(player.image, player.rect)
pygame.display.update()
You'll need to use time module. You can capture period of time with time.time().
import time
grandma = 3
cookie_count = 0
timeout = 1
while True:
cookie_count += grandma * 10
print 'cookie count: {}'.format(cookie_count)
time.sleep(timeout)
Another option is to validate the expression now - start > timeout. They will both do the same, but incase your timeout is larger than 1 this would be the solution. The first code above won't work.
import time
grandma = 3
cookie_count = 0
timeout = 1
start = time.time()
while True:
if time.time() - start > timeout:
cookie_count += grandma * 10
print 'cookie count: {}'.format(cookie_count)
time.sleep(timeout)
Related
I was working on making a animated background for my game. So I have the frames for it there are 174 ans store them in a list in a class. when I run the animate function in the class it only displays one image then dosnt do anything. I figured it is because of the for loop that is putting the images on the screen. How can I improve this so it can work properly?
basePath = os.path.dirname(sys.argv[0])+"\\"
pygame.init()
#setting fps lock for game and startign clock (Both are needed for fps)
fps = 30
fClock = pygame.time.Clock()
#Setting up pygame display size and window
res = pygame.display.Info()
tv = pygame.display.set_mode((res.current_w, res.current_h), pygame.SCALED)
class Background:
def __init__(self):
self.imFrames = os.listdir(basePath+"Background Frames/")
self.nTime = 0
def animate(self):
index = 0
tNow = pygame.time.get_ticks()
if (tNow > self.nTime):
fDelay = 0.1
self.nTime = tNow + fDelay
index += 1
if index >= len(self.imFrames):
index = 0
for item in self.imFrames:
tv.blit(pygame.image.load(basePath+"Background Frames/"+item).convert_alpha(), (0,0))
back = Background()
go = True
while go:
#event checks
for event in pygame.event.get():
if event.type == QUIT:
go = False
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
go = False
back.animate()
pygame.display.update()
fClock.tick(fps)
#Closing pygame and closing all functions
pygame.quit()
sys.exit()
Never implement a loop that tires to animate something in the application loop. This stops the application loop and the game becomes unresponsive. You need to load all the image int the constructor and loop through the images while the application loop is running.
Beside that, pygame.image.load is a very expensive operation. It needs to read this image file from the device and decode the image file. Never call pygame.image.load when the application loop is running, always load the images before the application for good performance.
Further note that pygame.time.get_ticks() returns the time in milliseconds, not seconds. 0.1 seconds is 100 milliseconds.
class Background:
def __init__(self):
self.imFrames = os.listdir(basePath+"Background Frames/")
self.images = []
for item in self.imFrames:
image = pygame.image.load(basePath+"Background Frames/"+item).convert_alpha()
self.images.appned(image)
self.image = self.images[0]
self.nTime = pygame.time.get_ticks()
self.index = 0
def animate(self):
tNow = pygame.time.get_ticks()
if tNow > self.nTime:
fDelay = 100
self.nTime = tNow + fDelay
self.index += 1
if self.index >= len(self.images):
self.index = 0
self.image = self.images[self.index]
tv.blit(self.image, (0,0))
You need to make a python list of all the images (a for-loop that appends pygame.image.load(basePath +"Background Frames"/item).convert_alpha() to the list would suffice), which is what I had mistakenly thought self.ImFrames was and instead of making a for-loop to run the animations, utilize the game-loop to your advantage and have the function which draws, increment the value every iteration of the game-loop (or you could do it externally which is how I did it):
def animate(self, index): # new param to avoid using global
tNow = pygame.time.get_ticks()
if (tNow > self.nTime):
fDelay = 0.1
self.nTime = tNow + fDelay
if index >= len(self.images):
index = 0
tv.blit(self.images[index])
index = 0
while go:
# code
# if animating, do this
index += 1
# code
Thank you for your help on my last question, i managed to fix my error.
So i am continuing my small game and i would like the player to see how many points they get (briefly) when they defeat an enemy.
if bullet_rect.colliderect(enemy1_rect):
#enemy respawn
enemy1_rect.x = 1350
enemy1_rect.y = random.randint(0, 500)
#points won display
points += 100
points_rect = points_text.render('+ 100$', None, 'Green')
screen.blit(points_rect, bullet_rect)
This kind of works, but it only displays for just 1 frame!
I tried pygame.time.wait,delay,sleep, and i have come nowhere.
I would like for it to display for half a second, so maybe since i run the game at 60 FPS capped, display it for 30 frames.
Any suggestions? Thank you!
You need to draw the text in the application loop. Use pygame.time.get_ticks to measure time in milliseconds. Calculate the time when the text must vanish and display the text until the time is reached:
text_end_time = 0
text_rect = None
run = True
while run:
# [...]
current_time = pygame.time.get_ticks()
if bullet_rect.colliderect(enemy1_rect):
enemy1_rect.x = 1350
enemy1_rect.y = random.randint(0, 500)
points += 100
points_surf = points_text.render('+ 100$', None, 'Green')
text_end_time = current_time + 2000 # 2 seconds
text_rect = bullet_rect.copy()
if text_end_time > current_time
screen.blit(points_surf, text_rect)
See also Pygame "pop up" text and Adding a particle effect to my clicker game.
I would like individual cars to drive across the screen from left to right with a certain time interval.It should be an endless loop. It is ended by program end
With my code, they all start at the same time. In addition, rect.y changes from the default value 300 to 0.
import pygame
import random
WIDTH = 800
HEIGHT = 600
FPS = 30
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("My Game")
clock = pygame.time.Clock()
class Car(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.images = []
for i in range(5):
img = pygame.image.load(f"Bilder/Gegenstaende/quer_auto_{i}.png")
self.images.append(img)
self.image = self.images[random.randrange(0, 5)]
self.rect = self.image.get_rect()
self.rect.x = 100
self.rect.y = 300
self.last = pygame.time.get_ticks()
self.delay = 5000
def update(self):
self.rect.x += 5
current = pygame.time.get_ticks()
if current - self.last > self.delay:
self.last = current
self.image = self.images[random.randrange(0, 5)]
self.rect = self.image.get_rect()
self.rect.x = 100
background = pygame.image.load("Bilder/starfield.png")
background_rect = background.get_rect()
cars = pygame.sprite.Group()
for i in range(3):
m = Car()
cars.add(m)
running = True
while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
cars.update()
screen.fill((250,250,250))
screen.blit(background,background_rect)
cars.draw(screen)
pygame.display.flip()
pygame.quit()
(Assuming you want these cars to be robots) First create variable and lets name them car_move_right and car_move_left . If you want the car to move towards the right at the beginning of the game, set the car_move_right to True and the other variable to False, and vice versa. Then in the main loop, add an if statement like this:
(Taking carX to be X-coordinate of car)
if car_move_right:
carX += ai_move_speed
if carX == WINDOWWIDTH or carX > WINDOWWIDTH:
car_move_right = False
car_move_left = True
if car_move_left:
carX -= ai_move_speed
if carX == 0 or carX < 0:
car_move_left = False
car_move_right = True
Please note: I had written this code for robot movement which also included going up and down. Not the cleanest and best, but I think it would work for you.
rect.y changes to 0 because you reset the rect, and default rect's x and y values are 0.
if current - self.last > self.delay:
self.last = current
self.image = self.images[random.randrange(0, 5)]
self.rect = self.image.get_rect() ##<--- here
self.rect.x = 100
Also you should consider storing your car's position in a python variable, since self.rect.x can only store integers.
For the timer thing, you should move your update function outside of your car class since it does not have any information about other cars and it needs it.
Here is an example implementation.
import pygame
WIDTH = 800
HEIGHT = 600
FPS = 30
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("My Game")
clock = pygame.time.Clock()
class Car(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((50, 50)).convert()
self.rect = self.image.get_rect()
self.rect.x = 100
self.rect.y = 300
def finished(self):
return self.rect.x >= WIDTH - self.rect.width
class Driver:
def __init__(self, cars):
self.cars = cars
self.currentCar = 0
self.delay = 1000
self.current = pygame.time.get_ticks()
self.last = pygame.time.get_ticks()
self.timePassed = False
def updateCar(self, car):
if self.timePassed:
car.rect.x += 1 #5
def runTimer(self):
self.current = pygame.time.get_ticks()
self.timePassed = False
if self.current - self.last > self.delay:
self.timePassed = True
def Run(self):
self.runTimer()
car = self.cars.sprites()[self.currentCar]
if not car.finished():
self.updateCar(car)
else:
self.current = pygame.time.get_ticks()
self.last = pygame.time.get_ticks()
self.currentCar += 1
def allFinished(self):
return self.currentCar >= len(self.cars)
cars = pygame.sprite.Group()
for i in range(3):
m = Car()
cars.add(m)
driver = Driver(cars)
while True:
if not driver.allFinished():
driver.Run()
#btw, no need to fill if you have background image the size of window
screen.fill((250,250,250))
#screen.blit(background,background_rect)
cars.draw(screen)
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
Create 10 car classes. Give them a different name, like car1, car2, etc.
Let's focus on car1 for now. Let car1X represent the x-coordinate of car1.
Now for the speed of movement. If you want each car to have the same speed, just create a variable named ai_move_speed. Now, if you want the speed to differ, then instead of ai_move_speed, make 10 variables and name these variables as ai1_move_speed, ai2_move_speed, etc. For now, set these variables to an integer value of 2.
For the movement of left to right, create a variable called car1_move_right and set it to True.
As each car will go from left to right, then die, We can simply add this code to our main loop :
if car1_move_right:
car1X += ai_move_speed
Now once the game is run, car1 will go from its x-coordinate towards the left side. Repeat this process for the other cars.
But if you want the cars to go back to their original position if they go out of the boundary or die, then just add this line of code at the end of the above code:
if carX == 800:
carX = 100
Now so far we fixed movement. Now if you want the cars to start 1 after the other, delete the entire code from before. Now you can customize when a car starts. For car1 let's simply create a variable and call this variable c1_time. Set it to 5000 for now. Now I want to introduce you to a pygame function called pygame.time.get_ticks(). It gets the number of milliseconds since pygame.init() has been called in milliseconds. Remember the c1_time? We set it to an integer value of 5000 because 1000 milliseconds is 1 second, so car1 will start moving after 5 seconds after the interpreter reads pygame.init().
Now create this variable : time_init and store this code inside it : pygame.time.get_ticks(). After that create an if statement as so :
if car1_move_right:
if time_init >= c1_time:
car1X += ai_move_speed
if car1X == 800:
car1X = 100
Now what this above code does is that 5 seconds after the game starts, it will start moving car1. If car1 goes out of boundary it gets it back again. Repeat these steps for the other cars. This is the simplest and bug-free way I could do it.
PS - Feel free to change any variable name. Just make sure to change it everywhere in the code.
PPS - Please read the entire thing. Also upvote if you found this helpful cuz I spent a lot of time on this.
I'm trying to create a program where balloons appear for the user to pop, however balloons are appearing so fast it becomes unmanageable. I took a screenshot about half a second into running the program:
Here is the code for time between balloons appearing:
timeTillNextBalloon = random.randint(100000, 200000)
while done == False:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if pygame.time.get_ticks() > timeTillNextBalloon:
timeTillNextBalloon = random.randint(30000, 250000)
yCoord = random.randint(50,350)
balloonType = random.randint(1,4)
balloon = Balloon(0, yCoord, "right", balloonType)
if balloonType >= 1 and balloonType <= 3:
otherBalloons.add(balloon)
else:
blueBalloons.add(balloon)
allBalloons.add(balloon)
I've tried to increase the timeTillNextBaloon variable, but it just shows a black screen if I try to make it any bigger than this.
Get_ticks gets the current time, timeTillNextBalloon should be the current to + the random value. Now every the loop repeats a balloon is added:
timeTillNextBalloon = pygame.time.get_ticks() + random.randint(30000, 250000)
I am looking to create characters in a game using pygame and python version 2.7.6. I have a simple python script which allows me to create a window and print an image to the screen. This works successfully, however it is simply a static image.
import pygame
class Game(object):
def main(self, screen):
#load first sprite to image for blit"
image = pygame.image.load('sprites/sprite1.png')
image2 = pygame.image.load('sprites/sprite2.png')
image3 = pygame.image.load('sprites/sprite3.png')
while 1:
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
return
screen.fill((200, 200, 200))
screen.blit(image, (320, 240))
pygame.display.flip()
if __name__ == '__main__':
pygame.init()
screen = pygame.display.set_mode((640, 480))
Game().main(screen)
I am looking to create a more dynamic image by changing the first sprite with the second and so on for at least three sprites possibly more (as sprite1.png, sprite2.png, sprite3.png). How can I cycle images after a given time interval has expired, for example: 1 second 1/2 seconds...
Lets go there from the top, when writing a game it's very important to keep the rendering loop away from the main loop of your program (the one that handles window events). This way even when something will get stuck in your rendering process, your window will be much less likely to become unresponsive. Among other benefits, for example people will be able to resize, and otherwise manipulate it, without interrupting the graphics thread.
There are some nice tutorials for it around the web, and while I don't have one at hand for python, SFML tutorials can teach you the right concepts which you can then use in python.
With that public service out of the way, when you already have the application split into separate threads, we are left with the issue of animations. Very simple implementation, and certainly not the best, would be to spawn a separate thread for each image that will cosist only of something like that:
def shake_it():
while True:
time.sleep(3)
#do rotation here
While this will work quite well, it is clearly not the best solution because it's very non-flexible. Imagine that now you would also like to do something else periodically, for example make your NPCs patrol the fort. This will require another thread, and this would quickly escalate to quite a high number of threads (which isn't bad in itself, but not a desired effect).
So to the rescue would be a much more generic thread - event handler. A class that will control all your functions that need to be executed at specific intervals. And then, in that additional thread you would use for animating images, you would run an instance of event_handler in the loop instead, which would work as an abstract for actions like animation.
Slightly more detail on #Puciek's suggestion in the comments, one could use a loop such as
import time
for img in [image, image1, image2]:
screen.fill((200, 200, 200))
screen.blit(img, (320, 240))
time.sleep(1) # sleep 1 second before continuing
To cycle through sprite images you should just blit the corresponding images in the loop. A good approach is to place all the images of the sequence in one image and load it when the program starts. Then use subsurface() function to get the image for blitting, so you can bind the "window" of the subsurface within the whole image with some variable which is changing in the loop. A separate question is, how to control the frequency, there are few approaches, here is one simple example of sprite animation. It is the same sprite sequence with 8 states, which is cycling at three different FPS: initial - 12 fps (1/12 second pause), then 1/3 of initial fps = 4 fps (3/12 second pause) and 1/12 of initial fps = 1 fps (12/12 = 1 second pause). I post the whole script, so you can run and see how it works, just put it with the image in one place and run the script.
Use this image with the script:
Source code:
import pygame, os
from pygame.locals import *
def Cycle(n, N) : # n - current state, N - max number of states
value = n+1
case1 = (value >= N)
case2 = (value < N)
result = case1 * 0 + case2 * value
return result
w = 600 # window size
h = 400
cdir = os.path.curdir # current directory in terminal
states = 8 # number of sprite states
pygame.init()
DISP = pygame.display.set_mode((w, h))
BG = (36, 0, 36) # background color
DISP.fill(BG)
band_1 = pygame.image.load(os.path.join(cdir, "band1.png")).convert()
K = 4 # global scale factor
band_1 = pygame.transform.scale(band_1, (K * band_1.get_width(), K * band_1.get_height()))
band_1.set_colorkey((0,0,0))
sw = K*8 # sprite width
sh = K*8 # sprite height
r0 = (100, 100, sw, sh) # sprite 0 rectangle
r1 = (150, 100, sw, sh) # sprite 1 rectangle
r2 = (200, 100, sw, sh) # sprite 2 rectangle
s0 = 0 # initial state of sprite 0
s1 = 0 # initial state of sprite 1
s2 = 0 # initial state of sprite 2
t0 = 1 # main ticker
t1 = 1 # ticker 1
t2 = 1 # ticker 2
Loop_1 = True
FPS = 12
clock = pygame.time.Clock( )
while Loop_1 :
clock.tick(FPS)
DISP.fill(BG, r0)
sprite_0 = band_1.subsurface((s0*sw, 0, sw, sh))
DISP.blit(sprite_0, r0)
s0 = Cycle(s0, states)
if t1 == 3 :
DISP.fill(BG, r1)
sprite_1 = band_1.subsurface((s1*sw, 0, sw, sh))
DISP.blit(sprite_1, r1)
s1 = Cycle(s1, states)
t1 = 0
if t2 == 12 :
DISP.fill(BG, r2)
sprite_2 = band_1.subsurface((s2*sw, 0, sw, sh))
DISP.blit(sprite_2, r2)
s2 = Cycle(s2, states)
t2 = 0
for event in pygame.event.get( ):
if event.type == QUIT : Loop_1 = False
if event.type == KEYDOWN:
if event.key == K_ESCAPE : Loop_1 = False
# t0 = t0 + 1
t1 = t1 + 1
t2 = t2 + 1
pygame.display.update()
pygame.quit( )