Related
Cannot figure out how to add anti-aliasing & color the circle produced from Mid-Point Circle Drawing Algorithm. The following is my code, should I implement it using another library?
from pygame import gfxdraw
import sys,pygame
pygame.init()
screen = pygame.display.set_mode((400,400))
screen.fill((0,0,0))
pygame.display.flip()
def circle(radius,offset):
x,y = 0,radius
plotCircle(x,y,radius,offset)
def symmetry_points(x,y,offset):
gfxdraw.pixel(screen,x+offset,y+offset,(255,255,255))
gfxdraw.pixel(screen,-x+offset,y+offset,(255,255,255))
gfxdraw.pixel(screen,x+offset,-y+offset,(255,255,255))
gfxdraw.pixel(screen,-x+offset,-y+offset,(255,255,255))
gfxdraw.pixel(screen,y+offset,x+offset,(255,255,255))
gfxdraw.pixel(screen,-y+offset,x+offset,(255,255,255))
gfxdraw.pixel(screen,y+offset,-x+offset,(255,255,255))
gfxdraw.pixel(screen,-y+offset,-x+offset,(255,255,255))
pygame.display.flip()
def plotCircle(x,y,radius,offset):
d = 5/4.0 - radius
symmetry_points(x,y,radius+offset)
while x < y:
if d < 0:
x += 1
d += 2*x + 1
else:
x += 1
y -= 1
d += 2*(x-y) + 1
symmetry_points(x,y,radius+offset)
circle(100,25) # circle(radius,<offset from edge>)
pygame.display.flip()
while 1:
for event in pygame.event.get():
if event.type == pygame.QUIT: sys.exit()
I'm making a very simple pixel art software in pygame. My logic was creating a grid class, which has a 2D list, containing 0's. When I click, the grid approximates the row and column selected, and mark the cell with a number, corresponding to the color. For simplicity, let's say '1'.
The code works correctly, but It's slow. If the number of rows and columns is less or equal than 10, It works perfectly, but if It's more, it's very laggy.
I think the problem is that I'm updating the entire screen everytime, and, since the program has to check EVERY cell, It can't handle a bigger list
import pygame
from grid import Grid
from pincel import Pincel
from debugger import Debugger
from display import Display
from pygame.locals import *
pygame.init()
pygame.mixer_music.load("musica/musica1.wav")
pygame.mixer_music.play(-1)
width = 1300
height = 1300
screen = pygame.display.set_mode((1366, 768), pygame.RESIZABLE)
pygame.display.set_caption("SquareDraw")
#Grid Creator
numberOfRows = 25
numberOfColumns = 25
grid = Grid(numberOfRows, numberOfColumns)
# Medidas
basicX = width / numberOfColumns
basicY = height / numberOfRows
#Tool Creator
pincel = Pincel(2)
#variáveis de controle
running = 1
#Initial values
grid.equipTool(pincel)
#variáveis de controle de desenho
clicking = 0
def drawScreen(screen, grid, rows, columns, basicX, basicY):
for i in range(rows):
for j in range(columns):
if grid[i][j]:
print('yes')
print(i, j)
pygame.draw.rect(screen, (0, 0, 0), (j * basicX, i * basicY, basicX, basicY))
while running:
screen.fill((255, 255, 255))
Display.drawScreen(screen, grid.board, grid.getRows(), grid.getColumns(), basicX, basicY)
pygame.display.flip()
events = pygame.event.get()
for event in events:
if (event.type == pygame.QUIT) or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
running = 0
if event.type == pygame.MOUSEBUTTONDOWN or clicking:
clicking = 1
x, y = pygame.mouse.get_pos()
Debugger.printArray2D(grid.board)
print('')
xInGrid = int(x / basicX)
yInGrid = int(y / basicY)
grid.ferramenta.draw(grid.board, xInGrid, yInGrid)
Debugger.printArray2D(grid.board)
print('')
if event.type == pygame.MOUSEBUTTONUP:
clicking = 0
if event.type == pygame.VIDEORESIZE:
width = event.w
height = event.h
basicX = width / numberOfColumns
basicY = height / numberOfRows
print(width, height)
pygame.quit()
The class grid contains the 2D list. The class "Pincel" marks the cells and The class "Debugger" is just for printing lists or anything related to debugging.
Is there a way to update only the part of the screen that was changed? If so, how can I apply that in my logic?
Thanks in advance :)
A few things:
Use the grid array to store the on\off blocks of the screen. It only gets read when the screen is resized and needs a full redraw
When a new rectangle is turned on, draw the rectangle directly in the event handler and update the grid array. There is no need to redraw the entire screen here.
In the resize event, reset the screen mode to the new size then redraw the entire screen using the grid array. This is the only time you need to do a full redraw.
Here is the updated code:
import pygame
#from grid import Grid
#from pincel import Pincel
#from debugger import Debugger
#from display import Display
from pygame.locals import *
pygame.init()
#pygame.mixer_music.load("musica/musica1.wav")
#pygame.mixer_music.play(-1)
width = 1000
height = 1000
screen = pygame.display.set_mode((width, height), pygame.RESIZABLE)
pygame.display.set_caption("SquareDraw")
#Grid Creator
numberOfRows = 250
numberOfColumns = 250
#grid = Grid(numberOfRows, numberOfColumns)
grid = [[0 for x in range(numberOfRows)] for y in range(numberOfColumns)] # use array for grid: 0=white, 1=black
# Medidas
basicX = width / numberOfColumns
basicY = height / numberOfRows
#Tool Creator
#pincel = Pincel(2)
#xx
running = 1
#Initial values
#grid.equipTool(pincel)
#xx
clicking = 0
def drawScreen(screen, grid, basicX, basicY): # draw rectangles from grid array
for i in range(numberOfColumns):
for j in range(numberOfRows):
if grid[i][j]:
#print('yes')
#print(i, j)
pygame.draw.rect(screen, (0, 0, 0), (j * basicX, i * basicY, basicX, basicY))
screen.fill((255, 255, 255)) # start screen
while running:
events = pygame.event.get()
for event in events:
if (event.type == pygame.QUIT) or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
running = 0
if event.type == pygame.MOUSEBUTTONDOWN or clicking: # mouse button down
clicking = 1
x, y = pygame.mouse.get_pos()
#Debugger.printArray2D(grid.board)
#print('')
xInGrid = int(x / basicX)
yInGrid = int(y / basicY)
grid[yInGrid][xInGrid] = 1 # save this point = 1, for screen redraw (if resize)
pygame.draw.rect(screen, (0, 0, 0), (xInGrid * basicX, yInGrid * basicY, basicX, basicY)) # draw rectangle
#grid.ferramenta.draw(grid.board, xInGrid, yInGrid)
#Debugger.printArray2D(grid.board)
#print('')
pygame.display.flip() # update screen
if event.type == pygame.MOUSEBUTTONUP:
clicking = 0
if event.type == pygame.VIDEORESIZE: # screen resized, must adjust grid height, width
width = event.w
height = event.h
basicX = width / numberOfColumns
basicY = height / numberOfRows
#print(width, height)
screen = pygame.display.set_mode((width, height), pygame.RESIZABLE) # reset screen with new height, width
screen.fill((255, 255, 255)) # clear screen
drawScreen(screen, grid, basicX, basicY) # redraw rectangles
pygame.display.flip() # update screen
pygame.quit()
making a square move in a grid
import pygame
from pygame.locals import *
pygame.init()
clock = pygame.time.Clock()
w = 1008
h = 640
left = 16
top = 16
width = 16
height = 16
YELLOW = (255, 255, 55)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
bg = (255, 255, 255)
x = 0
y = 0
screen = pygame.display.set_mode((w, h))
gameExit = False
while not gameExit:
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameExit = True
if event.type == KEYDOWN:
if event.key == K_DOWN:
top += 16
if event.key == K_UP:
top -= 16
if event.key == K_RIGHT:
left += 16
if event.key == K_LEFT:
left -= 16
x += 16
y += 16
screen.fill(BLACK)
pygame.draw.rect(screen, [255, 255, 55], [left, top, width, height], 0)
pygame.draw.line(screen, bg, (w - 16, y), (16, y), 1)
pygame.draw.line(screen, bg, (x, h - 16), (y, 16), 1)
pygame.display.flip()
pygame.quit()
at the moment if I # out screen.fill() grid appears on the screen and the rect moves with a tail behind, if I take away the # from the screen.fill() grid disappears but rect moves correct with no tail I want both to happen.
The issue with your code is that it isn't doing what you might expect. Every time you iterate through your while loop, you increment the x and y values by 16 and draw a single horizontal and vertical line. This appears to draw a grid, because the screen is NOT cleared automatically between your while loop iterations. However, as a result, after a while, your x and y values increase to the point that you start drawing more and more grid lines that are off the screen (try to print out the start and end points for these lines in your loop if this is unclear)!
When you add the screen.fill(BLACK) call before you start drawing grid lines, you basically clear the entire screen. Since you do not redraw your old grid lines, only the next pair of grid lines are drawn and the rest are lost. Since the new pair of grid lines eventually goes off the screen (as described in the first paragraph), your code does not appear to draw any grid lines.
When you comment out screen.fill(BLACK) instead, previous drawings are not cleared. All of the grid is drawn over multiple while loop iterations (in addition to wastefully drawing new grid lines off screen), as is all of the previously positions your player is drawn at.
To solve this, your game drawing loop should be structured like this:
Clear the screen.
Draw the entire grid, not just two lines at a time. Do this in a function (e.g. draw_grid).
Draw the rectangle at the new player position. Do this in a function as well (e.g. draw_player).
Rinse and repeat next iteration!
Implementing these small changes, your code could look like this:
import pygame
from pygame.locals import *
def draw_grid():
screen.fill(BLACK)
for y in range(height, h, height):#horizontal lines
pygame.draw.line(screen, bg, (width, y), (w - width, y), 1)
for x in range(width, w, width):#vertical lines
pygame.draw.line(screen, bg, (x, height), (x, h - height), 1)
def draw_player():
pygame.draw.rect(screen, [255, 255, 55], [left, top, width, height], 0)
pygame.init()
clock = pygame.time.Clock()
w = 1008
h = 640
left = 16
top = 16
width = 16
height = 16
YELLOW = (255, 255, 55)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
bg = (255, 255, 255)
x = 0
y = 0
screen = pygame.display.set_mode((w, h))
gameExit = False
while not gameExit:
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameExit = True
if event.type == KEYDOWN:
if event.key == K_DOWN:
top += 16
if event.key == K_UP:
top -= 16
if event.key == K_RIGHT:
left += 16
if event.key == K_LEFT:
left -= 16
draw_grid()
draw_player()
pygame.display.flip()
pygame.quit()
That being said, there is plenty of room for improvement for this code, which I will leave up to you. The big issue is that you are using a bunch of global variables. These are basically all of the variables listed before your while loop. Use of such global variables is "evil" when they are mutable. As your code gets bigger, it will become more difficult to reason what functions are changing these variables, since every function could potentially have access to these variables.
I have a task, where I have a square 3X3 and for every click in the small square, this square will paint in red. Here is my code yet. I think I did something wrong in my first while loop, but I'm not sure. Please help me.
import pygame
pygame.init()
#create a screen
screen = pygame.display.set_mode((400, 400))
#colors
white = [255, 255, 255]
red = [255, 0, 0]
x = 0
y = 0
#create my square
for j in range(3):
for i in range(3):
pygame.draw.rect(screen, white, (x, y, 30, 30), 1)
x += 30
if x == 90:
x = 0
y += 30
pygame.display.flip()
running = 1
while running:
event = pygame.event.poll()
#found in what position my mouse is
if event.type == pygame.QUIT:
running = 0
elif event.type == pygame.MOUSEMOTION:
print("mouse at (%d, %d)" % event.pos)
mouse = pygame.mouse.get_pos()
click = pygame.mouse.get_pressed()
#mouse click
if click[0] == 1 and x in range(30) and y in range (30):
pygame.draw.rect(screen, red, (30, 30 , 29 ,29))
while pygame.event.wait().type != pygame.QUIT:
pygame.display.change()
You have to update your screen each time when you draw or do something in screen. So, put this line under your first while loop.
pygame.display.flip()
In your condition you was checking x and y and which are not mouse position.
if click[0] == 1 and x in range(30) and y in range (30):
Check your mouse position in range(90) because of you have three rectangular and the are 30x30.
if click[0] == 1 and mouse[0] in range(90) and mouse[1] in range (90):
Then, set your rectangular start position to fill the mouse point.
rect_x = 30*(mouse[0]//30) # set start x position of rectangular
rect_y = 30*(mouse[1]//30) # set start y position of rectangular
You can edit your code with this.
while running:
pygame.display.flip()
event = pygame.event.poll()
#found in what position my mouse is
if event.type == pygame.QUIT:
running = 0
elif event.type == pygame.MOUSEMOTION:
print("mouse at (%d, %d)" % event.pos)
mouse = pygame.mouse.get_pos()
click = pygame.mouse.get_pressed()
#mouse click
if click[0] == 1 and mouse[0] in range(90) and mouse[1] in range (90):
'''
rect_x = 30*(0//30) = 0
rect_y = 30*(70//30) = 60
'''
rect_x = 30*(mouse[0]//30) # set start x position of rectangular
rect_y = 30*(mouse[1]//30) # set start y position of rectangular
pygame.draw.rect(screen, red, (rect_x, rect_y , 30 , 30)) # rectangular (height, width), (30, 30)
The program works fine like this but, I don't understand why it needs the useless for event in pygame.event.get(): None in the gameOver while statement inside game_loop. If you could find a way to delete it or explain why it doesn't run without it, that would be great!
import pygame, time, random
pygame.init()
# SOUND/TEXTURES
icon = pygame.image.load("textures\snakeicon.png")
pygame.display.set_icon(icon)
# VARIABLES
white = (255, 255, 255)
black = (0, 0, 0)
red = (200, 0, 0)
green = (0, 155, 0)
bright_green = (0, 250, 0)
bright_red = (255, 0, 0)
font_size = 50
font = pygame.font.SysFont(None, font_size)
# FUNCTIONS
def text_objects(text, font):
textSurface = font.render(text, True, black)
return textSurface, textSurface.get_rect()
def button(msg, x, y, w, h, ic, ac, action=None):
mouse = pygame.mouse.get_pos()
click = pygame.mouse.get_pressed()
if x + w > mouse[0] > x and y + h > mouse[1] > y:
pygame.draw.rect(gameWindow, ac, (x, y, w, h))
if click[0] == 1 and action != None:
if action == "play":
game_loop()
elif action == "quit":
gameRun = False
gameWindow.fill(white)
message_to_screen("Closing Game...", black, 280, 280)
pygame.display.update()
time.sleep(1)
pygame.quit()
quit()
else:
pygame.draw.rect(gameWindow, ic, (x, y, w, h))
smallText = pygame.font.Font("freesansbold.ttf", 20)
textSurf, textRect = text_objects(msg, smallText)
textRect.center = ((x + (w / 2)), (y + (h / 2)))
gameWindow.blit(textSurf, textRect)
def snake(rect_x, rect_y, block_size):
pygame.draw.rect(gameWindow, green, [rect_x, rect_y, block_size, block_size])
def message_to_screen(msg, color, x, y):
screen_text = font.render(msg, True, color)
gameWindow.blit(screen_text, [x, y])
# WINDOW/SURFACE
display_w = 800
display_h = 600
window_title = "Window"
gameWindow = pygame.display.set_mode((display_w, display_h))
pygame.display.set_caption(window_title)
# FPS/Clock
clock = pygame.time.Clock()
# Game Loop
def game_loop():
# RECT OPTIONS
moveSpeed = 10
block_size = 10
rect_x = display_w / 2
rect_y = display_h / 2
change_x = 0
change_y = 0
randApplex = round(random.randrange(0, display_w - block_size) / 10.0) * 10.0
randAppley = round(random.randrange(0, display_h - block_size) / 10.0) * 10.0
global gameRun, gameOver
gameRun = True
gameOver = False
while gameRun:
while gameOver:
gameRun = False
gameWindow.fill(white)
# button(msg, x, y, w, h, ic, ac, action=None)
message_to_screen("Game Over!", red, 300, 300)
button("Restart", 150, 450, 100, 50, green, bright_green, "play")
button("Quit", 550, 450, 100, 50, red, bright_red, "quit")
pygame.display.update()
# RIGHT HERE!
for event in pygame.event.get():
None
# RIGHT THERE!
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameRun = False
gameOver = False
gameWindow.fill(white)
message_to_screen("Closing Game...", black, 280, 280)
pygame.display.update()
time.sleep(1)
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_w:
change_y = -moveSpeed
change_x = 0
elif event.key == pygame.K_s:
change_y = moveSpeed
change_x = 0
elif event.key == pygame.K_a:
change_x = -moveSpeed
change_y = 0
elif event.key == pygame.K_d:
change_x = moveSpeed
change_y = 0
# BOARDER CRASH
if rect_x >= display_w or rect_x < 0 or rect_y >= display_h or rect_y < 0:
gameOver = True
# LOGIC
rect_x += change_x
rect_y += change_y
if rect_x == randApplex and rect_y == randAppley:
randApplex = round(random.randrange(0, display_w - block_size) / 10.0) * 10.0
randAppley = round(random.randrange(0, display_h - block_size) / 10.0) * 10.0
# RENDER
gameWindow.fill(white)
pygame.draw.rect(gameWindow, red, [randApplex, randAppley, block_size, block_size])
snake(rect_x, rect_y, block_size)
pygame.display.update()
clock.tick(15)
message_to_screen("You Lose!", red, 325, 300)
pygame.display.update()
time.sleep(1)
message_to_screen("Closing Game!", black, 280, 350)
pygame.display.update()
time.sleep(1)
# QUIT
pygame.quit()
quit()
game_loop()
Basically, the OS expects pygame to handle events during your program. If the OS notice that events aren't handled, it'll alert the user. The program doesn't actually crash or freeze, the OS is just saying that your program has become unresponsive (which it has because you're not responding to any user events), but it still works.
When your game is entering small scenes you might think that you don't need to handle events, but there is one event you should always check for: the pygame.QUIT event (sent when the user press the close button at the top corner). In your example you're not allowing the user to quit during the game over sequence (you're providing the player a button to click but a user would also expect clicking the close button would close the game as well).
Another reason is that the event queue is constantly filling up. So if the user would spam multiple keys and press multiple areas with the mouse, nothing would happen until he/she enters the game again (where you have an event loop). Then every event would be executed. Therefore it's important to regularly empty the queue. The queue is emptied every time you call pygame.event.get() or pygame.event.clear().
The function pygame.event.pump() is the function that put all events into the event queue (it doesn't clear the previous events, it just adds). The event queue won't be filled/updated with any events if the function isn't called. However, the function is implicitly called inside the functions pygame.event.get(), pygame.event.clear(), pygame.event.poll(), pygame.event.wait() and pygame.event.peek(), so there is rarely a reason to call it explicitly. If you are sure you don't want to handle events at some time, you could use pygame.event.clear() so the event queue is empty when you start processing events again. If you don't want to handle events at all, then use pygame.event.pump().
You can replace it with pygame.event.pump(). The documentation explains why you need to call this or have to use an event loop each frame.
For each frame of your game, you will need to make some sort of call to the event queue. This ensures your program can internally interact with the rest of the operating system. If you are not using other event functions in your game, you should call pygame.event.pump() to allow pygame to handle internal actions.
This function is not necessary if your program is consistently processing events on the queue through the other pygame.eventpygame module for interacting with events and queues functions.
There are important things that must be dealt with internally in the event queue. The main window may need to be repainted or respond to the system. If you fail to make a call to the event queue for too long, the system may decide your program has locked up.
Every process with a GUI needs to maintain a Message Pump (at least in Windows it's critical)
Most of the time, your GUI framework (QT for example) will maintain the pump for you - and will dispatch matching events for your callbacks (mouse clicks, keyboard etc..).
I guess that pygame wants to give you some finer control about how you handle the messages (if I'm not mistaken, game engines would want to wait and pump all events with each rendering of a single frame).