Related
This question already has answers here:
How can I play an mp3 with pygame?
(7 answers)
how to play wav file in python?
(5 answers)
Closed 5 months ago.
I'm not so sure why but my game screen window/pygame window sometimes freezes or is not responding because of the added SFX or music in my game. If I remove the SFX or music the game runs smoothly. Hoping that anyone can help me or at least tell me why it keeps freezing. Thanks very much.
The specific code for music and sounds in class Game can be found in load_data method, run method, and update method.
This is where you can find my music, target SFX when hit, bonus SFX when hit.
Below is from my main.py module
import pygame as py
import sys
from os import kill, path
from Setting import *
from Sprite import *
import random
#BUGs (BULLET DISAPPEAR WHEN PAUSED [PROBABLY FROM get_tick()])
#add highscore later in end screen
class Game():
def __init__(self):
py.mixer.pre_init(44100, -16, 1, 512)
py.init()
self.screen = py.display.set_mode((Width, Height),py.RESIZABLE)
py.display.set_caption("Hit Me!")
self.clock = py.time.Clock()
self.load_data()
self.score_value = 0
self.time_value = 90
self.paused = False
self.channel1 = py.mixer.Channel(1)
self.channel2 = py.mixer.Channel(2)
self.channel3 = py.mixer.Channel(3)
def draw_data(self, text, font_name, size, color, x, y,type=0, align="center", value=""):
font = py.font.Font(font_name, size)
if type == 0:
text_surface = font.render(text + str(value), True, color)
if self.paused == True:
text_surface.set_alpha(125)
else:
text_surface = font.render(text, True, color)
text_rect = text_surface.get_rect()
if align == "nw":
text_rect.topleft = (x, y)
if align == "ne":
text_rect.topright = (x, y)
if align == "sw":
text_rect.bottomleft = (x, y)
if align == "se":
text_rect.bottomright = (x, y)
if align == "n":
text_rect.midtop = (x, y)
if align == "s":
text_rect.midbottom = (x, y)
if align == "e":
text_rect.midright = (x, y)
if align == "w":
text_rect.midleft = (x, y)
if align == "center":
text_rect.center = (x, y)
self.screen.blit(text_surface, text_rect)
def load_data(self):
game_folder = path.dirname(__file__)
self.map_data = []
with open(path.join(game_folder, 'map.txt'), 'rt') as f:
for line in f:
self.map_data.append(line)
image_folder = path.join(game_folder, 'images')
sfx_folder = path.join(game_folder, 'sfx_music')
self.dim_screen = py.Surface(self.screen.get_size()).convert_alpha()
self.dim_screen.fill((0, 0, 0, 150))
self.player_img = py.image.load(
path.join(image_folder, character)).convert_alpha()
self.bullet_img = py.image.load(
path.join(image_folder, bullets)).convert_alpha()
self.target_img = py.image.load(
path.join(image_folder, targets)).convert_alpha()
self.target_img = py.transform.scale(
self.target_img, (TileSize + 20, TileSize + 20))
self.bonus_img = py.image.load(
path.join(image_folder, time)).convert_alpha()
self.bonus_img = py.transform.scale(
self.bonus_img, (TileSize + 30, TileSize + 30))
#sound loading
self.music = py.mixer.Sound(path.join(sfx_folder,background_music))
self.music.set_volume(0.25)
self.gun_sfx = py.mixer.Sound(path.join(sfx_folder,gun_sound))
self.bonus_sfx = py.mixer.Sound(path.join(sfx_folder,bonus_sound))
self.target_sfx = py.mixer.Sound(path.join(sfx_folder,target_sound))
self.gun_sfx.set_volume(0.4)
self.target_sfx.set_volume(0.5)
def new(self):
self.sprites = py.sprite.Group()
self.walls = py.sprite.Group()
self.grass = py.sprite.Group()
self.targets = py.sprite.Group()
self.bullets = py.sprite.Group()
self.bonuss = py.sprite.Group()
for row, tiles in enumerate(self.map_data):
for col, tile in enumerate(tiles):
if tile == 'P':
self.player = Player(self, col, row)
py.time.set_timer(py.USEREVENT, 1000)
def newTarget(self):
self.targets = py.sprite.Group()
for row, tiles in enumerate(self.map_data):
for col, tile in enumerate(tiles):
if tile == '.':
target_chances = random.randint(0, 100)
if target_chances <= 10: # 10% chance
tile = 'T'
if tile == 'M':
bonus_chances = random.randint(0,100)
if bonus_chances <= 5: # 5% chance bonus time (add 2 or 3 seconds to timer) [specified area]
tile = 'B'
if tile == 'T':
Target(self, col, row)
if tile == 'B':
Bonus(self,col,row)
def run(self):
self.playing = True
self.music_played = False
if not self.music_played:
self.channel3.play(self.music, loops = -1)
self.music_played = True
while self.playing:
self.dt = self.clock.tick(FPS) / 1000
self.events()
if not self.paused:
self.update()
self.channel3.unpause()
if self.paused:
self.channel3.pause()
self.draw()
def quit(self):
py.quit()
sys.exit()
def update(self):
self.sprites.update()
hits = py.sprite.groupcollide(
self.targets, self.bullets, True, True, py.sprite.collide_mask)
bonus_hits = py.sprite.groupcollide(
self.bonuss, self.bullets, True, True, py.sprite.collide_mask)
#for bullets & targets
for hit in hits:
hit.kill()
self.score_value += 1
self.target_sfx_played = False
if not self.target_sfx_played:
self.channel1.play(self.target_sfx)
self.target_sfx_played = True
#for bullets & bonus
for hit in bonus_hits:
hit.kill()
self.time_value += 3
self.bonus_sfx_played = False
if not self.bonus_sfx_played:
self.channel2.play(self.bonus_sfx)
self.bonus_sfx_played = True
#if there is no target in screen, it will create a new set
if len(self.targets) == 0:
self.newTarget()
def drawGrid(self):
for x in range(0, Width, TileSize):
py.draw.line(self.screen, Black, (x, 0), (x, Height))
for y in range(0, Height, TileSize):
py.draw.line(self.screen, Black, (0, y), (Width, y))
def draw(self):
py.display.set_caption("{:.2f}".format(self.clock.get_fps()))
self.screen.fill(BGcolor)
self.drawGrid()
self.sprites.draw(self.screen)
py.draw.rect(self.screen, Green, self.player.rect, 1)
if self.paused:
self.screen.blit(self.dim_screen, (0, 0))
self.draw_data("Paused",'freesansbold.ttf', 105, White, Width / 2, Height / 2,1)
self.draw_data("Score: ",'freesansbold.ttf', 40, White, 100, 35,value = self.score_value)
self.draw_data("Time: ",'freesansbold.ttf', 40, White, 450, 35 ,value = self.time_value)
self.draw_data("HighScore: ",'freesansbold.ttf', 40, White, Width - 225, 35) # add data in end game
py.display.flip()
def events(self):
for event in py.event.get():
if event.type == py.QUIT:
self.quit()
if event.type == py.KEYDOWN:
if event.key == py.K_ESCAPE:
self.paused = not self.paused
if event.type == py.MOUSEBUTTONDOWN:
if event.button == 1:
if not self.paused:
self.player.shoot()
if event.type == py.USEREVENT:
if not self.paused:
self.time_value -=1
def show_start_screen(self):
pass
def show_go_screen(self):
pass
# main loop
g = Game()
g.show_start_screen()
while True:
g.new()
g.run()
g.show_go_screen()
In the Sprite.py module, this is where you can find the SFX for gun shooting whenever I press/hold the left mouse button.
Below is from my Sprite.py module
import pygame as py
from Setting import *
from pygame.locals import *
vec = py.math.Vector2
class Player(py.sprite.Sprite):
def __init__(self, game,x,y):
self.groups = game.sprites
py.sprite.Sprite.__init__(self, self.groups)
self.game = game
self.orig_image = game.player_img
self.rect = self.orig_image.get_rect()
self.pos = vec(x,y) * TileSize
self.rect.center = self.pos
self.last_shot = 0
self.gun_sfx = game.gun_sfx
self.channel4 = py.mixer.Channel(4)
def shoot(self):
if py.mouse.get_pressed()[0]:
now = py.time.get_ticks()
if now - self.last_shot > bullet_rate:
self.gun_sfx_played = False
if not self.gun_sfx_played:
self.channel4.play(self.gun_sfx)
self.gun_sfx_played = True
self.last_shot = now
dir = py.mouse.get_pos() - self.pos
radius, angle = dir.as_polar()
direction = vec(1, 0).rotate(angle - 3)
pos = self.pos + Barrel_offset.rotate(angle)
Bullet(self.game, pos, direction, -angle)
def rotate(self):
dir = py.mouse.get_pos() - self.pos
radius, angle = dir.as_polar()
self.image = py.transform.rotate(self.orig_image, -angle)
self.rect = self.image.get_rect(center=self.rect.center)
def update(self):
self.shoot()
self.rotate()
Below is from my Setting.py
# sounds
gun_sound = 'gun_sfx.mp3'
background_music = 'bg_music.mp3'
target_sound = 'target_sfx_broken.mp3'
bonus_sound = 'hourglass_sfx.mp3'
I want to draw a triangle from a class, so I call the function
pygame.draw.polygon()
Now, the problem is that I need to pass the points in a manner that will allow me to calculate the centre of the triangle.
I was trying to pass the tuples one by one in this way
self.first_point = (int, int)
self.second_point = (int, int)
self.third_point = (int, int)
so that I can then access the single tuple values.
Then pass the three points like this
self.position = [self.first_point, self.second_point, self.third_point]
But for some reason, it doesn't work.
This is the error I get
File "C:/Users/oricc/PycharmProjects/designAChessboardChallange/display.py", line 178, in <module>
white_archer_3 = Archer(white, [(100, 100), (200, 200), (300, 300)])
[(100, 100), (200, 200), (300, 300)]
File "C:/Users/oricc/PycharmProjects/designAChessboardChallange/display.py", line 132, in __init__
self.triangle = pygame.draw.polygon(game_window, colour, self.position)
TypeError: points must be number pairs
By number of pairs, the Pygame documentation gives as an example
e.g. [(x1, y1), (x2, y2), (x3, y3)]
In fact, when I print the position I pass I get, as you can see from the error above
[(100, 100), (200, 200), (300, 300)]
Anyone can help with this?
Is there another manner to calculate the centre without accessing the xs and ys like that?
Full code here
import pygame
import sys
from coordinator import coordinator
# set up the display
pygame.init()
window_size = (800, 800)
game_window = pygame.display.set_mode(size=window_size)
pygame.display.set_caption('My Game')
# defines classes and related methods
class WhiteSquare:
def __init__(self):
self.height = int(window_size[0] / 8)
self.width = int(window_size[1] / 8)
self.white_square = pygame.Surface((self.height, self.width))
self.white_square.fill((255, 255, 255))
class BlackSquare:
def __init__(self):
self.height = int(window_size[0] / 8)
self.width = int(window_size[1] / 8)
self.black_square = pygame.Surface((self.height, self.width))
self.black_square.fill((0, 0, 0))
class ChessBoard:
def __init__(self):
self.ws = ws
self.bs = bs
self.white_columns = white_columns
self.black_columns = black_columns
def draw(self):
for w_columns in self.white_columns:
game_window.blit(self.ws.white_square, w_columns)
for b_columns in self.black_columns:
game_window.blit(self.bs.black_square, b_columns)
# declare letters and numbers
letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
numbers = ['1', '2', '3', '4', '5', '6', '7', '8']
# create coordinates
coordinates = []
for item_letter in letters:
letter = item_letter
for item_number in numbers:
number = item_number
coordinates.append(letter + number)
# create coordinates values components
x_values = []
for number in range(0, 800, 100):
x = number
x_values.append(x)
y_values = []
for number in range(0, 800, 100):
y = number
y_values.append(y)
# create coordinate values
coordinate_values = []
for x in x_values:
for y in y_values:
coordinate_values.append((x, y))
# assign values to coordinates
squares_coordinates = dict(zip(coordinates, coordinate_values))
# Background for units
class CircleSurface:
def __init__(self):
self.circle_surface = pygame.Surface((100, 100), flags=pygame.SRCALPHA)
pygame.draw.circle(self.circle_surface, (255, 0, 0), (50, 50), 45)
# define colours
black = (0, 0, 0)
white = (255, 255, 255)
gold = (153, 153, 0)
class Unit:
def __init__(self, colour, position):
# define Unit colour
self.colour = colour
# define Unit position
self.position = position
class Knight(Unit):
def __init__(self, colour, position):
# draw circle, inline, and outline
super().__init__(colour, position)
self.center_x = position[0]
self.center_y = position[1]
self.colour = colour
self.position = position
circle_radius = 40
self.circle = pygame.draw.circle(game_window, colour, self.position, circle_radius)
self.circle_outline = pygame.draw.circle(game_window, gold, self.position, circle_radius, 5)
self.circle_inline = pygame.draw.circle(game_window, gold, self.position, (circle_radius - 10), 5)
# draw letter
pygame.font.init()
my_font_size = 50
my_font = pygame.font.SysFont('Time New Roman', my_font_size)
text_surface = my_font.render('K', 1, gold)
center_text = text_surface.get_rect(center=(self.center_x, self.center_y))
game_window.blit(text_surface, center_text)
class Archer(Unit):
def __init__(self, colour, position):
super().__init__(colour, position)
self.first_point = (int, int)
self.second_point = (int, int)
self.third_point = (int, int)
self.position = [self.first_point, self.second_point, self.third_point]
print(position)
self.triangle = pygame.draw.polygon(game_window, colour, self.position)
self.triangle_outline = pygame.draw.polygon(game_window, gold, self.position, 5)
self.triangle_inline = pygame.draw.polygon(game_window, gold, self.position, 5)
# draw letter
# pygame.font.init()
# my_font_size = 50
# my_font = pygame.font.SysFont('Time New Roman', my_font_size)
# text_surface = my_font.render('A', 1, gold)
# center_text = text_surface.get_rect(center=(self.center_x, self.center_y))
# game_window.blit(text_surface, center_text)
# Sets and gets the coordinates for black and white squares
coordinator = coordinator()
black_columns = coordinator[2] + coordinator[3]
white_columns = coordinator[0] + coordinator[1]
# Creates needed objects
ws = WhiteSquare()
bs = BlackSquare()
cb = ChessBoard()
cs = CircleSurface()
# Event loop (outer)
while 1:
# Event loop (inner)
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
# Draws the chessboard
cb.draw()
# Draws white pieces in their initial position
white_knight_1 = Knight(white, (150, 650))
white_knight_2 = Knight(white, (650, 650))
white_archer_3 = Archer(white, [(100, 100), (200, 200), (300, 300)])
pygame.display.update()
Thank you
OK, I managed to make it work.
You guys are both right, I should know by now that I can't pass placeholders, so I figured out the solution to my problem as follow:
class Archer(Unit):
def __init__(self, colour, first_point, second_point, third_point):
self.first_point = first_point
self.second_point = second_point
self.third_point = third_point
position = [self.first_point, self.second_point, self.third_point]
super().__init__(colour, position)
print(self.position)
self.triangle = pygame.draw.polygon(game_window, colour, self.position)
self.triangle_outline = pygame.draw.polygon(game_window, gold, self.position, 5)
self.triangle_inline = pygame.draw.polygon(game_window, gold, self.position, 5)
Basically I have to declare the three points self variables, as well as position before the super function so that I can then pass them as 'position' to the parent class initializer. This has been really useful!!
Thanks both
you could also do this: self.first_point = (int(0), int(0)) as int is not a placeholder but to declare that a variable is an integer str('0') will print '0' you could also input this
zero = 0
str(zero) #'0'
int(zero) #0
and you dont need to put rgb tupels because you can store them in a variable like this
black = (0, 0, 0)
Display = pygame.display.set_mode((800, 600))
Display.fill(black)
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.
I'm trying to create a game where the action is shown in a little box within the main screen object, freeing up the surrounding space for text and menus and what-not. Since the map is larger than the allotted window, I coded a basic "camera" that follows the player around. It mostly works, but I'm having trouble "trimming off" the area outside of this window.
Here's the relevant bits of code (EDITED to provide Working Example):
import pygame, os, sys
from pygame.locals import *
pygame.init()
RIGHT = 'RIGHT'
LEFT = 'LEFT'
UP = 'UP'
DOWN = 'DOWN'
class Camera():
def __init__(self, screen, x_ratio = 1, y_ratio = 1, x_offset = 0, y_offset = 0):
self.screen = screen.copy()
self.rec = self.screen.get_rect()
self.rec.width *= x_ratio
self.rec.height *= y_ratio
self.x_offset = x_offset
self.y_offset = y_offset
def get_pos(self):
return (self.x_offset - self.rec.x, self.y_offset - self.rec.y)
def get_window(self):
w = pygame.Rect(self.rec)
w.topleft = (0 - self.rec.x, 0 - self.rec.y)
return w
def move(self, x, y):
"""Move camera into new position"""
self.rec.x = x
self.rec.y = y
def track(self, obj):
while obj.rec.left < self.rec.left:
self.rec.x -= 1
while obj.rec.right > self.rec.right:
self.rec.x += 1
while obj.rec.top < self.rec.top:
self.rec.y -= 1
while obj.rec.bottom > self.rec.bottom:
self.rec.y += 1
class Map:
def __init__(self, width, height):
self.width = width
self.height = height
self.rec = pygame.Rect(0,0,self.width,self.height)
def draw(self, screen):
pygame.draw.rect(screen, (200,200,200), self.rec)
class Obj:
def __init__(self, char, x = 0, y = 0, width = 0, height = 0):
self.width = width
self.height = height
self.rec = pygame.Rect(x, y, width, height)
self.cur_map = None
self.timers = {}
#Dummying in chars for sprites
self.char = char
self.x_dir = 1
self.y_dir = 1
self.speed = 1
self.moving = False
def move(self):
if self.x_dir != 0 or self.y_dir != 0:
new_x = self.rec.x + (self.x_dir*self.speed)
new_y = self.rec.y + (self.y_dir*self.speed)
new_rec = pygame.Rect(new_x, new_y, self.width, self.height)
#Keep movement within bounds of map
while new_rec.left < self.cur_map.rec.left:
new_rec.x += 1
while new_rec.right > self.cur_map.rec.right:
new_rec.x -= 1
while new_rec.top < self.cur_map.rec.top:
new_rec.y += 1
while new_rec.bottom > self.cur_map.rec.bottom:
new_rec.y -= 1
self.rec = new_rec
def set_dir(self, d):
self.x_dir = 0
self.y_dir = 0
if d == LEFT:
self.x_dir = -1
elif d == RIGHT:
self.x_dir = 1
elif d == UP:
self.y_dir = -1
elif d == DOWN:
self.y_dir = 1
def set_moving(self, val = True):
self.moving = val
class Game:
def __init__(self):
self.screen_size = (800, 600)
self.screen = pygame.display.set_mode(self.screen_size)
self.map_screen = self.screen.copy()
self.title = 'RPG'
pygame.display.set_caption(self.title)
self.camera = Camera(self.screen, 0.75, 0.75)#, 10, 75)
self.fps = 80
self.clock = pygame.time.Clock()
self.debug = False
self.bg_color = (255,255,255)
self.text_size = 18
self.text_font = 'Arial'
self.text_style = pygame.font.SysFont(self.text_font, self.text_size)
self.key_binds = {LEFT : [K_LEFT, K_a], RIGHT : [K_RIGHT, K_d], UP : [K_UP, K_w], DOWN : [K_DOWN, K_s],
'interact' : [K_RETURN, K_z], 'inventory' : [K_i, K_SPACE], 'quit' : [K_ESCAPE]}
self.player = Obj('p', 0, 0, 10, self.text_size)
def draw(self, obj):
char = obj.char
self.draw_text(char, obj.rec.x, obj.rec.y, screen = self.map_screen)
def draw_text(self, text, x, y, color = (0,0,0), screen = None):
textobj = self.text_style.render(text, 1, color)
textrect = textobj.get_rect()
textrect.x = x
textrect.y = y
if screen == None:
"""Use default screen"""
self.screen.blit(textobj, textrect)
else:
screen.blit(textobj, textrect)
def play(self):
done = False
cur_map = Map(800, 800)
self.map_screen = pygame.Surface((cur_map.width, cur_map.height))
self.map_screen.fill(self.bg_color)
bg = pygame.Surface((cur_map.width, cur_map.height))
cur_map.draw(bg)
self.player.cur_map = cur_map
while not done:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key in self.key_binds[LEFT]:
self.player.set_dir(LEFT)
self.player.set_moving()
elif event.key in self.key_binds[RIGHT]:
self.player.set_dir(RIGHT)
self.player.set_moving()
elif event.key in self.key_binds[UP]:
self.player.set_dir(UP)
self.player.set_moving()
elif event.key in self.key_binds[DOWN]:
self.player.set_dir(DOWN)
self.player.set_moving()
elif event.type == KEYUP:
self.player.set_moving(False)
if self.player.moving:
self.player.move()
self.camera.track(self.player)
self.clock.tick()
self.screen.fill(self.bg_color)
self.map_screen.blit(bg, (0,0))
self.draw(self.player)
pygame.draw.rect(self.map_screen, (0,0,0), self.camera.rec, 1)
#self.screen.blit(self.map_screen, (0,0), [0 - self.camera.rec.x, 0 - self.camera.rec.y, self.camera.rec.width, self.camera.rec.height])
self.screen.blit(self.map_screen, self.camera.get_pos(), self.camera.get_window())
pygame.display.flip()
game = Game()
game.play()
Moving the player past past the bounds of the camera's window causes the window to roll up completely and disappear. I tried adjusting the blitting coordinates, as advised earlier, but it seems to only change the direction in which the window rolls up.
From your updated code, the blitting coordinates for self.screen.blit(...) are still changing: self.camera.get_window() changes value because rec.x and rec.y are values referring to the player position within the map. Hence you should define a constant minimap coordinate, this should be the same as the camera offset.
self.screen.blit(self.map_screen, (self.camera.x_offset,self.camera.y_offset), (*self.camera.get_pos(), self.camera.rec.width, self.camera.rec.height))
Change the Camera().get_pos() to:
def get_pos(self):
return (self.rec.x, self.rec.y)
I believe I only changed the self.screen.blit(...) and stopped using or rewrote your Camera functions as you're confusing yourself with all the rec variables.
To illustrate it working amend the Map().draw(screen) to:
def draw(self, screen):
pygame.draw.rect(screen, (200,200,200), self.rec)
pygame.draw.circle(screen, (255, 255, 255), (50, 50), 20, 2)
One tip as well don't draw the entire map at each loop, just the part that will be visible.
So, I've been coding in Python for a few weeks and I felt like trying to make a game. I've gotten to a point where I have no clue what is wrong with my code except for the fact that when I try to draw a textured polygon the texture stretches across the display and only reveals on the cubes. It creates a really cool effect but it isn't what I'm looking for. The goal is to render the texture onto the face of the cube like in Minecraft.
import pygame, sys, os, math
from pygame import gfxdraw
def rotate2d(pos,rad) : x,y=pos; s,c = math.sin(rad), math.cos(rad); return x*c-y*s, y*c+x*s
class Cam:
def __init__(self,pos=(0,0,0), rot=(0,0)):
self.pos = list(pos)
self.rot = list(rot)
def events (self, event):
if event.type == pygame.MOUSEMOTION:
x,y = event.rel; x/=200; y/=200
self.rot[0]+=y; self.rot[1]+=x
def update(self,dt,key):
s = dt*10
if key[pygame.K_c]: self.pos[1]+=s
if key[pygame.K_SPACE]: self.pos[1]-=s
x,y = s*math.sin(self.rot[1]),s*math.cos(self.rot[1])
if self.rot[0] <= -1.58:
self.rot[0] = -1.58
if self.rot[0] >= 1.58:
self.rot[0] = 1.58
if key[pygame.K_w]: self.pos[0]+=x; self.pos[2]+=y
if key[pygame.K_s]: self.pos[0]-=x; self.pos[2]-=y
if key[pygame.K_a]: self.pos[0]-=y; self.pos[2]+=x
if key[pygame.K_d]: self.pos[0]+=y; self.pos[2]-=x
if key[pygame.K_ESCAPE]: pygame.quit(); sys.exit() #Quits Game#
pygame.init()
w,h = 800,600; cx,cy = w//2, h//2; fov = min(w,h)
os.environ['SDL_VIDEO_CENTERED'] = '1'
pygame.display.set_caption('PyCraft')
screen = pygame.display.set_mode((w,h))
clock = pygame.time.Clock()
## Textures ##
dirt_texture = pygame.image.load("textures/dirt.png").convert()
stone_texture = pygame.image.load("textures/stone.png").convert()
bedrock_texture = pygame.image.load("textures/bedrock.png").convert()
class Dirt:
vertices = (-1,-1,-1),(1,-1,-1),(1,1,-1),(-1,1,-1),(-1,-1,1),(1,-1,1),(1,1,1),(-1,1,1)
edges = (0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),(0,4),(1,5),(2,6),(3,7)
faces = (0,1,2,3),(4,5,6,7),(0,1,5,4),(2,3,7,6),(0,3,7,4),(1,2,6,5)
colours = (225,10,0),(255,128,0),(10,250,250),(128,128,128),(0,0,255),(0,255,0)
texture = dirt_texture
def __init__(self,pos=(0,0,0)):
x,y,z = pos
self.verts = [(x+X/2,y+Y/2,z+Z/2) for X,Y,Z in self.vertices]
class Stone:
vertices = (-1,-1,-1),(1,-1,-1),(1,1,-1),(-1,1,-1),(-1,-1,1),(1,-1,1),(1,1,1),(-1,1,1)
edges = (0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),(0,4),(1,5),(2,6),(3,7)
faces = (0,1,2,3),(4,5,6,7),(0,1,5,4),(2,3,7,6),(0,3,7,4),(1,2,6,5)
colours = (0,255,0),(255,128,0),(0,0,250),(128,133,128),(0,0,200),(200,0,255)
texture = stone_texture
def __init__(self,pos=(0,0,0)):
x,y,z = pos
self.verts = [(x+X/2,y+Y/2,z+Z/2) for X,Y,Z in self.vertices]
def TextUI(msg,colour,pos):
screen_text = font.render(msg, True, colour)
screen.blit(screen_text, (pos))
def Draw_Crosshair(colour, width):
pygame.draw.line(screen, colour, (cursorpos1, cursorpos2), (cursorpos3,cursorpos4), width)
pygame.draw.line(screen, colour, (cursorpos5, cursorpos6), (cursorpos7,cursorpos8), width)
cam = Cam((0,0,-5))
pygame.event.get(); pygame.mouse.get_rel()
pygame.mouse.set_visible(0); pygame.event.set_grab(1)
font = pygame.font.SysFont("calibri", 25)
cursorpos1, cursorpos2, cursorpos3, cursorpos4 = w/2, h/2+10, w/2, h/2-10
cursorpos5, cursorpos6, cursorpos7, cursorpos8 = w/2+10, h/2, w/2-10, h/2
cubes = [Dirt((0,0,0)),Stone((2,0,0)),Stone((-2,0,0)),Dirt((0,2,0)),Stone((2,2,0)),Dirt((-2,2,0))]
##cubes = [Dirt((1,0,0)), Dirt((2,0,0)),Dirt((3,0,0)),Dirt((4,0,0)),Dirt((5,0,0)),Dirt((5,1,0)),Dirt((6,1,0)),]
while True:
dt = clock.tick()/1000
for event in pygame.event.get():
if event.type == pygame.QUIT: pygame.quit(); sys.exit()
cam.events(event)
screen.fill((100,100,100))
face_list = []; face_colour = []; depth = [];
for obj in cubes:
vert_list = []; screen_coords = []
for x,y,z in obj.verts:
x-=cam.pos[0]; y-=cam.pos[1]; z-=cam.pos[2]
x,z = rotate2d((x,z), cam.rot[1])
y,z = rotate2d((y,z), cam.rot[0])
vert_list += [(x,y,z)]
f = fov/z
x,y = x*f,y*f
screen_coords+=[(cx+int(x),cy+int(y))]
for f in range (len(obj.faces)):
face = obj.faces[f]
on_screen = False
for i in face:
x,y = screen_coords[i]
if vert_list[i][2]>0 and x>0 and x<w and y>0 and y<h: on_screen = True ; break
if on_screen:
coords = [screen_coords[i] for i in face]
face_list += [coords]
face_colour += [obj.colours[f]]
depth += [sum(sum(vert_list[j][i] for j in face) **2 for i in range (3))]
order = sorted(range(len(face_list)), key=lambda i:depth[i], reverse=1)
for i in order:
try: pygame.gfxdraw.textured_polygon(screen, face_list[i], obj.texture, 0, 0); pygame.gfxdraw.aapolygon(screen, face_list[i], (0,0,0))
except: pass
## OLD CODE - pygame.draw.polygon(screen, face_colour[i], face_list[i]) ##
## NEW CODE - pygame.gfxdraw.textured_polygon(screen, face_list[i], obj.texture, 0, 0) ##
## TEST CODE - pygame.gfxdraw.filled_polygon(screen, face_list[i], face_colour[i]) ##
Draw_Crosshair((255,255,255),2)
TextUI("Press ESCAPE to exit", (255,255,255), (20,20))
TextUI("Movement Controls: W, A, S, D, SPACE and C", (255,255,255), (20,60))
pygame.display.update()
key = pygame.key.get_pressed()
cam.update(dt, key)
That is beautiful code which works exactly as expected, the 2-dimensional polygon is filled with flat texture. To move beyond 2-dimensions to render solid objects, still from pygame, you need to move to OpenGL.