Pygame: How to tile subsurfaces on an image - image

I'm completely new to Python/Pygame and I'm trying to slice an image up into subsurfaces and then blit them in order to the screen (so that later on I can modify it to call upon a certain tile and then blit it).
E1: I think I've finally managed to slice it the right way but there's a little hitch with the blitting.
E2: Updated the code and error (been getting this one on most of the code I tried before as well).
import pygame
from pygame.locals import *
pygame.init()
screen = pygame.display.set_mode((640, 480))
screen.fill((255, 255, 255))
def load_tileset(filename, width, height):
image = pygame.image.load(filename).convert()
image_width, image_height = image.get_size()
tileset = []
for tile_x in range(0, image_width//width):
line = []
tileset.append(line)
for tile_y in range(0, image_height//height):
rect = (tile_x*width, tile_y*height, width, height)
line.append(image.subsurface(rect))
return tileset
def draw_background(screen, tile_img, field_rect):
img_rect = tile_img.get_rect()
for y in range(0,400,32):
for x in range(0,600,32):
screen.blit(tile_img, (x,y))
Field_Rect = Rect(50,50, 300,300)
tileset = load_tileset("platform1.bmp", 17, 17)
bg_tile_img = tileset[0]
draw_background(screen, bg_tile_img, Field_Rect)
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
pygame.quit()
pygame.display.flip()
The error it gives atm is:
img_rect = tile_img.get_rect()
AttributeError: 'list' object has no attribute 'get_rect'

In this function here where you create the tileset:
def load_tileset(filename, width, height):
image = pygame.image.load(filename).convert()
image_width, image_height = image.get_size()
tileset = []
for tile_x in range(0, image_width//width):
line = []
tileset.append(line)
for tile_y in range(0, image_height//height):
rect = (tile_x*width, tile_y*height, width, height)
line.append(image.subsurface(rect))
return tileset
...you will notice that tileset is a list, and each element of tileset (labeled as line), is also a list.
So in this code here:
tileset = load_tileset("platform1.bmp", 17, 17)
bg_tile_img = tileset[0]
draw_background(screen, bg_tile_img, Field_Rect)
...you are passing a list, called bg_tile_img, to the draw_background function.
Your draw_background function should instead look more like this:
def draw_background(screen, tile_img, field_rect):
for img in tile_img:
img_rect = img.get_rect()
for y in range(0,400,32):
for x in range(0,600,32):
screen.blit(img, (x,y))
Alternatively, instead of passing bg_tile_img as a list, pop an element off of it to pass to the draw_background function.

You are not assigning the tileset:
load_tileset("platform1.bmp", 17, 17)
should be:
tileset = load_tileset("platform1.bmp", 17, 17)
Update:
set_mode creates the window, so it should be outside the main loop (load_tileset too):
screen = pygame.display.set_mode((640, 480))
tileset = load_tileset("platform1.bmp", 17, 17)
while True:
for event in pygame.event.get():
...
Also, I can't see any call to draw_background.
Update2:
As Haz said, tileset is a list of lines (a matrix), you could use coordinates to retrieve a specific tile:
bg_tile_img = tileset[0][0]
Or make the tileset a plain list, adding everything directly to the tileset instead of using lines.

Related

Problem with images overlapping in pygame

Im having problems with blitting images to rect objects in pygame. i have a background image blitted to my main pygame window, and also an image blitted to a rect object on the screen which moves. the problem i am having is the rect object is overlapping my background image when its moving around. i was looking to only be able to see the green helicopter shape and not the black outline around it. sorry if i havent explained this very well. will try to include all files im using.
Thanks for any help
import pygame as pg
import random as r
import time
pg.init()
MAX_X = 1190
MAX_Y = 590
MIN_X = 10
MIN_Y = 10
SIZE = 100
SPEED = 1
COLOR = (0,255,0)
move_amount = 0
wn = pg.display.set_mode((1200, 600))
BG_IMG = pg.image.load('bg.png').convert()
BG_IMG = pg.transform.scale(BG_IMG, (1200, 600))
class Wall (pg.Rect):
def __init__(self, posX, posY):
self.xcor = posX
self.ycor = posY
self.rect = None
class Heli (pg.Rect):
def __init__(self, posX, posY):
self.image = pg.image.load('art.png').convert()
self.rect = self.image.get_rect()
self.xcor = posX
self.ycor = posY
# top and bottom constant walls
TOP = pg.Rect(MIN_X, MIN_Y, MAX_X, 3)
BOTTOM = pg.Rect(MIN_X, MAX_Y, MAX_X, 3)
heli = Heli(MIN_X, MAX_Y //2)
# keep moving walls in a list
moving_walls = [Wall(MAX_X, r.randint((MIN_Y + 10), (MAX_Y - 10)))]
# main loop
while True:
# fill screen
wn.fill('black')
# editing objects to move
# blitting must happen before everything else
pg.draw.rect(wn,COLOR, heli.rect)
wn.blit(BG_IMG, (0,0))
wn.blit(heli.image, heli.rect)
heli.rect.y += move_amount
heli.rect.y += 1
# use a variable to control how much movement is happening
# movement happens continuosly
# if key down it oves if key up it doesnt
for wall in moving_walls :
wall.rect = pg.Rect(wall.xcor, wall.ycor, 3, SIZE)
pg.draw.rect(wn, COLOR, wall.rect)
wall.xcor -= SPEED
if wall.xcor < MIN_X + 10:
wall.xcor = MAX_X
wall.ycor = r.randint((MIN_Y), (MAX_Y - SIZE))
# drawing all objects back to the screen
pg.draw.rect(wn, COLOR, TOP)
pg.draw.rect(wn, COLOR, BOTTOM)
# update window
pg.display.update()
# event handling
for ev in pg.event.get():
if ev.type == pg.KEYDOWN:
if ev.key == pg.K_UP:
move_amount = -3
if ev.type == pg.KEYUP:
move_amount = 0
if ev.type == pg.QUIT:
pg.quit()
time.sleep(0.01)
You discard the transparency information of the image. You have to use convert_alpha instead of convert:
self.image = pg.image.load('art.png').convert()
self.image = pg.image.load('art.png').convert_alpha()
The pygame documentation notes that:
The returned Surface will contain the same color format, colorkey and alpha transparency as the file it came from. You will often want to call convert() with no arguments, to create a copy that will draw more quickly on the screen.
For alpha transparency, like in .png images, use the convert_alpha() method after loading so that the image has per pixel transparency.
See also How can I make an Image with a transparent Backround in Pygame?

Tkinter animation without canvas.move()

I would like to animate a canvas item WITHOUT (!!!) the canvas.move() function.
For example, I tried this:
see below:
coords is known
def getCoords(i):
....
return coords #a list
for i in range(4):
id=canvas.create_oval(getCoords(i))
canvas.after(1000)
canvas.delete(id)
canvas.update()
It does NOT work this way.
What is wrong? and/or
Where do I find an example?
You can both get and change the coordinates of a canvas object with the coords method.
Here's a complete example that grows an oval in the x direction by one pixel every 100ms:
import Tkinter as tk
def grow(canvas, item):
(x1,y1,x2,y2) = canvas.coords(item)
new_coords = (x1, y1, x2+1, y2)
canvas.coords(item, new_coords)
root.after(100, grow, canvas, item)
root = tk.Tk()
canvas = tk.Canvas(root)
canvas.pack(side="top", fill="both", expand=True)
item = canvas.create_oval(10, 10, 100, 100, outline="black", fill="red")
grow(canvas, item)
root.mainloop()

Tkinter - Creating a square that follows my mouse location

I have to create a square around my pointer on the canvas. And I want that square to follow my pointer as I move it around.
from tkinter import *
root = Tk()
f = Frame(root)
f.pack()
c = Canvas(f,bg = "black")
while root:
x = c.winfo_pointerx()
y = c.winfo_pointery()
c.create_rectangle(x,y,(x+10),(y+10),fill = "red")
root.mainloop()
root.mainloop()
Now when I run this the rectangle doesn't load.
Your method won't work, because once you call mainloop, it waits for the window to close, so it will never get past the first iteration of the loop. And if you remove the mainloop from the loop, it will never reach the mainloop after the (infinite) loop.
The proper way to do this is to use callback events. Also, you should move the rectangle, instead of creating a bunch of new ones. Try something like this:
def callback(event):
x, y = event.x, event.y
c.coords(rect, x - 10, y - 10, x + 10, y + 10)
root = Tk()
c = Canvas(root)
rect = c.create_rectangle(0, 0, 0, 0)
c.bind('<Motion>', callback)
c.pack()
root.mainloop()

Bouncing text animation issue in Pygame

I'm trying to code a program that can take text and animate it to bounce on a loop, like a ball bouncing to the floor. I used a similar piece of code I found a starting point as I'm still fairly new to Pygame (thank you Pete Shinners, whoever you are), but after updating the code and playing with it for a long time I still can't get it to blit to the screen correctly. The text starts above the rendered area and then gradually falls into view, but the top part of the text is cut off.
I've tried moving the blitted region around the window and resizing the rectangles and surface the program is using, but nothing seems to fix it.
import os, sys, math, pygame, pygame.font, pygame.image
from pygame.locals import *
def bounce():
# define constants
G = 0.98
FLOOR = 0
COEFFICIENT = 0.8
#define variables
ball = 500
direction = 'DOWN'
v = 0
count = 0
#create array to store data
array = [ball]
while True:
if count == 4:
return array
elif ball > FLOOR and direction == 'DOWN':
v += G
if (ball - v) >= FLOOR:
ball = ball - v
array.append(round(ball,2))
else:
ball = FLOOR
array.append(round(ball,2))
direction = 'UP'
v *= COEFFICIENT
count += 1
elif ball >= FLOOR and direction == 'UP':
v -= G
if (ball + v) >= FLOOR:
ball = ball + v
array.append(round(ball,2))
if v <= 0:
direction = 'DOWN'
else:
ball = FLOOR
array.append(ball)
direction = 'UP'
v *= COEFFICIENT
class textBouncy:
array = bounce()
def __init__(self, font, message, fontcolor, amount=10):
# Render the font message
self.base = font.render(message, 0, fontcolor)
# bounce amount (height)
self.amount = amount
#size = rect of maximum height/width of text
self.size = self.base.get_rect().inflate(0, amount).size
#normalise array to meet height restriction
self.array = [round(-x/(500/amount),2) for x in array]
def animate(self):
# create window surface s
s = pygame.Surface(self.size)
# height = max inflated height
height = self.size[1]
# define a step-sized rectangle in the location of the step
src = Rect(0, 0, self.base.get_width(), height)
# moves the message according to the array list.
dst = src.move(0, self.array[i])
if (i + 1) == len(self.array):
global i
i = 0
# blits the information onto the screen
s.blit(self.base, dst, src)
return s
entry_info = 'Bouncing ball text'
if __name__ == '__main__':
pygame.init()
#create text renderer
i = 0
array = bounce()
bigfont = pygame.font.Font(None, 60)
white = 255, 255, 255
renderer = textBouncy(bigfont, entry_info, white, 16)
text = renderer.animate()
#create a window the correct size
win = pygame.display.set_mode(text.get_size())
win.blit(text, (0, 10))
pygame.display.flip()
#run animation loop
finished = 0
while True:
pygame.time.delay(10)
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
text = renderer.animate()
i += 1
win.blit(text, (0, 10)) # blits the finished product from animate
pygame.display.flip()
(Quote) "it all comes down to math really" Kay so you need to - the y axis when you want to make it go up and + the x axis to make it go side ways you could make it go up and down will moveing it horizontally and then when it reaches a point it will stop moving horizontally and just bonce up and down +ing it more every time
That was my 100$ which took me 5 mins to write
After revisiting this I managed to work this out - I needed to add everything I blitted down to compensate for the bounce up. So in the __init__function:
self.array = [round(-x/(500/amount),2)**+self.amount** for x in array]
Works perfectly now :)

tkinter: displaying a square grid

I'm trying to use tkinter to periodically refresh a square grid. Each square in the grid is painted a certain color (say obtained from a given function); in the center of each square, a smaller circle is drawn (the color is also obtained from a function).
If I have to do it from scratch, perhaps there's an example or a standard recipe I can use?
Here's a quick hack showing how to draw a grid of rectangles and circles, and how to update the display once a second. If you've never programmed in tk before, it uses concepts that are no doubt foreign to you. You'll need to read up on canvas object ids and tags, as well as what the after command does.
import Tkinter as tk
import random
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.canvas = tk.Canvas(self, width=500, height=500, borderwidth=0, highlightthickness=0)
self.canvas.pack(side="top", fill="both", expand="true")
self.rows = 100
self.columns = 100
self.cellwidth = 25
self.cellheight = 25
self.rect = {}
self.oval = {}
for column in range(20):
for row in range(20):
x1 = column*self.cellwidth
y1 = row * self.cellheight
x2 = x1 + self.cellwidth
y2 = y1 + self.cellheight
self.rect[row,column] = self.canvas.create_rectangle(x1,y1,x2,y2, fill="blue", tags="rect")
self.oval[row,column] = self.canvas.create_oval(x1+2,y1+2,x2-2,y2-2, fill="blue", tags="oval")
self.redraw(1000)
def redraw(self, delay):
self.canvas.itemconfig("rect", fill="blue")
self.canvas.itemconfig("oval", fill="blue")
for i in range(10):
row = random.randint(0,19)
col = random.randint(0,19)
item_id = self.oval[row,col]
self.canvas.itemconfig(item_id, fill="green")
self.after(delay, lambda: self.redraw(delay))
if __name__ == "__main__":
app = App()
app.mainloop()

Resources