Python - How to trigger action in method from a different method - python

I want to spawn an enemy into the game at random intervals of 1-5 seconds. To prevent the gameplay part of the program (moving character etc.) from pausing whenever there is a sleep, I have split the whole code into 2 methods so that I can have 2 threads- one that sets enemyspawn True every 1-5 seconds and another one which controlls the character.
Sadly, either the 2nd method doesn't change enemyspawn in the first method or threading outputs an error. I have read about and tried retrieving the value from EnemySpawn() before the if statement but that didn't work (or I just did it wrong). I have also tried turning enemyspawn to a global variable. Didn't change anything.
def Gameplay():
width=80
height=80
y = 720/2+height/2
x = 720/2+width/2
speed=2
enemyspawn = False
while True:
#controll character here
if enemyspawn:
enemyspawn=False
print(enemyspawn) #Spawn enemy here later
window.blit(bg, [0,0])
pygame.draw.rect(window,(100,100,100),(x,y,width,height))
pygame.display.update()
def EnemySpawn():
enemyspawn = EnemySpawn() #idrk about this line
while True:
sleep(randint(1,5))
enemyspawn=True
print(enemyspawn)
return enemyspawn
Gameplay = threading.Thread(target=Gameplay)
Gameplay.start()
EnemySpawn = threading.Thread(target=EnemySpawn)
EnemySpawn.start()
The threading error message:
line 51, in EnemySpawn
enemyspawn = Gameplay(enemyspawn)
UnboundLocalError: local variable 'enemyspawn' referenced before assignment
Another error message:
line 51, in EnemySpawn
enemyspawn = EnemySpawn()
TypeError: 'Thread' object is not callable

You wouldn't want to call a function from within the same function (a recursive function) in this situation. A better approach might be:
def spawn_enemy():
# code that makes an enemy
print("an enemy has been spawned")
return True # would be "return enemy" after you create your enemy entity
def EnemySpawnThread():
enemy_list = [] # to maintain records of all enemies made
while True: # make enemies forever
sleep(randint(1,5))
enemy_list.append(spawn_enemy()) # call our function we made above which spawns enemies
Currently this would just make a list like [True, True, True, ...], but eventually you would probably define an enemy class and it would become [enemy_object, enemy_object, ...] which is preferable because the best way I've found to delete objects is to store them in lists and then del enemy_list[index] to delete them (for instance when the enemy dies so it doesn't keep using memory). Additionally it gives you the power to iterate over like:
for enemy in enemy_list:
enemy.move()
enemy.attack()
enemy.die()
Which is why you would want enemy to be a class that has methods like shown above.
As a side note, you would likely want to have enemy_list as a global variable (or in your main function) so that all your threads can access it. Otherwise you will need to implement the queue.Queue() built in standard with python.

Related

Python Processing: How to call multiple of the same class

I am a noob at Python and I am having trouble with this problem. I am trying to create a raindrop scene with the 'drop' class. Right now, the code only calls one drop at a time. How can I call multiple drops at once?
Here is my code:
import random
class drop():
def __init__(self):
# where the drop starts
self.x = random.randint(20,480)
self.y = 0
#how fast the drop falls
self.yspeed = 5
def fall(self):
self.y = self.y+self.yspeed
def show(self):
# color of drop
stroke(1,1,1)
fill(1,1,1)
strokeWeight(1)
line(self.x,self.y,self.x,self.y+10)
def setup():
size(500,500)
global d
d = drop()
def draw():
background(255,255,255)
d.fall()
d.show()
You can create as many instances of a class as you want. For example the following code creates two drops.
d1 = drop()
d2 = drop()
You can also create a list of drops like this -
drops = []
drops.append(drop())
drops.append(drop())
drops[0].fall()
drops[1].show()
I'm a bit rusty with Python, But I would suggest changing draw to accept an array of elements.
So for example, in setup and can generate an array of droplets by doing:
def setup():
size(500,500) #Idk what this does, I assume its another function somewhere else in your code
droplets = [] #The array of droplets we want to render
for x in range(6): #6 is the number of droplets we want.
droplets.append(drop())
#Pass droplets into draw.
def draw(entities):
background(255,255,255) #Again, IDK what this does but its in your code. I assume it is another function somewhere.
for droplet in entities: #Goes through ALL the droplets in the list you passed.
droplet.fall() #Calls these methods on every droplet in the array.
droplet.show()
This is untested, but from here you would somehow pass the droplets array into draw and BOOM you have a bunch of droplets being rendered every draw cycle. I would suggest NOT using global variables. Its just bad practice. You should have another function somewhere that runs all of this, I assume on a time for the draw cycle.

How to display text for only a certain amount of time?

I'm looking to display a message on the screen when a player wins, and get the text to display for 5 seconds, and then go back to the main menu start screen. Using the time.delay function however, my screen pauses and then displays the text in a flash, but then immediately goes to the startscreen. Is there a more efficient way of getting the text to be displayed for long enough to be read?
Below is the function I use to actually display the message:
def winnerPlayerOne():
screen.fill(PINK)
winnerP1Message = winnerFont.render("Congrats Player 1. You Win!", True, WHITE)
screen.blit(winnerP1Message, ((400 - (winnerP1Message.get_width()/2)),(300 - (winnerP1Message.get_height()/2))))
pygame.display.update()
pygame.time.delay(5000)
startscreen()
And below this is how I call this function, within the main loop:
if playeroneScore == 5:
winnerPlayerOne()
if playertwoScore == 5:
winnerPlayerTwo()
Any help would be greatly appreciated!
Benjamin's answer probably will work fine in your case. But if you want something that doesn't interfere with your game's visuals, I would consider setting a timer like so...
WITHIN GAME LOOP:
if gameWonScreen:
screen.fill(PINK)
winnerP1Message = winnerFont.render("Congrats Player 1. You Win!", True, WHITE)
screen.blit(winnerP1Message, ((400 - (winnerP1Message.get_width()/2)),(300 - (winnerP1Message.get_height()/2))))
timer = timer + elapsed/1000
elapsed = fpsClock.tick(FPS)
if timer > timeWinScreen:
gameWonScreen = false
Initialize 'timeWinScreen' to the desired message duration at the start of the application and set 'timer' to '0' and gameWonScreen to 'true' when the player wins. Using elapsed = fpsClock.tick(FPS) will hold the time value since the last tick. You don't need to use it for this process (you could just use a fraction of your FPS) but using 'elapsed' is good practice because it helps with smoothing animations of certain objects.
Try out pygame.time.wait(5000). It should behave more in the way you are expecting. It does prevent any code running in the background as well, but that didn't seem like it would be an issue for your use case.
Try this:
Remember that in the class below, time is not the way you measure time in pygame. It is the number of loop happened in the main loop.
class disp_txt:
def __init__(self,text,time,destination):
self.text = text
self.time = time
self.destination = destination
self.shfnt = pygame.font.SysFont("comicsens",30,False)
self.shtxt = self.shfnt.render(self.text,0,(255,0,0))
def show(self,surface):
if self.time>0:
surface.blit(self.shtxt,self.destination)
self.time -= 1
hint = disp_txt("text",100,(100,400)) #example
hint.show(screen) #in the main loop or where you are drawing

Create a lot of objects and delete them

so i'm kinda new to programming, but for now with python and pygame i'm trying to create a small game. It is quite simple, the player will move around dodging small projectiles. However i'm having trouble creating a lot of objects, for example i need to randomly generate a position and a speed for a new projectile, and how do i create many objects like that, plus when they actually go 'out of the screen' they should disapear.
So I also need to delete them. My first idea was to create a list of objects, I and a loop that would move them one by one before updating the screen, but how do i create new objects with different names while I don't know how many projectiles will be on the screen, it should be random.
class Projectiles:
ProjectilesCount = 0
def __init__(self, x, y, speed):
self.pos = (x,y)
self.speed = speed
Projectiles.ProjectilesCount += 1
def moveProj(self):
x, y = self.pos
x -= self.speed
self.pos = (x,y)
pygame.draw.line(DISPLAY, WHITE, self.pos, (x,y+self.SIZE), self.SIZE)
bullet = Projectiles(500,200,5)
bullet.SIZE = SIZE
while true:
# some stuff
bullet.moveProj()
pygame.display.update()
fpsClock.tick(FPS)
This is the class i use for now (it just goes left) and it works for just one projectile.
You want a list:
list_of_bullets = []
for i in range(100):
list_of_bullets.append(Projectiles(...))
Use dictionaries. A list will work, but when using objects where the order is not important use dictionaries. Dictionaries also perform better when iterating through lots of data within them. Use some code similar to below...
bulletdict = {}
#This is a list because we can change the value within a function without returning it
#You could also use a global variable instead of a list
bulletcount = [0]
def SpawnBullet(bulletdict,bulletcount):
bulletdict[bulletcount] = Projectiles
bulletcount[0] += 1
The key will be a number and the value will be the bullet itself. To iterate through the dictionary to update stuff do...
for item in bulletdict:
bulletdict[item].moveproj(...)
If you need to remove stuff use code similar to this. I usually have 'checkDelete' functions inside my objects that I can call to check to see if we should remove them. This is used for things like deleting objects if they get off the screen or collide with something.
def deleteObjects(objectdict):
deletelist = []
for item in objectdict:
if objectdict[item].checkdelete() == True:
deletelist.append(item)
for item in deletelist:
del objectdict[item]
This allows for easy use and iteration of your objects. You can always tell the last spawned object because it will have the highest number as the key. This key number will also tell you the total number of spawned objects if you so need it.
I hope this helps you and good luck making your game.

How to use a while loop within a class in pygame?

I'm trying to make an instance class (Block) spawn when the mouse is clicked and fade out slowly, and i want to be able to spawn an infinite number of blocks that fade out provided that the mouse is clicked fast enough.
To do this, I want to initiate the fade() function when the instance is first spawned and set ticks to 255 and set the alpha to ticks.
However, when using a while loop, the function completes itself without updating the display because the program gets constrained to the while loop in the fade() function.
Can someone help me with calling the fade function 255 times per instance?
import pygame,sys,time,Blockm
from pygame.locals import *
black,white=(0,0,0),(255,255,255)
size=w,h=1400,800
screen=pygame.display.set_mode(size)
pygame.init()
class Block(object):
sprite = None
def __init__(self, x, y):
if not Block.sprite:
Block.sprite = pygame.image.load("Block.png").convert_alpha()
self.rect = Block.sprite.get_rect(top=y, left=x)
self.fade()
def fade(self):
ticks=255
Block.sprite.set_alpha(ticks)
while ticks!=0:
ticks-=1
print(ticks)
while True:
blocks = []
mmraw=pygame.mouse.get_pos()
mmx,mmy=mmraw[0]-(pygame.image.load("Block.png").get_width())/2,mmraw[1]-(pygame.image.load("Block.png").get_width())/2
for event in pygame.event.get():
if event.type== pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
print('click')
blocks.append(Block(mmx,mmy))
for block in blocks:
screen.blit(block.sprite, block.rect)
print('trying to blit')
print(Block.sprite.get_offset())
pygame.display.update()
I have one solution for you that ought to accomplish what you want it to, but I concede that it does not answer the question exactly as asked.
The first and most hindering problems you may be having are that you need to use pygame.image.load("Block.png").convert() and not pygame.image.load("Block").convert_alpha(), since .convert_alpha() doesn't work with .set_alpha(..).
It's also possible that you aren't noticing when blocks fade in any solution because screen isn't being refreshed prior to update. New, faded blocks are being drawn over 'brighter' ones, producing no difference (overlapping blocks notwithstanding). I've added a stopgap to my solution code below that fills the screen with blue, but I imagine you'll want something different. It's marked with a comment.
What I would suggest is having each Block process its alpha locally during a call from the main loop in your for block in blocks: block before you blit it. This version of your code should give you the result you want, although it does it using just the main loop, rather than parallel loops like you were asking about...
import pygame,sys,time,Blockm
from pygame.locals import *
black,white=(0,0,0),(255,255,255)
size=w,h=1400,800
screen=pygame.display.set_mode(size)
pygame.init()
class Block(object):
sprite = None
def __init__(self, x, y):
if not Block.sprite:
Block.sprite = pygame.image.load("Block.png").convert()
# ^ MODIFIED: .convert_alpha() won't recognize the effects of .set_alpha(), so use regular .convert() instead.
self.rect = Block.sprite.get_rect(top=y, left=x)
self.ticks = 255 # NEW: Set a local tick count.
# REMOVED `self.fade()`: This runs on main now.
def fade(self):
## REPURPOSED METHOD: Runs each frame that the Block is active. Called on main loop.
# MODIFIED BLOCK: Update local tick count before setting the class's sprite alpha.
self.ticks -= 1
Block.sprite.set_alpha(self.ticks) # MOVED, MODIFIED: uses local tick count for alpha.
print(self.ticks) # UPDATED: Using local ticks.
blocks = [] # MOVED: Make a list for the Blocks, but don't clear it on frame.
while True:
mmraw=pygame.mouse.get_pos()
mmx,mmy=mmraw[0]-(pygame.image.load("Block.png").get_width())/2,mmraw[1]-(pygame.image.load("Block.png").get_width())/2
# ^ There may be a tidier way of doing this. Described in solution body...
for event in pygame.event.get():
if event.type== pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
print('click')
blocks.append(Block(mmx,mmy))
screen.fill((22,22,222))
# ^ NEW: Fill the screen with some backdrop so that the erasure is obvious!
# If you don't do this, you'll be blitting faded images over brighter ones and there will be no real change!!
for block in blocks:
block.fade() # NEW: Update this block.
if block.ticks < 1: # NEW BLOCK: If the block has become invisible...
blocks.remove(block) # NEW: Expunge this invisible block.
continue # NEW: Immediately move on to the next block.
screen.blit(Block.sprite, block.rect)
print('trying to blit')
print(Block.sprite.get_offset())
pygame.display.update()
There's a small problem with it, which is that vanishing Blocks trigger a 'flicker' in other remaining Blocks (or at least the next one in blocks) and I'm not sure why or how.
While I was looking, I found a few other things you might want to consider:
...in class Block::
-Consider using sprite = pygame.image.load("Block.png").convert() instead of sprite = None. This way, you can use something like mmx,mmy = mmraw[0] - Block.sprite.get_rect.centerx, mmraw[1] - Block.sprite.get_rect().centery instead of loading the image for a moment, just to know its size. Since all Blocks use the same graphic, it shouldn't make a difference, and this way, you won't have to reacquire the offset if you change Block.sprite during runtime!
-Consider assigning a copy of Block's sprite to each instance instead of using the class's Surface. This will take up more processing power, but only momentarily if you use it as a return instead. For instance:
class Block(object):
...
def fade(self):
sprite = Block.sprite.copy()
sprite.set_alpha(self.ticks)
self.ticks -= 1
return sprite
...
while True: # main loop
...
for block in blocks:
screen.blit(block.fade(), block.rect) # Although there are more Pythonic ways of writing this.
Alternatively, you could use screen.blit(sprite, self.rect) in Block.fade() rather than on main and forego the return entirely. Because the alpha is set locally, it won't have to be reset every time fade runs, and Block's unbound sprite can stay fresh!
Anyway, I hope this solves your problem, even if it does not (exactly) answer your question!
You probably want to use threads. These allow you to run multiple things at once. For your code, change it like this:
from threading import Thread
import pygame, sys, time, Blockm
from pygame.locals import *
black = (0,) * 3
white = (255,) * 3
size = w, h = 1400, 800
screen = pygame.display.set_mode(size)
pygame.init()
class Block(object):
sprite = None
def __init__(self, x, y):
if not Block.sprite:
Block.sprite = pygame.image.load("Block.png").convert_alpha()
self.rect = Block.sprite.get_rect(top=y, left=x)
Thread(target=self.fade) # Made this a Thread so it runs separately.
def fade(self):
for tick in range(255, 0, -1):
Block.sprite.set_alpha(tick)
print(tick)
def game():
while True:
# The contents of that while True loop at the end
def main():
Thread(target=Main)
if __name__ == "__main__":
main()
This also adds an entry point to your program, which was lacking. Also, Block.fade wouldn't actually do what you want, as you only set_alpha once. I fixed that with a for loop instead. Also, note that you can now just return to break out of the whole game.

If player comes within a radius of enemy,change state

So I've been teaching myself some pygame for the past few months, doing a spaceship game.
I have multiple enemy spaceships that guard a certain area. If the player comes within a certain radius of them, I want to change the enemy's state. What's the best way to do this for a beginner, without using vectors?
You can create a method in your program called is_close and then call it for each player and enemy comparison. It would look something like this:
def is_close(object1, object2, distance):
return math.hypot(object2.x-object1.x, object2.y-object1.y) < float(distance)
... #rest of your code
while True: #This is your main while loop
... #rest of your code
for enemy in enemies:
if is_close(player, enemy, 25):
enemy.state = new_state #change state, this may simply be a color change

Categories

Resources