I am trying to learn PyMunk and I used their basic example from the website:
import pymunk
space = pymunk.Space()
space.gravity = 0,-1000
body = pymunk.Body(1,1666)
body.position = 50,100
poly = pymunk.Poly.create_box(body)
space.add(body, poly)
while True:
space.step(0.02)
But it does not create a window, does not show anything. How to use PyGame to create the graphical window?
What that example does is create a simulation, add a box shaped object inside and then run the simulation infinitely. The code doesn't print or draw anything, so you will not actually see the output. To get a better understanding and something on screen I suggest you start with the tutorial: http://www.pymunk.org/en/latest/tutorials/SlideAndPinJoint.html
Pymunk is a 2d rigid body physics library, which means that what it does is simulate how objects move and interact with each other in 2 dimensions. Its not made for drawing to the screen or read input.
You can of course use it as is without anything else, and just print out the result of the simulation. But more common is that you want to draw to the screen, read input and so on. One way to do that is by using the game library Pygame that helps out with drawing to the screen, reading input, having a game loop and so on.
Pymunk itself does have some helper functions so that you can easily connect it with Pygame (and a couple of other libraries), but this is not the core part. Usually these helper functions are good for when you want something quick-n-dirty such as a prototype and you don't have need to customize the drawing.
Now, this said, if you want to see something you can add a print statement to the while loop, so it becomes like this:
while True:
space.step(0.02)
print(body.position)
Then it will print out the position of the ball each step of the simulation, and you can see that its changing all the time (because of the gravity that is set on the space).
There are more advanced examples included in Pymunk that are both interactive and show something on screen. These examples depends on mostly either Pygame or Pyglet, but the principle is the same in case you have a different library you want to use it with.
Here's an example that shows how I use Pymunk in combination with pygame. The Entity class is a pygame.sprite.Sprite subclass to which I attach a pymunk.Body and a pymunk.Shape as well as a reference to the pm.Space, so that the bodies and shapes can be added and removed from it. The position of the sprite's rect gets set to the self.body.position each frame, so that we get the correct blit position for the self.image and can simply draw all sprites by calling self.sprite_group.draw(self.screen).
import math
import pygame as pg
import pymunk as pm
from pymunk import Vec2d
def flipy(p):
"""Convert chipmunk coordinates to pygame coordinates."""
return Vec2d(p[0], -p[1]+600)
class Entity(pg.sprite.Sprite):
def __init__(self, pos, space):
super().__init__()
self.image = pg.Surface((46, 52), pg.SRCALPHA)
pg.draw.polygon(self.image, (0, 50, 200),
[(0, 0), (48, 0), (48, 54), (24, 54)])
self.orig_image = self.image
self.rect = self.image.get_rect(topleft=pos)
vs = [(-23, 26), (23, 26), (23, -26), (0, -26)]
mass = 1
moment = pm.moment_for_poly(mass, vs)
self.body = pm.Body(mass, moment)
self.shape = pm.Poly(self.body, vs)
self.shape.friction = .9
self.body.position = pos
self.space = space
self.space.add(self.body, self.shape)
def update(self, dt):
pos = flipy(self.body.position)
self.rect.center = pos
self.image = pg.transform.rotate(
self.orig_image, math.degrees(self.body.angle))
self.rect = self.image.get_rect(center=self.rect.center)
# Remove sprites that have left the screen.
if pos.x < 20 or pos.y > 560:
self.space.remove(self.body, self.shape)
self.kill()
def handle_event(self, event):
if event.type == pg.KEYDOWN:
if event.key == pg.K_a:
self.body.angular_velocity = 5.5
elif event.key == pg.K_w:
self.body.apply_impulse_at_local_point(Vec2d(0, 900))
class Game:
def __init__(self):
self.done = False
self.clock = pg.time.Clock()
self.screen = pg.display.set_mode((800, 600))
self.gray = pg.Color('gray68')
self.red = pg.Color('red')
# Pymunk stuff.
self.space = pm.Space()
self.space.gravity = Vec2d(0.0, -900.0)
self.static_lines = [
pm.Segment(self.space.static_body, (60, 100), (370, 100), 0),
pm.Segment(self.space.static_body, (370, 100), (600, 300), 0),
]
for lin in self.static_lines:
lin.friction = 0.8
self.space.add(self.static_lines)
# A sprite group which holds the pygame.sprite.Sprite objects.
self.sprite_group = pg.sprite.Group(Entity((150, 200), self.space))
def run(self):
while not self.done:
self.dt = self.clock.tick(30) / 1000
self.handle_events()
self.run_logic()
self.draw()
def handle_events(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.done = True
if event.type == pg.MOUSEBUTTONDOWN:
self.sprite_group.add(Entity(flipy(event.pos), self.space))
for sprite in self.sprite_group:
sprite.handle_event(event)
def run_logic(self):
self.space.step(1/60) # Update physics.
self.sprite_group.update(self.dt) # Update pygame sprites.
def draw(self):
self.screen.fill(pg.Color(140, 120, 110))
for line in self.static_lines:
body = line.body
p1 = flipy(body.position + line.a.rotated(body.angle))
p2 = flipy(body.position + line.b.rotated(body.angle))
pg.draw.line(self.screen, self.gray, p1, p2, 5)
self.sprite_group.draw(self.screen)
# Debug draw. Outlines of the Pymunk shapes.
for obj in self.sprite_group:
shape = obj.shape
ps = [pos.rotated(shape.body.angle) + shape.body.position
for pos in shape.get_vertices()]
ps = [flipy((pos)) for pos in ps]
ps += [ps[0]]
pg.draw.lines(self.screen, self.red, False, ps, 1)
pg.display.flip()
if __name__ == '__main__':
pg.init()
Game().run()
pg.quit()
Related
I am trying to learn PyMunk and I used their basic example from the website:
import pymunk
space = pymunk.Space()
space.gravity = 0,-1000
body = pymunk.Body(1,1666)
body.position = 50,100
poly = pymunk.Poly.create_box(body)
space.add(body, poly)
while True:
space.step(0.02)
But it does not create a window, does not show anything. How to use PyGame to create the graphical window?
What that example does is create a simulation, add a box shaped object inside and then run the simulation infinitely. The code doesn't print or draw anything, so you will not actually see the output. To get a better understanding and something on screen I suggest you start with the tutorial: http://www.pymunk.org/en/latest/tutorials/SlideAndPinJoint.html
Pymunk is a 2d rigid body physics library, which means that what it does is simulate how objects move and interact with each other in 2 dimensions. Its not made for drawing to the screen or read input.
You can of course use it as is without anything else, and just print out the result of the simulation. But more common is that you want to draw to the screen, read input and so on. One way to do that is by using the game library Pygame that helps out with drawing to the screen, reading input, having a game loop and so on.
Pymunk itself does have some helper functions so that you can easily connect it with Pygame (and a couple of other libraries), but this is not the core part. Usually these helper functions are good for when you want something quick-n-dirty such as a prototype and you don't have need to customize the drawing.
Now, this said, if you want to see something you can add a print statement to the while loop, so it becomes like this:
while True:
space.step(0.02)
print(body.position)
Then it will print out the position of the ball each step of the simulation, and you can see that its changing all the time (because of the gravity that is set on the space).
There are more advanced examples included in Pymunk that are both interactive and show something on screen. These examples depends on mostly either Pygame or Pyglet, but the principle is the same in case you have a different library you want to use it with.
Here's an example that shows how I use Pymunk in combination with pygame. The Entity class is a pygame.sprite.Sprite subclass to which I attach a pymunk.Body and a pymunk.Shape as well as a reference to the pm.Space, so that the bodies and shapes can be added and removed from it. The position of the sprite's rect gets set to the self.body.position each frame, so that we get the correct blit position for the self.image and can simply draw all sprites by calling self.sprite_group.draw(self.screen).
import math
import pygame as pg
import pymunk as pm
from pymunk import Vec2d
def flipy(p):
"""Convert chipmunk coordinates to pygame coordinates."""
return Vec2d(p[0], -p[1]+600)
class Entity(pg.sprite.Sprite):
def __init__(self, pos, space):
super().__init__()
self.image = pg.Surface((46, 52), pg.SRCALPHA)
pg.draw.polygon(self.image, (0, 50, 200),
[(0, 0), (48, 0), (48, 54), (24, 54)])
self.orig_image = self.image
self.rect = self.image.get_rect(topleft=pos)
vs = [(-23, 26), (23, 26), (23, -26), (0, -26)]
mass = 1
moment = pm.moment_for_poly(mass, vs)
self.body = pm.Body(mass, moment)
self.shape = pm.Poly(self.body, vs)
self.shape.friction = .9
self.body.position = pos
self.space = space
self.space.add(self.body, self.shape)
def update(self, dt):
pos = flipy(self.body.position)
self.rect.center = pos
self.image = pg.transform.rotate(
self.orig_image, math.degrees(self.body.angle))
self.rect = self.image.get_rect(center=self.rect.center)
# Remove sprites that have left the screen.
if pos.x < 20 or pos.y > 560:
self.space.remove(self.body, self.shape)
self.kill()
def handle_event(self, event):
if event.type == pg.KEYDOWN:
if event.key == pg.K_a:
self.body.angular_velocity = 5.5
elif event.key == pg.K_w:
self.body.apply_impulse_at_local_point(Vec2d(0, 900))
class Game:
def __init__(self):
self.done = False
self.clock = pg.time.Clock()
self.screen = pg.display.set_mode((800, 600))
self.gray = pg.Color('gray68')
self.red = pg.Color('red')
# Pymunk stuff.
self.space = pm.Space()
self.space.gravity = Vec2d(0.0, -900.0)
self.static_lines = [
pm.Segment(self.space.static_body, (60, 100), (370, 100), 0),
pm.Segment(self.space.static_body, (370, 100), (600, 300), 0),
]
for lin in self.static_lines:
lin.friction = 0.8
self.space.add(self.static_lines)
# A sprite group which holds the pygame.sprite.Sprite objects.
self.sprite_group = pg.sprite.Group(Entity((150, 200), self.space))
def run(self):
while not self.done:
self.dt = self.clock.tick(30) / 1000
self.handle_events()
self.run_logic()
self.draw()
def handle_events(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.done = True
if event.type == pg.MOUSEBUTTONDOWN:
self.sprite_group.add(Entity(flipy(event.pos), self.space))
for sprite in self.sprite_group:
sprite.handle_event(event)
def run_logic(self):
self.space.step(1/60) # Update physics.
self.sprite_group.update(self.dt) # Update pygame sprites.
def draw(self):
self.screen.fill(pg.Color(140, 120, 110))
for line in self.static_lines:
body = line.body
p1 = flipy(body.position + line.a.rotated(body.angle))
p2 = flipy(body.position + line.b.rotated(body.angle))
pg.draw.line(self.screen, self.gray, p1, p2, 5)
self.sprite_group.draw(self.screen)
# Debug draw. Outlines of the Pymunk shapes.
for obj in self.sprite_group:
shape = obj.shape
ps = [pos.rotated(shape.body.angle) + shape.body.position
for pos in shape.get_vertices()]
ps = [flipy((pos)) for pos in ps]
ps += [ps[0]]
pg.draw.lines(self.screen, self.red, False, ps, 1)
pg.display.flip()
if __name__ == '__main__':
pg.init()
Game().run()
pg.quit()
I am working with Pygame currently, and I made a simple function to create window instances much like Windows 10 UI. the code I made doesn't give any errors or any unwanted outputs. It just seems not to be working properly, what I mean by "not working properly"; it just doesn't seem to be moving the frames that are meant to be dragged around by a master frame...
This is my code:
import pygame
from pyautogui import size
import datetime
pygame.init()
infoObject = pygame.display.Info()
surface = pygame.display.set_mode((900, 700))
run = True
clock = pygame.time.Clock()
def draw_text(text, font, text_col, x,y):
img = font.render(text, True, text_col)
rect = img.get_rect()
rect.center = (x,y)
surface.blit(img, rect)
return rect
class make_a_window():
def __init__(self,app,width=750,height=500):
self.app_name = str(app)
self.width = width
self.height = height
def run(self):
self.top_frame = pygame.draw.rect(surface, "#C0C0C0", pygame.Rect(0,0,int(self.width),40))#master frame
self.main_frame = pygame.draw.rect(surface, (255,255,255), pygame.Rect(0,40,int(self.width),int(self.height)))
self.red_frame_for_exit_btn_X = pygame.draw.rect(surface, (255,0,0), pygame.Rect(self.width-42,0,42,40))
self.exit_btn_X = draw_text("x", pygame.font.SysFont("calibri",25), "black", self.width-20, 15)
self.mouse_position = pygame.mouse.get_pos()
if pygame.mouse.get_pressed()[0] == 1:
if self.top_frame.collidepoint(self.mouse_position):
#moving the frames
self.top_frame.move(self.mouse_position[0],self.mouse_position[1])
self.main_frame.move(self.mouse_position[0]-40,self.mouse_position[1])
self.red_frame_for_exit_btn_X.move(self.mouse_position[0]-42,self.mouse_position[1])
self.exit_btn_X.move(self.mouse_position[0]-20,self.mouse_position[1])
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
run = False
app = make_a_window("hello")
app.run()
pygame.display.update()
clock.tick(60)
Sorry for my bad English. and thanks for the help, I really appreciate it 😃!
There is some logic error from line 32 to 41.
Firstly you should use the event queue by pygame.event.get() to track mouse activities(this is really important) and secondly why are you recording the mouse position before hand you are checking for its collision. Instead you should insert your
{self.mouse_position = pygame.mouse.get_pos()}
inside the collision checking if statement (rather that would not work smoothly until you use pygame.event.get())
One more thing that the function
pygame.Rect().move()
takes x and y offesets as its arguments not x and y coordinates.
So, mainly give focus on your event loop and the destination positions of your manual window. Maybe I would share the correct code later (don't wait for it.)
The method pygame.Rect.move doesn't move the rectangle itself, but it returns new rectangle that is moved. In compare, the method pygame.Rect.move_ip move the object in place.
However, these methods do not move anything that has been drawn on the screen. These methods simply move a rectangle representing an area of the screen. This rectangle can later be used to draw something on the screen at a new location.
Create the pygame.Rect objects in the class's constructor and use them to draw the objects. Use move_ip to move the rectangles:
class make_a_window():
def __init__(self,app,width=750,height=500):
self.app_name = str(app)
self.width = width
self.height = height
self.top_frame = pygame.Rect(0,0,int(self.width),40)
self.main_frame = pygame.Rect(0,40,int(self.width),int(self.height))
self.red_frame_for_exit_btn_X = pygame.Rect(self.width-42,0,42,40)
self.exit_btn_X = pygame.Rect(self.width-20, 15, 0, 0)
def run(self):
pygame.draw.rect(surface, "#C0C0C0", self.top_frame)
pygame.draw.rect(surface, (255,255,255), self.main_frame)
pygame.draw.rect(surface, (255,0,0), self.red_frame_for_exit_btn_X)
draw_text("x", pygame.font.SysFont("calibri",25), "black", self.exit_btn_X.topleft)
self.mouse_position = pygame.mouse.get_rel()
if pygame.mouse.get_pressed()[0] == 1:
if self.top_frame.collidepoint(self.mouse_position):
#moving the frames
move_rel = pygame.mouse.get_rel()
self.top_frame.move_ip(*move_rel)
self.main_frame.move_ip(*move_rel)
self.red_frame_for_exit_btn_X.move_ip(*move_rel)
self.exit_btn_X.move_ip(*move_rel)
I was programming a game and I did not know how to program in python 3.7 so I had to get a book called Code This Game, book from OddDot. And When I got to chapter 6 I was learning how to create a group for the enemy sprites. and every time I ran the program all it drew was the background image and the grid(because the book is not only teaching me how to code in Python, while I am learning I am making a game from the book while I learn from the book.) it did not draw the group of Vampire Pizzas in the right column if fact it doesn't even draw them at all. I have tried redoing the chapter over and over again and I can't do it
here is my code
#Import Libraries
import pygame
from pygame import *
from random import randint
#Initialize pygame
pygame.init()
#Define constant variables
WINDOW_WIDTH = 1100
WINDOW_HEIGHT = 600
WINDOW_RES = (WINDOW_WIDTH, WINDOW_HEIGHT)
#Define Tile Parameters
WIDTH = 100
HEIGHT = 100
#Define Tile colors
WHITE = (255, 255, 255)
#Set up rates
SPAWN_RATE = 360
#This is the code where the game window will show up
GAME_WINDOW = display.set_mode(WINDOW_RES)
display.set_caption('Attack of the Vampire Pizzaas!')
#---------------------------------------------------------
#Set up the enemy image
pizza_img = image.load('vampire.png')
pizza_surf = Surface.convert_alpha(pizza_img)
VAMPIRE_PIZZA = transform.scale(pizza_surf, (HEIGHT, WIDTH))
#Create a subclass of Sprite called VampireSprite
class VampireSprite(sprite.Sprite):
#Set up enemy instances
def __init__(self):
super().__init__()
self.speed = 2
self.lane = randint(0, 4)
all_vampires.add(self)
self.image = VAMPIRE_PIZZA.copy()
y = 50 + self.lane * 100
self.rect = self.image.get_rect(center = (1100, y))
def update(self, game_window):
game_window.blit(self.image, (self.rect.x, self.rect.y))
#Set up the background image
background_img = image.load('restaurant.jpg')
background_surf = Surface.convert_alpha(background_img)
BACKGROUND = transform.scale(background_surf, WINDOW_RES)
#------------------------------------------------------
all_vampires = sprite.Group()
#Initialile and draw background grid
tile_color = WHITE
for row in range(6):
for column in range(11):
draw.rect(BACKGROUND, tile_color, (WIDTH * column,
HEIGHT * row, WIDTH, HEIGHT),1)
GAME_WINDOW.blit(BACKGROUND, (0, 0))
#---------------------------------------------------------
#Start The Main Game Loop
#Game Loop
game_running = True
while game_running:
#Check for Events
for event in pygame.event.get():
if event.type == QUIT:
game_running = False
#Spawn vampire pizza sprites
if randint(1, SPAWN_RATE) == 1:
VampireSprite()
#Update displays
for vampire in all_vampires:
vampire.update(GAME_WINDOW)
display.update()
#End of the Main game loop
#---------------------------------------------------------
#Clean up game
pygame.quit()
I don't know if I am doing something wrong or what
If my eyes are working fine(I guess) the code is valid
Make sure that the pygame version you are using is the one compatible with Python 3.7 and not older versions such as Python 2.
I had the same issue while working my way through this fun coding book. Comparing the code from the book's website and my own code I found the issue was incorrect spacing while defining the update() function within the VampireSprite class.
# Create a subclass of Sprite called VampireSprite
class VampireSprite(sprite.Sprite):
# Set up enemy instances
def __init__(self):
super().__init__()
self.Speed = 2
self. Lane = randint(0, 4)
all_vampires.add(self)
self.image = VAMPIRE_PIZZA.copy()
y = 50 + self.lane * 100
self.rect = self.image.get_rect(center = (1100, y))
def update(self, game_window):
game_window.blit(self.image, (self.rect.x, self.rect.y))
The correct code is:
# Create an enemy class
class VampireSprite(sprite.Sprite):
# This function creates an instance of the enemy
def __init__(self):
super().__init__()
self.speed = 2
self.lane = randint(0, 4)
all_vampires.add(self)
self.image = VAMPIRE_PIZZA.copy()
y = 50 + self.lane * 100
self.rect = self.image.get_rect(center=(1100, y))
# This function moves the enemies from right to left and destroys them after they've left the screen
def update(self, game_window):
game_window.blit(self.image, (self.rect.x, self.rect.y))
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.
I need to add an icon on the bottom of the Sprite worker and then change this icon randomly at each iteration. Please notice that the Sprite worker has 2 states: RUNNING and IDLE. In each of these states, the worker has a specific image. What I need now is to put an additional small image on the bottom of worker that will specify emotional state: HAPPY or ANGRY.
In the class Worker I create the array emo_images and also specify the variable emo_state. This variable denotes an emotional state of the worker: happy or angry. Each emotional state has its image stored in emotional_images.
In the code I randomly generate the variable state_indicator. If it's greater than 9, then the emotional state of the worker is changed to ANGRY. Otherwise, it's happy.
state_indicator = random.randint(0,10)
if state_indicator > 9:
print(state_indicator)
self.alert_notif_worker()
def alert_notif_worker(self):
self.emo_state = Worker.ANGRY
However I don't not know how to put the emotional image on the bottom of the worker image, because I don't want to replace the worker image (IDLE, RUNNING). I only need to add another image on the bottom and this additional image should move together with the worker.
If it's very difficult to do, then it would be also fine to have rectangles of two colours: red and green, instead of images, in order to indicate emotional states.
Complete code:
import sys
import pygame, random
from pygame.math import Vector2
from scipy.optimize import minimize
import math
WHITE = (255, 255, 255)
GREEN = (20, 255, 140)
GREY = (210, 210 ,210)
BLACK = (0, 0 ,0)
RED = (255, 0, 0)
PURPLE = (255, 0, 255)
SCREENWIDTH=1000
SCREENHEIGHT=578
# Create point vectors for the corners.
corners = [
Vector2(0, 0), Vector2(SCREENWIDTH, 0),
Vector2(SCREENWIDTH, SCREENHEIGHT), Vector2(0, SCREENHEIGHT)
]
ABS_PATH = "/Users/sb/Desktop/"
IMG_BACKGROUND = ABS_PATH + "images/background.jpg"
IMG_WORKER_RUNNING = ABS_PATH + "images/workers/worker_1.png"
IMG_WORKER_IDLE = ABS_PATH + "images/workers/worker_2.png"
IMG_WORKER_ACCIDENT = ABS_PATH + "images/workers/accident.png"
IMG_WORKER_HAPPY = ABS_PATH + "images/workers/happy.png"
IMG_WORKER_ANGRY = ABS_PATH + "images/workers/angry.png"
class Background(pygame.sprite.Sprite):
def __init__(self, image_file, location, *groups):
# we set a _layer attribute before adding this sprite to the sprite groups
# we want the background to be actually in the back
self._layer = -1
pygame.sprite.Sprite.__init__(self, groups)
# let's resize the background image now and only once
self.image = pygame.transform.scale(pygame.image.load(image_file).convert(), (SCREENWIDTH, SCREENHEIGHT))
self.rect = self.image.get_rect(topleft=location)
class Worker(pygame.sprite.Sprite):
RUNNING = 0
IDLE = 1
HAPPY = 0
ANGRY = 1
IMAGE_CACHE = {}
def __init__(self, idw, image_running, image_idle, image_happy, image_angry, location, *groups):
self.font = pygame.font.SysFont('Arial', 20)
# each state has it's own image
self.images = {
Worker.RUNNING: pygame.transform.scale(self.get_image(image_running), (45, 45)),
Worker.IDLE: pygame.transform.scale(self.get_image(image_idle), (20, 45))
}
self.emo_images = {
Worker.HAPPY: pygame.transform.scale(self.get_image(image_happy), (20, 20)),
Worker.ANGRY: pygame.transform.scale(self.get_image(image_angry), (20, 20))
}
# we set a _layer attribute before adding this sprite to the sprite groups
# we want the workers on top
self._layer = 0
pygame.sprite.Sprite.__init__(self, groups)
self.idw = idw
# let's keep track of the state and how long we are in this state already
self.state = Worker.IDLE
self.emo_state = Worker.HAPPY
self.ticks_in_state = 0
self.image = self.images[self.state]
self.rect = self.image.get_rect(topleft=location)
self.direction = pygame.math.Vector2(0, 0)
self.speed = random.randint(1, 3)
self.set_random_direction()
def set_random_direction(self):
# random new direction or standing still
vec = pygame.math.Vector2(random.randint(-100,100), random.randint(-100,100)) if random.randint(0, 5) > 1 else pygame.math.Vector2(0, 0)
# check the new vector and decide if we are running or not
length = vec.length()
speed = sum(abs(int(v)) for v in vec.normalize() * self.speed) if length > 0 else 0
if (length == 0 or speed == 0) and (self.state != Worker.ACCIDENT):
new_state = Worker.IDLE
self.direction = pygame.math.Vector2(0, 0)
else:
new_state = Worker.RUNNING
self.direction = vec.normalize()
self.ticks_in_state = 0
self.state = new_state
# use the right image for the current state
self.image = self.images[self.state]
#self.emo_image = self.emo_images[self.emo_state]
def update(self, screen):
self.ticks_in_state += 1
# the longer we are in a certain state, the more likely is we change direction
if random.randint(0, self.ticks_in_state) > 70:
self.set_random_direction()
# now let's multiply our direction with our speed and move the rect
vec = [int(v) for v in self.direction * self.speed]
self.rect.move_ip(*vec)
# if we're going outside the screen, change direction
if not screen.get_rect().contains(self.rect):
self.direction = self.direction * -1
send_alert = random.randint(0,10)
if send_alert > 9:
print(send_alert)
self.alert_notif_worker()
self.rect.clamp_ip(screen.get_rect())
def alert_notif_worker(self):
self.emo_state = Worker.ANGRY
def get_image(self,key):
if not key in Worker.IMAGE_CACHE:
Worker.IMAGE_CACHE[key] = pygame.image.load(key)
return Worker.IMAGE_CACHE[key]
pygame.init()
all_sprites = pygame.sprite.LayeredUpdates()
workers = pygame.sprite.Group()
screen = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT))
pygame.display.set_caption("TEST")
# create multiple workers
idw = 1
for pos in ((30,30), (50, 400), (200, 100), (700, 200)):
Worker(idw, IMG_WORKER_RUNNING, IMG_WORKER_IDLE,
IMG_WORKER_HAPPY, IMG_WORKER_ANGRY,
pos, all_sprites, workers)
idw+=1
# and the background
Background(IMG_BACKGROUND, [0,0], all_sprites)
carryOn = True
clock = pygame.time.Clock()
while carryOn:
for event in pygame.event.get():
if event.type==pygame.QUIT:
carryOn = False
pygame.display.quit()
pygame.quit()
quit()
all_sprites.update(screen)
all_sprites.draw(screen)
pygame.display.flip()
clock.tick(20)
I'd either use Micheal O'Dwyer's solution and blit the icon images in a separate for loop or create an Icon sprite class which can be added as an attribute to the Worker class. Then you can just update the position of the icon sprite in the update method and swap the image when the workers state gets changed.
You need a LayeredUpdates group, so that the icon appears above the worker sprite.
import pygame as pg
from pygame.math import Vector2
pg.init()
WORKER_IMG = pg.Surface((30, 50))
WORKER_IMG.fill(pg.Color('dodgerblue1'))
ICON_HAPPY = pg.Surface((12, 12))
ICON_HAPPY.fill(pg.Color('yellow'))
ICON_ANGRY = pg.Surface((10, 10))
ICON_ANGRY.fill(pg.Color('red'))
class Worker(pg.sprite.Sprite):
def __init__(self, pos, all_sprites):
super().__init__()
self._layer = 0
self.image = WORKER_IMG
self.rect = self.image.get_rect(center=pos)
self.state = 'happy'
self.emo_images = {'happy': ICON_HAPPY, 'angry': ICON_ANGRY}
# Create an Icon instance pass the image, the position
# and add it to the all_sprites group.
self.icon = Icon(self.emo_images[self.state], self.rect.bottomright)
self.icon.add(all_sprites)
def update(self):
# Update the position of the icon sprite.
self.icon.rect.topleft = self.rect.bottomright
def change_state(self):
"""Change the state from happy to angry and update the icon."""
self.state = 'happy' if self.state == 'angry' else 'angry'
# Swap the icon image.
self.icon.image = self.emo_images[self.state]
class Icon(pg.sprite.Sprite):
def __init__(self, image, pos):
super().__init__()
self._layer = 1
self.image = image
self.rect = self.image.get_rect(topleft=pos)
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
all_sprites = pg.sprite.LayeredUpdates()
worker = Worker((50, 80), all_sprites)
all_sprites.add(worker)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.MOUSEMOTION:
worker.rect.center = event.pos
elif event.type == pg.KEYDOWN:
worker.change_state()
all_sprites.update()
screen.fill((30, 30, 30))
all_sprites.draw(screen)
pg.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()
pg.quit()
This could be accomplished fairly easily, by just blitting each Worker's emotion image, at a certain location, in relation to the Worker's rect.x, and rect.y co-ordinates.
Unfortunately, I cannot test the code example below, because your code uses lots of images which I don't have. One problem I do see which you will need to fix before trying to implement this code, is that the Background object you are initializing is being added to all_sprites, so you may consider changing all_sprites to all_workers, and maybe add Background to a different group.
You will also need to initialize offset_x, and offset_y to values which work for you. The values used below will just move the image to the bottom left corner of the worker.
Here is the example code:
for worker in all_workers:
offset_x = 0
offset_y = worker.rect.height
screen.blit(worker.emo_images[worker.emo_state], (worker.rect.x+offset_x, worker.rect.y+offset_y))
I hope this answer helps you! Please let me know if this works for you, and if you have any further questions, feel free to leave a comment below.