for this code im trying to make a class that can take a png with one immage, or a png with many frames of an animation and put it on screen. most of the functionality works but there are a few bugs and i have no idea what is causing them.
in the class it initializes the information.
it creates a clear surface, puts the entire png on it to use later.
then it creates a clear surface the size of one frame, which is used in the rendering function to get the right area of the png when its needed.
then each frame of the pygame window it loads the images using a class function. right now it does not run the function that iterates through animation frames it only renders the one immage.
import pygame
from sys import exit
pygame.init()
screenwidth = 1000
screenheight = 500
fps = 32
screen = pygame.display.set_mode((screenwidth,screenheight))
screen.convert_alpha()
clock = pygame.time.Clock()
class sprite():
def __init__(self,
position = (0,0),#top left
frames = 1,
finalsize = (100,100),
pngname="missing.png",
startingframe = 1):
self.position = position
self.frames = frames
self.finalsize = finalsize
self.pngname = pngname
self.currentframe = startingframe
self.immage = pygame.image.load(self.pngname)#this loads a surface
self.immage.convert_alpha()
#dimentions defined, self.framesurface
#transparency
totalwidth = self.immage.get_width()
self.frameheight = self.immage.get_height()
self.framewidth = totalwidth/self.frames
self.framesurface = pygame.Surface((self.framewidth,self.frameheight))#makes surface with frame dimentions
self.framesurface.convert_alpha()
self.framesurface.fill((0,0,0,255))
clear_surface = pygame.Surface((totalwidth,self.frameheight))
clear_surface.convert_alpha()
clear_surface.fill((100,100,100,255))#transparent surface made
#clear_surface.set_alpha(0) this turns the entire immage transparent
clear_surface.blit(self.immage,(0,0),(0,0,finalsize[0],finalsize[1]))
self.immage = clear_surface
#self.immage
#self.framesurface
def frame_render(self):
self.framesurface.blit(self.immage,
(0,0),
(0, self.framewidth*self.currentframe, self.framewidth, self.frameheight))
#blits one frame
#self.immage = self.framesurface
self.framesurface = pygame.transform.smoothscale(self.framesurface,self.finalsize)#scales immage to finalsize
self.framesurface.fill((255,255,255,255))#this makes it work????
screen.blit(self.immage,self.position,(0,0,self.finalsize[0],self.finalsize[1]))#adds it to screen
def frame_incriment(self,speed= 1):#this function is not used as of this moment
self.currentframe = int((((animationcounter%fps)+1)/fps)*self.frame*speed)
if self.currentframe == 0:
self.currentframe = self.frame
test = sprite()
apple = sprite(pngname = "apple small.png",position = (150,150))
banana = sprite()
activesprites = [apple,banana]
animationcounter = 0
animationloopreset_frames = fps*5 #every 5 seconds the animation counter resets
while True:
clock.tick(fps)
animationcounter = animationcounter + 1
if animationcounter > animationloopreset_frames:
animationcounter = 1
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()#shuts pygame
exit()#ends code
#pygame events here, buttons change activesprites
screen.fill((255,255,255))#clears screen, place things after
#for item in activesprites:
#print("drawing sprite:" + item)
test.frame_render()
apple.frame_render()
pygame.display.update()
but what I'm getting is
which shows a grey surface behind the apple. it should be transparent because of the line "clear_surface.fill((100,100,100,255))" in the init function.
and the apple is cropped to 100x100, this happens on the line "self.framesurface = pygame.transform.smoothscale(self.framesurface,self.finalsize)" because the scale function is making the surface 100x100 and not effecting the image for some reason even though that's the entire point of a scale function.
ive looked through the documentation of pygame and alternate ways of transparency seem a lot more complex or remove a color entirely which i don't want in this code, and i havent seen a scale alternative fixes my issue.
Your code
self.framesurface = pygame.Surface((self.framewidth,self.frameheight))
self.framesurface.convert_alpha()
creates a surface in RGB format and converts it to a surface in RGBA format. Therefore the surface is completely opaque and the value of all pixels is (0, 0, 0, 1).
You have to create a surface with RGBA format using the [SRCALPHA]:
self.framesurface = pygame.Surface((self.framewidth, self.frameheight), pygame.SRCALPHA)
blit the complete image on clear_surface not just a part of it.
clear_surface = pygame.Surface((totalwidth, self.frameheight), pygame.SRCALPHA)
clear_surface.blit(self.immage, (0, 0), (0, 0, totalwidth, self.frameheight))
In this case the surface is completely transparent and the initial value of the pixels is (0, 0, 0, 0):
Furthermore you need to blit the scaled surface on surface with the size of the original image, before you can scale it. You have to create a surface with the size of the original image in every frame. After that you can scale that surface:
def frame_render(self):
self.framesurface = pygame.Surface((self.framewidth, self.frameheight), pygame.SRCALPHA)
self.framesurface.blit(
self.immage, (0, 0),
(0, self.framewidth*(self.currentframe-1), self.framewidth, self.frameheight))
self.framesurface = pygame.transform.smoothscale(self.framesurface, self.finalsize)
screen.blit(self.framesurface, self.position)
Complete and working example:
import pygame
from sys import exit
pygame.init()
screenwidth = 1000
screenheight = 500
fps = 32
screen = pygame.display.set_mode((screenwidth,screenheight))
screen.convert_alpha()
clock = pygame.time.Clock()
class sprite():
def __init__(self,
position = (0,0),#top left
frames = 1,
finalsize = (100,100),
pngname="missing.png",
startingframe = 1):
self.position = position
self.frames = frames
self.finalsize = finalsize
self.pngname = pngname
self.currentframe = startingframe
self.immage = pygame.image.load(self.pngname)
self.immage.convert_alpha()
totalwidth = self.immage.get_width()
self.frameheight = self.immage.get_height()
self.framewidth = totalwidth/self.frames
self.framesurface = pygame.Surface((self.framewidth, self.frameheight), pygame.SRCALPHA)
clear_surface = pygame.Surface((totalwidth, self.frameheight), pygame.SRCALPHA)
clear_surface.blit(self.immage, (0, 0), (0, 0, totalwidth, self.frameheight))
self.immage = clear_surface
def frame_render(self):
self.framesurface = pygame.Surface((self.framewidth, self.frameheight), pygame.SRCALPHA)
self.framesurface.blit(
self.immage, (0, 0),
(0, self.framewidth*(self.currentframe-1), self.framewidth, self.frameheight))
self.framesurface = pygame.transform.smoothscale(self.framesurface, self.finalsize)
screen.blit(self.framesurface, self.position)
def frame_incriment(self,speed= 1):#this function is not used as of this moment
self.currentframe = int((((animationcounter%fps)+1)/fps)*self.frame*speed)
if self.currentframe == 0:
self.currentframe = self.frame
test = sprite()
apple = sprite(pngname = "apple small.png",position = (150,150))
banana = sprite()
activesprites = [apple,banana]
animationcounter = 0
animationloopreset_frames = fps*5 #every 5 seconds the animation counter resets
while True:
clock.tick(fps)
animationcounter = animationcounter + 1
if animationcounter > animationloopreset_frames:
animationcounter = 1
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()#shuts pygame
exit()#ends code
#pygame events here, buttons change activesprites
screen.fill((255,255,255))#clears screen, place things after
#for item in activesprites:
#print("drawing sprite:" + item)
test.frame_render()
apple.frame_render()
pygame.display.update()
Related
for this code im trying to make a class that can take a png with one immage, or a png with many frames of an animation and put it on screen. most of the functionality works but there are a few bugs and i have no idea what is causing them.
in the class it initializes the information.
it creates a clear surface, puts the entire png on it to use later.
then it creates a clear surface the size of one frame, which is used in the rendering function to get the right area of the png when its needed.
then each frame of the pygame window it loads the images using a class function. right now it does not run the function that iterates through animation frames it only renders the one immage.
import pygame
from sys import exit
pygame.init()
screenwidth = 1000
screenheight = 500
fps = 32
screen = pygame.display.set_mode((screenwidth,screenheight))
screen.convert_alpha()
clock = pygame.time.Clock()
class sprite():
def __init__(self,
position = (0,0),#top left
frames = 1,
finalsize = (100,100),
pngname="missing.png",
startingframe = 1):
self.position = position
self.frames = frames
self.finalsize = finalsize
self.pngname = pngname
self.currentframe = startingframe
self.immage = pygame.image.load(self.pngname)#this loads a surface
self.immage.convert_alpha()
#dimentions defined, self.framesurface
#transparency
totalwidth = self.immage.get_width()
self.frameheight = self.immage.get_height()
self.framewidth = totalwidth/self.frames
self.framesurface = pygame.Surface((self.framewidth,self.frameheight))#makes surface with frame dimentions
self.framesurface.convert_alpha()
self.framesurface.fill((0,0,0,255))
clear_surface = pygame.Surface((totalwidth,self.frameheight))
clear_surface.convert_alpha()
clear_surface.fill((100,100,100,255))#transparent surface made
#clear_surface.set_alpha(0) this turns the entire immage transparent
clear_surface.blit(self.immage,(0,0),(0,0,finalsize[0],finalsize[1]))
self.immage = clear_surface
#self.immage
#self.framesurface
def frame_render(self):
self.framesurface.blit(self.immage,
(0,0),
(0, self.framewidth*self.currentframe, self.framewidth, self.frameheight))
#blits one frame
#self.immage = self.framesurface
self.framesurface = pygame.transform.smoothscale(self.framesurface,self.finalsize)#scales immage to finalsize
self.framesurface.fill((255,255,255,255))#this makes it work????
screen.blit(self.immage,self.position,(0,0,self.finalsize[0],self.finalsize[1]))#adds it to screen
def frame_incriment(self,speed= 1):#this function is not used as of this moment
self.currentframe = int((((animationcounter%fps)+1)/fps)*self.frame*speed)
if self.currentframe == 0:
self.currentframe = self.frame
test = sprite()
apple = sprite(pngname = "apple small.png",position = (150,150))
banana = sprite()
activesprites = [apple,banana]
animationcounter = 0
animationloopreset_frames = fps*5 #every 5 seconds the animation counter resets
while True:
clock.tick(fps)
animationcounter = animationcounter + 1
if animationcounter > animationloopreset_frames:
animationcounter = 1
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()#shuts pygame
exit()#ends code
#pygame events here, buttons change activesprites
screen.fill((255,255,255))#clears screen, place things after
#for item in activesprites:
#print("drawing sprite:" + item)
test.frame_render()
apple.frame_render()
pygame.display.update()
but what I'm getting is
which shows a grey surface behind the apple. it should be transparent because of the line "clear_surface.fill((100,100,100,255))" in the init function.
and the apple is cropped to 100x100, this happens on the line "self.framesurface = pygame.transform.smoothscale(self.framesurface,self.finalsize)" because the scale function is making the surface 100x100 and not effecting the image for some reason even though that's the entire point of a scale function.
ive looked through the documentation of pygame and alternate ways of transparency seem a lot more complex or remove a color entirely which i don't want in this code, and i havent seen a scale alternative fixes my issue.
Your code
self.framesurface = pygame.Surface((self.framewidth,self.frameheight))
self.framesurface.convert_alpha()
creates a surface in RGB format and converts it to a surface in RGBA format. Therefore the surface is completely opaque and the value of all pixels is (0, 0, 0, 1).
You have to create a surface with RGBA format using the [SRCALPHA]:
self.framesurface = pygame.Surface((self.framewidth, self.frameheight), pygame.SRCALPHA)
blit the complete image on clear_surface not just a part of it.
clear_surface = pygame.Surface((totalwidth, self.frameheight), pygame.SRCALPHA)
clear_surface.blit(self.immage, (0, 0), (0, 0, totalwidth, self.frameheight))
In this case the surface is completely transparent and the initial value of the pixels is (0, 0, 0, 0):
Furthermore you need to blit the scaled surface on surface with the size of the original image, before you can scale it. You have to create a surface with the size of the original image in every frame. After that you can scale that surface:
def frame_render(self):
self.framesurface = pygame.Surface((self.framewidth, self.frameheight), pygame.SRCALPHA)
self.framesurface.blit(
self.immage, (0, 0),
(0, self.framewidth*(self.currentframe-1), self.framewidth, self.frameheight))
self.framesurface = pygame.transform.smoothscale(self.framesurface, self.finalsize)
screen.blit(self.framesurface, self.position)
Complete and working example:
import pygame
from sys import exit
pygame.init()
screenwidth = 1000
screenheight = 500
fps = 32
screen = pygame.display.set_mode((screenwidth,screenheight))
screen.convert_alpha()
clock = pygame.time.Clock()
class sprite():
def __init__(self,
position = (0,0),#top left
frames = 1,
finalsize = (100,100),
pngname="missing.png",
startingframe = 1):
self.position = position
self.frames = frames
self.finalsize = finalsize
self.pngname = pngname
self.currentframe = startingframe
self.immage = pygame.image.load(self.pngname)
self.immage.convert_alpha()
totalwidth = self.immage.get_width()
self.frameheight = self.immage.get_height()
self.framewidth = totalwidth/self.frames
self.framesurface = pygame.Surface((self.framewidth, self.frameheight), pygame.SRCALPHA)
clear_surface = pygame.Surface((totalwidth, self.frameheight), pygame.SRCALPHA)
clear_surface.blit(self.immage, (0, 0), (0, 0, totalwidth, self.frameheight))
self.immage = clear_surface
def frame_render(self):
self.framesurface = pygame.Surface((self.framewidth, self.frameheight), pygame.SRCALPHA)
self.framesurface.blit(
self.immage, (0, 0),
(0, self.framewidth*(self.currentframe-1), self.framewidth, self.frameheight))
self.framesurface = pygame.transform.smoothscale(self.framesurface, self.finalsize)
screen.blit(self.framesurface, self.position)
def frame_incriment(self,speed= 1):#this function is not used as of this moment
self.currentframe = int((((animationcounter%fps)+1)/fps)*self.frame*speed)
if self.currentframe == 0:
self.currentframe = self.frame
test = sprite()
apple = sprite(pngname = "apple small.png",position = (150,150))
banana = sprite()
activesprites = [apple,banana]
animationcounter = 0
animationloopreset_frames = fps*5 #every 5 seconds the animation counter resets
while True:
clock.tick(fps)
animationcounter = animationcounter + 1
if animationcounter > animationloopreset_frames:
animationcounter = 1
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()#shuts pygame
exit()#ends code
#pygame events here, buttons change activesprites
screen.fill((255,255,255))#clears screen, place things after
#for item in activesprites:
#print("drawing sprite:" + item)
test.frame_render()
apple.frame_render()
pygame.display.update()
I am trying to create a game using pygame and I am attempting to add a background to it (I have used some code from a YouTube video but this is not working). I also to not understand what the code is on about. I mean the background and does move but it automatically adds a new version of the background in the middle of the screen when the older background has not gone off screen yet:
class Background:
def __init__(self, x, y, picture):
self.xpos = x
self.ypos = y
self.picture = picture
self.rect = self.picture.get_rect()
self.picture = pygame.transform.scale(self.picture, (1280, 720))
def paste(self, xpos, ypos):
screen.blit(self.picture, (xpos, ypos))
def draw(self):
screen.blit(self.picture, (self.xpos, self.ypos))
while True:
background=pygame.image.load("C:/images/mars.jpg").convert_alpha()
cliff = Background(0, 0, background)
rel_x = x % cliff.rect.width
cliff.paste(rel_x - cliff.rect.width, 0)
if rel_x < WIDTH:
cliff.paste(rel_x, 0)
x -= 1
This is what currently happens with my background
[![what my problem looks like][1]][1]
[![What I want the background to move like ][2]][2]
This is what I want my background to look like (please ignore the sign it was the only one I could find)
I have now discovered what the real problem is
If you want to have a continuously repeating background, then you've to draw the background twice:
You've to know the size of the screen. The size of the height background image should match the height of the screen. The width of the background can be different, but should be at least the with of the window (else the background has to be drawn more than 2 times).
bg_w, gb_h = size
bg = pygame.transform.smoothscale(pygame.image.load('background.image'), (bg_w, bg_h))
The background can be imagined as a endless row of tiles.
If you want to draw the background at an certain position pos_x, then you have to calculate the position of the tile relative to the screen by the modulo (%) operator. The position of the 2nd tile is shifted by the width of the background (bg_w):
x_rel = pos_x % bg_w
x_part2 = x_rel - bg_w if x_rel > 0 else x_rel + bg_w
Finally the background has to be blit twice, to fill the entire screen:
screen.blit(bg, (x_rel, 0))
screen.blit(bg, (x_part2, 0))
You can test the process by the following example program. The background can be moved by <- respectively ->
import pygame
pygame.init()
size = (800,600)
screen = pygame.display.set_mode(size)
clock = pygame.time.Clock()
bg_w, bg_h = size
bg = pygame.transform.smoothscale(pygame.image.load('background.image'), (bg_w, bg_h))
pos_x = 0
speed = 10
done = False
while not done:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
allKeys = pygame.key.get_pressed()
pos_x += speed if allKeys[pygame.K_LEFT] else -speed if allKeys[pygame.K_RIGHT] else 0
x_rel = pos_x % bg_w
x_part2 = x_rel - bg_w if x_rel > 0 else x_rel + bg_w
screen.blit(bg, (x_rel, 0))
screen.blit(bg, (x_part2, 0))
pygame.display.flip()
Also see How to make parallax scrolling work properly with a camera that stops at edges pygame
This SO answer should have what you need
This seems to provide maybe a smarter and more functional background class than what you're using. I'd say give a try.
I'm trying to create a platformer game, and I want to maintain 60 FPS in it without having the sprite animations move really quickly. I've seen other answers on how to do so using the time module, but I don't really understand how to apply that.
Main Code:
import pygame
import os
import sys
import random
from pygame.locals import *
import spritesheet
import time
pygame.init()
clock = pygame.time.Clock()
FPS = 60
prev_time = time.time()
pygame.display.set_caption('Platformer')
BG_COLOR = (50, 50, 50)
BLACK = (0, 0, 0)
WIN_SIZE = (1920,1080)
WIN = pygame.display.set_mode(WIN_SIZE, 0, 32)
# CONFIGURING Animations
sprite_sheet_img_IDLE = pygame.image.load('Spritesheets/Outline/120x80_PNGSheets/_Idle.png')
sprite_sheet = spritesheet.SpriteSheet(sprite_sheet_img_IDLE)
IDLE_FRAMES = []
IDLE_STEPS = 9
IDLE_INDEX = 0
INDEX = 0
IDLE_ANIM_SPEED = 20
for x in range(IDLE_STEPS):
newFRAME = sprite_sheet.get_image(x, 120, 80, 3.5, BLACK)
IDLE_FRAMES.append(newFRAME)
while True:
clock.tick(FPS)
now = time.time()
dt = now - prev_time
prev_time = now
WIN.fill(BG_COLOR)
WIN.blit(IDLE_FRAMES[], (0, 0))
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
pygame.display.update()
if IDLE_INDEX < 8:
IDLE_INDEX += 1
else:
IDLE_INDEX = 0
INDEX += 1
Spritesheet Class:
import pygame
class SpriteSheet():
def __init__(self, image):
self.sheet = image
def get_image(self, frame, width, height, scale, colour):
image = pygame.Surface((width, height)).convert_alpha()
image.blit(self.sheet, (0, 0), ((frame * width), 0, width, height))
image = pygame.transform.scale(image, (width * scale, height * scale))
image.set_colorkey(colour)
return image
Here is the spritesheet if anyone wants to replicate my situation:
I'm 100% confident there is a better way to do it, but I would create a new variable called animation_tick and add one to it each iteration in the while loop. Only call the sprite change every n ticks to add delay between changes.
Example code with only necessary parts:
while True:
if animation_tick == 20: #change 20 to how ever many ticks in between animation frames
if IDLE_INDEX < 8:
IDLE_INDEX += 1
else:
IDLE_INDEX = 0
animation_tick = 0 #reset tick back to 0 after changing frame
animation_tick += 1 #add 1 each iteration of the while loop
It's practically just a for loop inside a while loop
I'm making a Sudoku Solver via pygame and I've been able to draw the whole board, however, while programming the section of code that deals with clicking on a tile, I made it so that the current tile would "light up" green so the user can know the current tile. However, I'm stuck figuring out how to draw over the green highlighted section when a user decides to click on a different tile. I'd like to remove that part entirely but, since it's a rect with a thickness of 4 and the base tiles have a thickness of 1, all I accomplish is having an ugly black line over the thick green line. I tried redrawing the whole board but I obtain similar results. Any help?
class Tile:
'''Represents each white tile/box on the grid'''
def __init__(self, value, window, x1, x2):
self.value = value #value of the num on this grid
self.window = window
self.active = False
self.rect = pygame.Rect(x1, x2, 60, 60) #dimensions for the rectangle
def draw(self, color, thickness):
'''Draws a tile on the board'''
pygame.draw.rect(self.window, color, self.rect, thickness)
pygame.display.flip()
def main():
board = Board(screen)
tiles = board.draw_board() #store the locations of all the tiles on the grid
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONUP:
mousePos = pygame.mouse.get_pos()
#board.draw_board() or (see next comment)
for i in range(9):
for j in range (9): #look for tile we clicked on
#tiles[i][j].draw((0,0,0),1)
#yield same results
if tiles[i][j].is_clicked(mousePos):
tiles[i][j].draw((50,205,50),4) #redraws that tile but with a highlighted color to show it's been clicked
break
main()
You could simply store the highlighted tile in a variable. I also don't think you need a Tile class at all, since a sudoku game state is basically just a list of list of numbers.
Here's a simple example. Note the comments:
import random
import pygame
TILE_SIZE = 64
# function to draw the grid
def draw_board():
board_surface = pygame.Surface((9*TILE_SIZE, 9*TILE_SIZE))
board_surface.fill((255, 255, 255))
for x in range(9):
for y in range(9):
rect = pygame.Rect(x*TILE_SIZE, y*TILE_SIZE, TILE_SIZE, TILE_SIZE)
pygame.draw.rect(board_surface, (0, 0, 0), rect, 1)
pygame.draw.line(board_surface, (0, 0, 0), (0, 3*TILE_SIZE), (9*TILE_SIZE, 3*TILE_SIZE), 5)
pygame.draw.line(board_surface, (0, 0, 0), (0, 6*TILE_SIZE), (9*TILE_SIZE, 6*TILE_SIZE), 5)
pygame.draw.line(board_surface, (0, 0, 0), (3*TILE_SIZE, 0), (3*TILE_SIZE, 9*TILE_SIZE), 5)
pygame.draw.line(board_surface, (0, 0, 0), (6*TILE_SIZE, 0), (6*TILE_SIZE, 9*TILE_SIZE), 5)
return board_surface
def main():
# standard pygame setup
pygame.init()
clock = pygame.time.Clock()
screen = pygame.display.set_mode((9*TILE_SIZE, 9*TILE_SIZE))
font = pygame.font.SysFont(None, 40)
# seperate the game state from the UI
# create a dummy 9x9 sudoku board
state = [[None for _ in range(10)] for _ in range(10)]
for _ in range(15):
x = random.randint(0, 9)
y = random.randint(0, 9)
state[y][x] = random.randint(1, 9)
# a variable to hold the selected tile's position on the board
# (from 0,0 to 8,8)
selected = None
# create the grid surface ONCE and reuse it to clear the screen
board_surface = draw_board()
while True:
for event in pygame.event.get():
pos = pygame.mouse.get_pos()
if event.type == pygame.QUIT:
return
# when the player clicks on a tile
# we translate the screen coordinates to the board coordinates
# e.g. a pos of (140, 12) is the tile at (2, 0)
if event.type == pygame.MOUSEBUTTONDOWN:
w_x, w_y = event.pos
selected = w_x // TILE_SIZE, w_y // TILE_SIZE
# clear everything by blitting the grid surface to the screen
screen.blit(board_surface, (0, 0))
# print all numbers in the state to the screen
# we use a Rect here so we can easily center the numbers
rect = pygame.Rect(0, 0, TILE_SIZE, TILE_SIZE)
for line in state:
for tile in line:
if tile != None:
tmp = font.render(str(tile), True, (0, 0, 0))
screen.blit(tmp, tmp.get_rect(center=rect.center))
rect.move_ip(TILE_SIZE, 0)
rect.x = 0
rect.move_ip(0, TILE_SIZE)
# if a tile is selected, we calculate the world coordinates from the board coordinates
# and draw a simple green rect
if selected:
rect = pygame.Rect(selected[0] * TILE_SIZE, selected[1] * TILE_SIZE, TILE_SIZE, TILE_SIZE)
pygame.draw.rect(screen, (0, 200, 0), rect, 5)
clock.tick(30)
pygame.display.flip()
main()
Also, you shouldn't call pygame.display.flip() outside your main loop. It can lead to effects like flickering or images not showing up correctly.
I see several options. I'll start with the ones that will require the least amount of code changes.
Draw your selection rect only inside your tile. Problem solved.
when you draw a tile, draw the border around it as well.
You might also want to store the selected tile in a variable, so you can some stuff with it later on.
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.