Surface display able to properly represent opacity, but any other surface cannot - python

I am trying to make a tic-tac-toe game with pygame. An important thing I want is being able to make my images (eg. X and O) slightly translucent for when my user is only hovering over a grid tile. I also use opacity to visually show whose turn it is.
This is what I have tried:
x_tile = pygame.image.load('x_tile').convert()
x_tile.set_alpha(100)
This works fine when I'm blitting x_tile directly onto the display like this:
# This is for simplicity's sake. The actual blit process is all being done in an infinite loop
screen = pygame.display.set_mode((300, 300))
screen.blit(x_file, x_file.get_rect())
But my game is using another image that represents the grid, and that is what I'm blitting onto. So I'm blitting this board onto the display, then blitting the actual X and O tiles on the board.
screen = pygame.display.set_mode((300, 300))
screen.blit(board, board_rect)
board.blit(x_tile, x_tile.get_rect(center=grid[0].center)) # I have a list of Rects that make a grid on the board image. grid[0] is the top left
When I do it that way, x_tile.set_alpha(100) seems to have no effect and I don't know what to do.
Edit: I am using pygame 2.0.1. I'm on Windows 10.
Here is the entire code
import os
import pygame
from pygame.locals import *
# Game constants
WIN_SIZE = WIN_WIDTH, WIN_HEIGHT = 800, 600
BLACK = 0, 0, 0
WHITE = 255, 255, 255
RED = 255, 0, 0
BLUE = 0, 0, 255
# Game functions
class NoneSound:
"""dummy class for when pygame.mixer did not init
and there is no sound available"""
def play(self): pass
def load_sound(file):
"""loads a sound file, prepares it for play"""
if not pygame.mixer:
return NoneSound()
music_to_load = os.path.join('sounds', file)
try:
sound = pygame.mixer.Sound(music_to_load)
except pygame.error as message:
print('Cannot load following sound:', music_to_load)
raise SystemExit(message)
return sound
def load_image(file, colorkey=None, size=None):
"""loads image into game"""
image_to_load = os.path.join('images', file)
try:
image = pygame.image.load(image_to_load).convert()
except pygame.error as message:
print('Cannot load following image:', image_to_load)
raise SystemExit(message)
if colorkey is not None:
if colorkey == -1:
colorkey = image.get_at((0, 0))
image.set_colorkey(colorkey, RLEACCEL)
if size is not None:
image = pygame.transform.scale(image, size)
return image
# Game class
class TTTVisual:
"""Controls game visuals"""
def __init__(self, win: pygame.Surface):
self.win = win
# Load in game images
self.board = load_image('board.png', size=(600, 450), colorkey=WHITE)
self.x_tile = load_image('X_tile.png', size=(100, 100), colorkey=BLACK)
self.o_tile = load_image('O_tile.png', size=(100, 100), colorkey=BLACK)
# Translucent for disabled looking tile
self.x_tile_trans = self.x_tile.copy()
self.o_tile_trans = self.o_tile.copy()
self.x_tile_trans.set_alpha(100)
self.o_tile_trans.set_alpha(100)
# Used to let user know whose turn it is
self.x_turn = pygame.transform.scale(self.x_tile, (50, 50))
self.o_turn = pygame.transform.scale(self.o_tile, (50, 50))
self.x_turn_trans = pygame.transform.scale(self.x_tile_trans, (50, 50))
self.o_turn_trans = pygame.transform.scale(self.o_tile_trans, (50, 50))
self.get_rects()
self.grid = self.setup_grid()
def get_rects(self):
"""Creates coords for some visual game assets"""
self.board_rect = self.board.get_rect(
center=self.win.get_rect().center)
self.x_turn_rect = self.x_turn.get_rect(top=10, left=10)
self.o_turn_rect = self.o_turn.get_rect(top=10, left=WIN_WIDTH-60)
def setup_grid(self):
grid = []
left = 0
top = 150
row = 0
for i in range(9):
if (i != 0) and (i % 3 == 0):
row += 1
left = 0
grid.append(pygame.Rect(left, row*top, 200, 150))
left += 200
return grid
def update_turn_status(self):
"""Updates the X and O tiles on the top left and right to
let user know whose turn it is"""
self.win.blits((
(self.x_turn_trans, self.x_turn_rect),
(self.o_turn, self.o_turn_rect)
))
def update_grid(self):
"""Updates board"""
self.win.blit(self.board, self.board_rect)
# Here is where you could change board to win and see that the tile changes in opacity
self.board.blit(self.x_tile_trans, self.x_tile_trans.get_rect(center=self.grid[0].center))
def update(self):
self.win.fill(WHITE)
self.update_turn_status()
self.update_grid()
pygame.display.flip()
def main():
pygame.init()
win = pygame.display.set_mode(WIN_SIZE)
tttvisual = TTTVisual(win)
tttfunc = TTTFunc(tttvisual)
clock = pygame.time.Clock()
running = True
while running:
clock.tick(60)
for event in pygame.event.get():
if event.type == QUIT:
running = False
tttvisual.update()
pygame.quit()
if __name__ == "__main__":
main()

The issue is caused by the line:
self.board.blit(self.x_tile_trans, self.x_tile_trans.get_rect(center=self.grid[0].center))
You don't blit the image on the display Surface, but on the self.board Surface. When a Surface is blit, it is blended with the target. When you draw on a Surface, it changes permanently. Since you do that over and over again, in every frame, the source Surface appears to by opaque. When you decrease the alpha value (e.g. self.x_tile_trans.set_alpha(5)), a fade in effect will appear.
Never draw on an image Surface. Always draw on the display Surface. Cleat the display at begin of a frame. Draw the entire scene in each frame and update the display once at the end of the frame.
class TTTVisual:
# [...]
def update_grid(self):
"""Updates board"""
self.win.blit(self.board, self.board_rect)
# Here is where you could change board to win and see that the tile changes in opacity
x, y = self.grid[0].center
x += self.board_rect.x
y += self.board_rect.y
self.win.blit(self.x_tile_trans, self.x_tile_trans.get_rect(center=(x, y)))
The typical PyGame application loop has to:
handle the events by either pygame.event.pump() or pygame.event.get().
update the game states and positions of objects dependent on the input events and time (respectively frames)
clear the entire display or draw the background
draw the entire scene (blit all the objects)
update the display by either pygame.display.update() or pygame.display.flip()

Related

How do I save a section of a pygame screen and blit it to another location?

I am making a program with a graph that scrolls and I just need to move a section of the screen.
If I do something like this:
import pygame
screen = pygame.display.set_mode((300, 300))
sub = screen.subsurface((0,0,20,20))
screen.blit(sub, (30,40))
pygame.display.update()
It gives the error message: pygame.error: Surfaces must not be locked during blit
I assume it means the child is locked to its parent surface or something but how else could I go about doing this?
screen.subsurface creates a surface, which reference to the original surface. From documentation:
Returns a new Surface that shares its pixels with its new parent.
To avoid undefined behaviour, the surfaces get locked. You've to .copy the surface, before you can .blit it to its source:
sub = screen.subsurface((0,0,20,20)).copy()
screen.blit(sub, (30,40))
Just don't draw to the screen surface directly. Create a Surface for each part of your game/UI, and blit each of those to the screen.
import pygame
def main():
pygame.init()
screen = pygame.display.set_mode((640, 480))
# create two parts: a left part and a right part
left_screen = pygame.Surface((400, 480))
left_screen.fill((100, 0, 0))
right_screen = pygame.Surface((240, 480))
right_screen.fill((200, 200, 0))
x = 100
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
# don't draw to the screen surface directly
# draw stuff either on the left_screen or right_screen
x += 1
left_screen.fill(((x / 10) % 255, 0, 0))
# then just blit both parts to the screen surface
screen.blit(left_screen, (0, 0))
screen.blit(right_screen, (400, 0))
pygame.display.flip()
if __name__ == '__main__':
main()

Faster drawing in python pygame

I made a program using Python, with pygame, that loads pictures of materials and then creates blocks and each block is assigned with random material.
Block is a class and in the drawing process, it iterates through the array with stored blocks, but that is very slow. Isn't there a faster method than storing them in array and iterating through?
class block:
def __init__(self, texture, x, y):
self.texture = texture
self.x = x
self.y = y
material = pygame.image
material.grass = pygame.image.load("textures/grass.png")
material.water = pygame.image.load("textures/water.png")
material.sand = pygame.image.load("textures/sand.png")
materials = [material.grass, material.water, material.sand]
white = (255,255,255);(width, height) = (2048, 1008);black = (0, 0, 0);screen = pygame.display.set_mode((width, height))
b_unit = 16
b = []
count = 0
cx = 0
cy = 0
while count < (width * height) / (b_unit * b_unit):
b.append(block(random.choice(materials), b_unit * cx, b_unit * cy))
cx += 1
count += 1
if cx == width / b_unit:
cx = 0
cy += 1
while True:
for block in b:
screen.blit(block.texture, (block.x + viewx, block.y + viewy))
pygame.display.flip()
I've already mentioned in the comments that you should (almost) always convert your images to improve the performance.
It can also help to blit separate images/pygame.Surfaces onto a big background surface and then just blit this background once per frame. I use two nested for loops here to get the coordinates and randomly blit one of two images.
I get around 120 fps if I use separate sprites (5184) here and ~430 fps with this single background image.
Of course I'm just blitting here and in a real game you'd probably have to store the rects of the tiles in a list or use pygame sprites and sprite groups, for example to implement collision detection or other map related logic, so the frame rate would be lower.
import itertools
import pygame as pg
from pygame.math import Vector2
BLUE_IMAGE = pg.Surface((20, 20))
BLUE_IMAGE.fill(pg.Color('lightskyblue2'))
GRAY_IMAGE = pg.Surface((20, 20))
GRAY_IMAGE.fill(pg.Color('slategray4'))
def main():
screen = pg.display.set_mode((1920, 1080))
clock = pg.time.Clock()
all_sprites = pg.sprite.Group()
images = itertools.cycle((BLUE_IMAGE, GRAY_IMAGE))
background = pg.Surface(screen.get_size())
# Use two nested for loops to get the coordinates.
for y in range(screen.get_height()//20):
for x in range(screen.get_width()//20):
# This alternates between the blue and gray image.
image = next(images)
# Blit one image after the other at their respective coords.
background.blit(image, (x*20, y*20))
next(images)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
# Now you can just blit the background image once
# instead of blitting thousands of separate images.
screen.blit(background, (0, 0))
pg.display.set_caption(str(clock.get_fps()))
pg.display.flip()
clock.tick(1000)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
Side notes: Don't add your images to the pygame.image module (that makes no sense at all).
material = pygame.image
material.grass = pygame.image.load("textures/grass.png")
Writing several statements in the same row separated with semicolons is really ugly and makes code less readable.
white = (255,255,255);(width, height) = (2048, 1008)

Can somebady help me why errors exist in my pygame code about `ball moving`?

I will show my code below. I think when I def moveDot, something wrong I can feel it. However, I just code as my instructor's presentation. She's code can work and mine cannot. I don't know the reason. I think the order of moveDot's variances may have some problems. Is that right? I will appreciate anyone who can help me! Thanks a lot!
# Poke The Dots
# There are two dots that are moving on a 500 by 400 window
# There is a score board that displays the time in seconds
# since the game started
# If the player clicks inside the window, the dots disappear
# and reappear at some random location
# If the dots collide, the dots stop moving, the score stops
# changing and Game Over is displayed.
# Version 1
import pygame, sys, time
from pygame.locals import *
# User-defined classes
# User-defined functions
def main():
# Initialize pygame
pygame.init()
# Set window size and title, and frame delay
surfaceSize = (500, 400) # window size
windowTitle = 'Poke The Dots' #window title
frameDelay = 0.02 # smaller is faster game
# Create the window
surface = pygame.display.set_mode(surfaceSize, 0, 0)
pygame.display.set_caption(windowTitle)
# create and initialize red dot and blue dot
gameOver = False
color1=pygame.Color('red')
center1 = [50, 75]
radius1=30
speed1=[1,2]
color2=pygame.Color('blue')
center2=[200,100]
radius2=40
speed2=[2,1]
# Draw objects
pygame.draw.circle(surface, color1, center1, radius1, 0)
pygame.draw.circle(surface, color2,center2,radius2,0)
gameOver = update(surface, color1, center1, radius1, speed1, color2, center2, radius2, speed2)
# Refresh the display
pygame.display.update()
# Loop forever
while True:
# Handle events
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
# Handle additional events
# Update and draw objects for the next frame
#gameOver = update(center, surface)
# Refresh the display
pygame.display.update()
# Set the frame speed by pausing between frames
time.sleep(frameDelay)
def update(surface, color1, center1, radius1, speed1, color2, center2, radius2, speed2):
#Check if the game is over. If so, end the game and
#returnTrue. Otherwise, update the game objects, draw
#them, and return False.
#This is an example update function - replace it.
#- center is a list containing the x and y int coords
#of the center of a circle
#- surface is the pygame.Surface object for the window
erasecolor=pygame.Color('black')
if False: # check if the game is over
return True
else: # continue the game
surface.fill(erasecolor)
moveDot(surface, color1, center1, radius1)
moveDot(surface, color2, center2, radius2)
pygame.draw.circle(surface,color1,center1,radius1,0,0)
pygame.draw.circle(surface,color2,center2,radius2,0,0)
return False
def moveDot(surface,center,radius,speed):
size=surface.get_size()
for coord in range(0,2):
center[coord]=center[coord]+speed[coord]
if center [coord]<radius:
speed[coord]=-speed[coord]
if center[coord]+radius>size(coord):
speed[coord]=-speed[coord]
main()
The order of your arguments being passed when you call moveDot is incorrect. It should be
moveDot(surface, center1, radius1, speed)
For both statements. Speed should be the speed of movement of the circle.

text being cut in pygame

I seem to have problems with displaying text on the screen
The code draws text on the screen but half 'S' of 'Score' gets cut for reason.
However, if I change screen.blit(text, self.score_rect, self.score_rect) to screen.blit(text, self.score_rect), it works fine. I would like to know why is this happening and how can I fix this.
Thanks.
Here's the code:
class Score(object):
def __init__(self, bg, score=100):
self.score = score
self.score_rect = pygame.Rect((10,0), (200,50))
self.bg = bg
def update(self):
screen = pygame.display.get_surface()
font = pygame.font.Font('data/OpenSans-Light.ttf', 30)
WHITE = (255, 255, 255)
BG = (10, 10, 10)
score = "Score: " + str(self.score)
text = font.render(score, True, WHITE, BG)
text.set_colorkey(BG)
screen.blit(
self.bg,
self.score_rect,
self.score_rect)
screen.blit(text,
self.score_rect,
self.score_rect)
def main():
pygame.init()
#initialize pygame
pygame.init()
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption('Score Window')
#initialize background
bg = pygame.Surface((screen.get_size())).convert()
bg.fill((30, 30, 30))
screen.blit(bg, (0, 0))
#initialize scoreboard
score_board = Score(bg)
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit(0)
score_board.update()
pygame.display.flip()
Well - it looks like the third parameter on the call do blit, where you repeat the core_rect` parameter is designed exactly to do that: it selects a rectangular area on the
source image (in this case your rendered text) to be pasted in the destination (in this case, the screen).
Text in Pygame is rendered with nice margins, you should not need the source-crop parameter at all - and if you thinbk ou do, you should pass it a suitable set of coordinates, relevant inside the rendered text, not therectangle with the destination coordinates on the screen.
From http://www.pygame.org/docs/ref/surface.html#pygame.Surface.blit:
blit() draw one image onto another blit(source, dest, area=None,
special_flags = 0) -> Rect Draws a source Surface onto this Surface.
The draw can be positioned with the dest argument. Dest can either be
pair of coordinates representing the upper left corner of the source.
A Rect can also be passed as the destination and the topleft corner of
the rectangle will be used as the position for the blit. The size of
the destination rectangle does not effect the blit.
An optional area rectangle can be passed as well. This represents a
smaller portion of the source Surface to draw.
...

problem with making background of image transparent: pygame

I'm in the middle of working on a simple typing tutor using pygame. My problem is that I'm using an image that has a white background, waves1.png. Now's I've specified that I want white to be transparent in the image (self.image.set_colorkey((255, 255, 255))) and it is for everything except the text block. When the waves intersect with the text object, the white background of the waves show on top of the text. You can try running this if you have pygame (with the exception of the waves1.png image).
import pygame
from pygame.locals import *
class TextSprite(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.wordList = ['words yes', 'hello', 'this is a sentence', 'this is another sentence'] # read directly from external file
self.pos = 0
self.wordNum = 0
self.update1()
def update1(self):
# Render the given word
self.image = pygame.font.Font(None, 36).render(self.wordList[self.wordNum], 1, (0, 0, 0))
# Render the correctly guessed letters
self.correct = pygame.font.Font(None, 36).render(self.wordList[self.wordNum][:self.pos], 1, (255, 0, 0))
# Copy correct letters onto given word
self.image.blit(self.correct, (0, 0))
self.rect = self.image.get_rect()
# set the center of the center the given word to the center of the screen
self.rect.center = pygame.display.get_surface().get_rect().center
def keyin(self, key):
word = self.wordList[self.wordNum]
letter = word[self.pos]
if letter == key:
self.pos = self.pos + 1
if self.pos == len(word):
self.reset()
self.update1()
def reset(self):
self.pos = 0
self.wordNum = self.wordNum + 1
self.update1()
class Waves(pygame.sprite.Sprite):
# Constructor. Pass in the color of the block,
# and its x and y position
def __init__(self, filename):
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
# Create an image of the block, and fill it with a color.
# This could also be an image loaded from the disk.
self.image = pygame.image.load(filename).convert()
# makes any white in the image transparent
self.image.set_colorkey((255, 255, 255))
self.rect = self.image.get_rect()
# Decrease the y coordinate so the waves look like they're moving up
def update(self, text):
self.rect.y = self.rect.y - 6
if self.rect.y <= 200:
text.reset()
self.rect.y = 485
def main():
#I - Import and initialize
pygame.init()
#D - Display configuration
# The screen variable is a pygame Surface object
# Note that the set_mode() method creates a Surface object for you automatically
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption("Typing Game")
#E - Entities (just background for now)
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((255, 255, 255))
screen.blit(background, (0,0))
#A - Action (broken into ALTER steps)
#A - Assign values to key variables
clock = pygame.time.Clock()
keepGoing = True
# Collect the sprite in a list
all = pygame.sprite.RenderPlain()
waveList = pygame.sprite.RenderPlain()
text = TextSprite()
all.add(text)
waves = Waves("waves1.png")
waveList.add(waves)
waves.rect.x = 0
waves.rect.y = 485
#L - Set up main loop
while keepGoing:
#T - Timer to set frame rate
# Tick is a method in the Clock class that determines the maximum frame rate
clock.tick(30)
#E - Event handling
for event in pygame.event.get():
if event.type == QUIT:
keepGoing = False
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
keepGoing = False
else:
text.keyin(event.unicode)
# update position of waves
waves.update(text)
# clears screen
all.clear(screen, background)
# update screen
all.draw(screen)
waveList.clear(screen, background)
waveList.draw(screen)
# display.flip is a method that copies everything from the screen object to the actual visual display
pygame.display.flip()
pygame.quit ()
if __name__ == '__main__': main()
I don't know if it's an option for you, but you should get better results with png's native alpha transparency.
If you can edit/recreate the png yourself, then try using a transparent background.
From there, you can use convert_alpha() arfter loading the image. (instead of using a colorkey)
http://pygame.org/docs/ref/surface.html#Surface.convert_alpha
EDIT: one other aspect, is that the image may have an alpha channel interfering with the colorkey. Best to ensure you're not trying to use both.
I'm told that you can detect an image's alpha channel programmatically. Something like ...
if self.image.get_masks()[3]!=0:
print "image has alpha!"
See here http://pygame.org/docs/ref/surface.html#Surface.get_masks
HTH
Well done! You've actually done everything correctly to take advantage of transparency and colorkey (ie, making sure to call convert on the surface, making sure to pass the color into the set_colorkey method, etc).
The problem is with the order of calls to draw and clear on your respective sprite groups, "all" and "waveList". After you've rendered the text blocks by calling all.draw, you then follow it with the call to waveList.clear.
Here's the problem: once you've drawn the text sprites, you don't want to clear the space underneath the wave sprites, or that will wipe out the area that overlaps the already-drawn text blocks.
If you want to do this properly, try doing it in this order:
waves.update()
all.clear(screen,background)
waveList.clear(screen,background)
all.draw(screen)
waveList.draw(screen)
(more simply, just move waveList.clear(screen, background) to the line just below all.clear(screen, background); that should do it)
When I'm working with sprite groups, I usually try to group it so that each sprite group calls the same method in this order: clears, updates, collision checks (if any), draws.
This usually handles things in the right order. Then you still may have to pay attention to whether there is any layering of sprites, but that's another story for another day.

Categories

Resources