Trying to Make Slime Bombs and clear screen with spacebar - python

import pygame
pygame.init()
screen = pygame.display.set_mode((500,500))
clock = pygame.time.Clock()
isRunning = True
r = 255
g = 255
b = 255
color = (r, g, b)
gridDraw = 0
mouse_pressed = []
def grid():
x = 50
y = 50
for a in range(10):
for c in range(10):
pygame.draw.circle(screen, color, (x, y), 10)
x += 45
y += 45
x = 50
def slime(mPos):
x = 50
y = 50
green = (0,255,0)
for a in range(10):
for c in range(10):
if (mPos[0] <= x+10 and mPos[0] >= x-10) and\
(mPos[1] <= y+10 and mPos[1] >= y-10):
pygame.draw.circle(screen, green, (x, y), 10)
if x + 45 <= 455:
pygame.draw.circle(screen, green, (x+45, y), 10)
if x - 45 >= 45:
pygame.draw.circle(screen, green, (x-45, y), 10)
if y + 45 <= 455:
pygame.draw.circle(screen, green, (x, y+45), 10)
if y - 45 >= 45:
pygame.draw.circle(screen, green, (x, y-45), 10)
x += 45
y += 45
x = 50
def bomb(mouse):
for a in mouse:
slime(a)
pygame.draw.circle(screen, (0,200,150), a, 8)
while isRunning:
event = pygame.event.get()
for e in event:
if e.type == pygame.QUIT:
isRunning = False
elif e.type == pygame.KEYDOWN:
if e.type == pygame.K_ESCAPE:
isRunning = False
if e.type == pygame.MOUSEBUTTONDOWN:
mpos = pygame.mouse.get_pos()
mouse_pressed.append(mpos)
gridDraw = 0
if gridDraw == 0:
grid()
gridDraw += 1
bomb(mouse_pressed)
pygame.display.update()
pygame.quit()
So I have most of the first part of the assignment done except I can still press in the black spaces and I cant figure out how to not, I thought I had gotten the constraints correct but apparently not. Im thinking about making right click the medium or large bomb, would this be ideal?
This is my lab for class, I finally was able to complete the placing small bombs but I havent put in screen wipe after I press and I can still press in the black spaces.

First of all you have to draw the circles continuously in the application loop rather than the event loop.
Get the current position of the muse by pygame.mouse.get_pos and the state of the mouse buttons by pygame.mouse.get_pressed:
mpos = pygame.mouse.get_pos()
mpressed = pygame.mouse.get_pressed()
Evaluate if the left mouse button is pressed (mpressed[0]) and the distance of the mouse to the center of the circle is less than the radius of the circle (pygame.math.Vector2.distance_to):
if mpressed[0] and pygame.math.Vector2(x, y).distance_to(mpos) < 10:
# [...]
See the full example:
import pygame
screen = pygame.display.set_mode((500,500))
clock = pygame.time.Clock()
isRunning = True
x, y = 50, 50
r, g, b = 255, 255, 255
while isRunning:
#Time
deltaTime = clock.tick() / 1000
event = pygame.event.get()
#USER Events
for e in event:
if e.type == pygame.QUIT:
isRunning = False
mpos = pygame.mouse.get_pos()
mpressed = pygame.mouse.get_pressed()
#Draws a grid
for a in range(50):
x = a * 50
for c in range(50):
y = c * 50
color = (r, g, b)
if mpressed[0] and pygame.math.Vector2(x, y).distance_to(mpos) < 10:
color = (0, g, 0)
pygame.draw.circle(screen, color, (x, y), 10)
pygame.display.update()
pygame.quit()

I used the previous answer for the mouse position and added a board_state so that the Circle stays in your desired color.
import pygame
import numpy as np
import time
def set_color(board_state):
if not board_state:
return 255, 255, 255
else:
return 255, 0, 0
screen = pygame.display.set_mode((500,500))
clock = pygame.time.Clock()
isRunning = True
board_state = np.zeros((50, 50), dtype=np.bool)
pressed = True
while isRunning:
deltaTime = clock.tick() / 1000
event = pygame.event.get()
for e in event:
if e.type == pygame.QUIT:
isRunning = False
mpos = pygame.mouse.get_pos()
mpressed = pygame.mouse.get_pressed()
for a in range(board_state.shape[0]):
x = a * 50
for c in range(board_state.shape[1]):
y = c * 50
if mpressed[0] and pygame.math.Vector2(x, y).distance_to(mpos) < 10:
board_state[a, c] = not board_state[a, c]
print("Kick", a, c, board_state[a, c])
pressed = True
pygame.draw.circle(screen, set_color(board_state[a, c]), (x, y), 10)
if pressed:
pygame.display.update()
time.sleep(0.2)
pressed = False
pygame.quit()
You can change the color in the set_color function and I added a time.sleep which needs to be adjusted to the klicking length, because I had some problem with detecting a single klick with the trackpad.

Related

How to add a background and simple square as a character for a single screen platformer in pygame?

I’ve tried adding a background for the game, but as it pops up a second later it goes away It is white and everything else with the background is fine, but this is the only issue. Also, I tried to make a square to use as a character but it just won’t pop up.
import pygame, sys
from pygame.locals import QUIT
background_colour = (255, 255, 255)
BLUE = (0, 0, 255)
RED = (255, 0, 0)
(width, height) = (900, 450)
screen = pygame.display.set_mode((width, height))
screen.fill(background_colour)
pygame.display.flip()
pygame.display.update()
pygame.init()
dt = 0
x = 30
y = 30
w = 30
h = 30
a = 30
e = 30
l = 30
k = 30
def draw():
square = pygame.draw.rect(screen, RED, pygame.Rect(30, 30, 60, 60), 2)
pygame.display.flip()
def screen_bound():
global x
global y
global w
global h
global a
global e
global l
global k
# hit right wall
if ((x+w) > width):
x = width - w
# hit floor
if ((y+h) > height):
y = height - h
# hit left wall
if (x < 0):
x = 0
# hit roof
if (y < 0):
y = 0
def movement():
global x
global y
GRAVITY = .8
keys = pygame.key.get_pressed()
if keys[pygame.K_w]:
y = y - (.5*dt)
if keys[pygame.K_s]:
y = y + (.5*dt)
y = y = GRAVITY
def handle_events():
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
pass
def start():
draw()
movement()
screen_bound()
handle_events()
You need to implement an application loop where you move the objects and redraw the scene in each frame. The typical PyGame application loop has to:
limit the frames per second to limit CPU usage with pygame.time.Clock.tick
handle the events by calling 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 calling either pygame.display.update() or pygame.display.flip()
import pygame
pygame.init()
window = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()
background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 50, *background.get_size(), (128, 128, 128), (64, 64, 64)
tiles = [((x*ts, y*ts, ts, ts), c1 if (x+y) % 2 == 0 else c2) for x in range((w+ts-1)//ts) for y in range((h+ts-1)//ts)]
[pygame.draw.rect(background, color, rect) for rect, color in tiles]
rect = pygame.Rect(0, 0, 20, 20)
rect.center = window.get_rect().center
speed = 5
# main application loop
run = True
while run:
# limit frames per second
clock.tick(100)
# event loop
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
# update the game states and positions of objects dependent on the input
keys = pygame.key.get_pressed()
rect.x += (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * speed
rect.y += (keys[pygame.K_DOWN] - keys[pygame.K_UP]) * speed
border_rect = window.get_rect()
rect.clamp_ip(border_rect)
# clear the display and draw background
window.blit(background, (0, 0))
# draw the scene
pygame.draw.rect(window, (255, 0, 0), rect)
# update the display
pygame.display.flip()
pygame.quit()
exit()

I'm trying to detect the collision between two moving Rects in pygame

So I keep trying to detect two different moving rects in pygame colliding but its just constantly registering a collision. As in the console constantly print lose, but the enemy isn't even touching the ball. The effect I want is just a simple game where the ball has to avoid the tack so that the ball doesn't pop.
import pygame
import sys
import math
import random
pygame.init()
size = width, height = 800, 600
white = 255, 255, 255
red = 255, 0, 0
clock = pygame.time.Clock()
screen = pygame.display.set_mode(size)
character = pygame.image.load("intro_ball.gif")
charrect = character.get_rect()
x = 340
y = 480
enemy = pygame.image.load("ho.png")
enrect = enemy.get_rect()
ex = random.randint(0, 690)
ey = 0
lose = False
while 1:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT: sys.exit()
if ey >= 600 and lose != True:
ey = 0
ex = random.randint(0, 690)
collide = pygame.Rect.colliderect(charrect, enrect)
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT and x < 690:
x += 4
if event.key == pygame.K_LEFT and x > 0:
x -= 4
if collide:
lose = True
else: lose = False
if lose == True: print("lose")
ey += 2
screen.fill(white)
screen.blit(enemy, (ex, ey))
screen.blit(character, (x, y))
pygame.display.update()
pygame.Surface.get_rect.get_rect() returns a rectangle with the size of the Surface object, that always starts at (0, 0) since a Surface object has no position. A Surface is blit at a position on the screen. The position of the rectangle can be specified by a keyword argument. For example, the top lelft of the rectangle can be specified with the keyword argument topleft. These keyword argument are applied to the attributes of the pygame.Rect before it is returned (see pygame.Rect for a full list of the keyword arguments):
while 1:
# [...]
charrect = character.get_rect(topleft = (x, y))
enrect = enemy.get_rect(topleft = (ex, ey))
collide = pygame.Rect.colliderect(charrect, enrect)
# [...]
On the other hand you do not need the variables x, y, ex, ey at all. Use the features of the pygame.Rect objects.
Additionally read How can I make a sprite move when key is held down
Minimal example:
import pygame, sys, math, random
pygame.init()
size = width, height = 800, 600
white = 255, 255, 255
red = 255, 0, 0
clock = pygame.time.Clock()
screen = pygame.display.set_mode(size)
#character = pygame.image.load("intro_ball.gif")
character = pygame.Surface((20, 20))
character.fill((0, 255, 0))
charrect = character.get_rect(topleft = (340, 480))
#enemy = pygame.image.load("ho.png")
enemy = pygame.Surface((20, 20))
enemy.fill((255, 0, 0))
enrect = enemy.get_rect(topleft = (random.randint(0, 690), 0))
lose = False
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
charrect.x += (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * 4
charrect.clamp_ip(screen.get_rect())
enrect.y += 2
if enrect.y >= 600 and lose != True:
enrect.y = 0
enrect.x = random.randint(0, 690)
collide = pygame.Rect.colliderect(charrect, enrect)
if collide:
lose = True
else:
lose = False
if lose == True:
print("lose")
screen.fill(white)
screen.blit(enemy, enrect)
screen.blit(character, charrect)
pygame.display.update()
pygame.quit()
exit()

How to find the button on the entered position by user and change it's color in pygame?

import pygame
pygame.init()
width = 800
height = 600
running = True
screen = pygame.display.set_mode((width, height))
def drawGrid():
for y in range (50):
for x in range (50):
pygame.draw.rect (screen , ( 0 , 0 , 255 ) , ( ( x * 20 ) , ( y * 20 ) , 20 , 20 ) , 1)
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONUP:
pos = event.pos
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill((0, 0, 0))
drawGrid()
pygame.display.update()
So I basically create a grid and I also figured out how to get the position at which the user clicks the screen. What I want to do now is that using the position which the user has clicked, I want to find the corresponding button at that location. Once I find which button corresponds to the position at which the user clicked, I want to then change the color of the button to something else.
Edit:
import pygame
pygame.init()
width = 800
height = 600
running = True
screen = pygame.display.set_mode((width, height))
cell_size = 20
size_x = width // cell_size
size_y = height // cell_size
grid = [[False for y in range(size_y)] for x in range(size_x)]
def drawGrid():
mouse_pos = pygame.mouse.get_pos()
for y in range (size_y):
for x in range (size_x):
# rectangle and color of cell
cell_rect = pygame.Rect(x * 20, y * 20, 20, 20)
cell_color = (0, 0, 255)
# change color of button if the mouse is on the button
if cell_rect.collidepoint(mouse_pos):
cell_color = (255, 255, 255)
elif grid[x][y]:
cell_color = (0, 255, 255)
pygame.draw.rect(screen, cell_color, cell_rect, 1)
z = 0
while running and z < 2:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
cell_x, cell_y = event.pos[0] // cell_size, event.pos[1] // cell_size
grid[cell_x][cell_y] = not grid[cell_x][cell_y]
z += 1
screen.fill((0, 0, 0))
drawGrid()
pygame.display.update()
So basically what I want to do is that I want to stop clicking a rectangle in after two rectangles. So like if the user has already clicked two boxes (starting point and ending point), then I want to no longer add more boxes whenever the user clicks over a rectangle. I got it to work but the issue is that I only got it to work when two clicks are done the whole window closes. How can I fix this?
Never get the events (pygame.event.get()) multiple times in the application loop.
Use pygame.mouse.get_pos() to get the position of the mouse. Create a pygame.Rect object with the size of the cell and us collidepoint() to evaluate if the mouse is in the cell.
def drawGrid():
# get current mouse position
mosue_pos = pygame.mouse.get_pos()
for y in range (50):
for x in range (50):
# rectangle and color of cell
cell_rect = pygame.Rect(x * 20, y * 20, 20, 20)
cell_color = (0, 0, 255)
# change color of button if the mouse is on the button
if cell_rect.collidepoint(mosue_pos):
cell_color = (255, 255, 255)
pygame.draw.rect(screen, cell_color, cell_rect, 1)
If you want to change the cell on click, then you have to create a grid of states:
grid = [[False for y in range(size_y)] for x in range(size_x)]
Change the state of the cell when clicked on it:
if event.type == pygame.MOUSEBUTTONDOWN:
cell_x, cell_y = event.pos[0] // cell_size, event.pos[1] // cell_size
grid[cell_x][cell_y] = not grid[cell_x][cell_y]
Set the color dependent on the state of the cell:
if grid[x][y]:
cell_color = (255, 255, 255)
See the example:
import pygame
pygame.init()
width = 800
height = 600
running = True
screen = pygame.display.set_mode((width, height))
cell_size = 20
size_x = width // cell_size
size_y = height // cell_size
grid = [[False for y in range(size_y)] for x in range(size_x)]
def drawGrid():
for y in range(size_y):
for x in range(size_x):
# rectangle and color of cell
cell_rect = pygame.Rect(x * 20, y * 20, 20, 20)
cell_color = (0, 0, 255)
# change color of button if the mouse is on the button
if grid[x][y]:
cell_color = (255, 255, 255)
pygame.draw.rect(screen, cell_color, cell_rect, 1)
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
cell_x, cell_y = event.pos[0] // cell_size, event.pos[1] // cell_size
grid[cell_x][cell_y] = not grid[cell_x][cell_y]
screen.fill((0, 0, 0))
drawGrid()
pygame.display.update()
If you want to prevent to select cells, after 2 boxes have been clicked, then you have to evaluate the number of selected cells in the MOUSEBUTTONDOWN event:
z = 0
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
if z < 2:
cell_x, cell_y = event.pos[0] // cell_size, event.pos[1] // cell_size
grid[cell_x][cell_y] = not grid[cell_x][cell_y]
z += 1 if grid[cell_x][cell_y] else -1
# [...]

How would I detect NO KEY PRESS / No Input in Python?

I am making a simple box moving "game" to learn the pygame import, but I would like to set the box back to gray after there are no keys pressed.
import pygame
from pygame.locals import *
pygame.init()
width = 400
height = 400
screen = pygame.display.set_mode((width,height))
done = False
clock = pygame.time.Clock()
color = (150, 150, 150)
x = 0
y = 0
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
is_blue = not is_blue
pressed = pygame.key.get_pressed()
if pressed[pygame.K_UP] or pressed[pygame.K_w] and y > 0:
y -= 3
color = (0,0,255)
if pressed[pygame.K_DOWN] or pressed[pygame.K_s] and y+60 < height: #Adding 60 because height of block = 60
y += 3
color = (255,255,0)
if pressed[pygame.K_LEFT] or pressed[pygame.K_a] and x > 0:
x -= 3
color = (0,255,0)
if pressed[pygame.K_RIGHT] or pressed[pygame.K_d] and x+60 < width: #Adding 60 because width of block = 60
x += 3
color = (255,0,0)
screen.fill((0, 0, 0))
pygame.draw.rect(screen, color, pygame.Rect(x, y, 60, 60))
pygame.display.flip()
clock.tick(60)
Just add the set of the color=(150,150,150) inside the while loop(:
Put it as the first line inside the loop.

Is there an effiecient way of making a function to drag and drop multiple png's?

I'm making a chess game, but I'm completely stuck on the drag and drop element, there's a few guides out there but they're all either dragging shapes, or only dragging one image.
I've tried several variants of code, but all were 50+ lines just to move one .png and most were incredibly inefficient
pygame.init()
pygame.display.set_caption("Python Chess")
clock = pygame.time.Clock()
red = (213,43,67)
chew = pygame.image.load("chew.png")
gameDisplay.fill(red)
gameDisplay.blit(chew, (400, 400))
pygame.display.update()
drag = 0
if pygame.MOUSEBUTTONDOWN:
drag = 1
if pygame.MOUSEBUTTONUP:
drag = 0
gameExit = False
while not gameExit:
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameExit = True
Image simply doesn't drag.
Let's walk through this step by step.
Step 1: Let's start with a basic skeleton of every pygame game:
import pygame
def main():
screen = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
screen.fill(pygame.Color('grey'))
pygame.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()
We create a window and then start a loop to listen for events and drawing the window.
So far, so good. Nothing to see here, let's move on.
Step 2: a chess board
So, we want a chess game. So we need a board. We create a list of lists to represent our board, and we create a Surface that draws our board on the screen. We want to always seperate our game's state from the actual drawing functions, so we create a board variable and a board_surf.
import pygame
TILESIZE = 32
def create_board_surf():
board_surf = pygame.Surface((TILESIZE*8, TILESIZE*8))
dark = False
for y in range(8):
for x in range(8):
rect = pygame.Rect(x*TILESIZE, y*TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(board_surf, pygame.Color('black' if dark else 'white'), rect)
dark = not dark
dark = not dark
return board_surf
def create_board():
board = []
for y in range(8):
board.append([])
for x in range(8):
board[y].append(None)
return board
def main():
screen = pygame.display.set_mode((640, 480))
board = create_board()
board_surf = create_board_surf()
clock = pygame.time.Clock()
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
screen.fill(pygame.Color('grey'))
screen.blit(board_surf, (0, 0))
pygame.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()
Step 3: Where's the mouse?
We need to know which piece we want to select, so we have to translate the screen coordinates (where's the mouse relative to the window?) to the world coordinates (which square of the board is the mouse pointing to?).
So if the board is not located at the origin (the position (0, 0)), we also have to take this offset into account.
Basically, we have to substract that offset (which is the position of the board on the screen) from the mouse position (so we have the mouse position relative to the board), and divide by the size of the squares.
To see if this works, let's draw a red rectangle on the selected square.
import pygame
TILESIZE = 32
BOARD_POS = (10, 10)
def create_board_surf():
board_surf = pygame.Surface((TILESIZE*8, TILESIZE*8))
dark = False
for y in range(8):
for x in range(8):
rect = pygame.Rect(x*TILESIZE, y*TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(board_surf, pygame.Color('black' if dark else 'white'), rect)
dark = not dark
dark = not dark
return board_surf
def create_board():
board = []
for y in range(8):
board.append([])
for x in range(8):
board[y].append(None)
return board
def get_square_under_mouse(board):
mouse_pos = pygame.Vector2(pygame.mouse.get_pos()) - BOARD_POS
x, y = [int(v // TILESIZE) for v in mouse_pos]
try:
if x >= 0 and y >= 0: return (board[y][x], x, y)
except IndexError: pass
return None, None, None
def main():
screen = pygame.display.set_mode((640, 480))
board = create_board()
board_surf = create_board_surf()
clock = pygame.time.Clock()
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
piece, x, y = get_square_under_mouse(board)
screen.fill(pygame.Color('grey'))
screen.blit(board_surf, BOARD_POS)
if x != None:
rect = (BOARD_POS[0] + x * TILESIZE, BOARD_POS[1] + y * TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(screen, (255, 0, 0, 50), rect, 2)
pygame.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()
Step 4: Let's draw some pieces
Chess is boring without some pieces to move around, so let's create some pieces.
I just use a SysFont to draw some text instead of using real images, so everyone can just copy/paste the code and run it immediately.
We store a tuple (color, type) in the nested board list. Also, let's use some other colors for our board.
import pygame
TILESIZE = 32
BOARD_POS = (10, 10)
def create_board_surf():
board_surf = pygame.Surface((TILESIZE*8, TILESIZE*8))
dark = False
for y in range(8):
for x in range(8):
rect = pygame.Rect(x*TILESIZE, y*TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(board_surf, pygame.Color('darkgrey' if dark else 'beige'), rect)
dark = not dark
dark = not dark
return board_surf
def get_square_under_mouse(board):
mouse_pos = pygame.Vector2(pygame.mouse.get_pos()) - BOARD_POS
x, y = [int(v // TILESIZE) for v in mouse_pos]
try:
if x >= 0 and y >= 0: return (board[y][x], x, y)
except IndexError: pass
return None, None, None
def create_board():
board = []
for y in range(8):
board.append([])
for x in range(8):
board[y].append(None)
for x in range(0, 8):
board[1][x] = ('black', 'pawn')
for x in range(0, 8):
board[6][x] = ('white', 'pawn')
return board
def draw_pieces(screen, board, font):
for y in range(8):
for x in range(8):
piece = board[y][x]
if piece:
color, type = piece
s1 = font.render(type[0], True, pygame.Color(color))
s2 = font.render(type[0], True, pygame.Color('darkgrey'))
pos = pygame.Rect(BOARD_POS[0] + x * TILESIZE+1, BOARD_POS[1] + y * TILESIZE + 1, TILESIZE, TILESIZE)
screen.blit(s2, s2.get_rect(center=pos.center).move(1, 1))
screen.blit(s1, s1.get_rect(center=pos.center))
def draw_selector(screen, piece, x, y):
if piece != None:
rect = (BOARD_POS[0] + x * TILESIZE, BOARD_POS[1] + y * TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(screen, (255, 0, 0, 50), rect, 2)
def main():
pygame.init()
font = pygame.font.SysFont('', 32)
screen = pygame.display.set_mode((640, 480))
board = create_board()
board_surf = create_board_surf()
clock = pygame.time.Clock()
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
piece, x, y = get_square_under_mouse(board)
screen.fill(pygame.Color('grey'))
screen.blit(board_surf, BOARD_POS)
draw_pieces(screen, board, font)
draw_selector(screen, piece, x, y)
pygame.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()
Step 5: Drag'n'Drop
For drag and drop we need two things:
we have to change the state of your game (going into the "drag-mode")
eventhandling to enter and leave the "drag-mode"
It's actually not that complicated. To enter the "drag-mode", we just set a variable (selected_piece) when the MOUSEBUTTONDOWN event occurs. Since we already have the get_square_under_mouse function, it's easy to know if there's actually a piece under the mouse cursor.
if selected_piece is set, we draw a line and the piece under the mouse cursor, and we keep track of the current square under the cursor in case the MOUSEBUTTONUP event occurs. If that's the case, we swap the position of the piece in our board.
import pygame
TILESIZE = 32
BOARD_POS = (10, 10)
def create_board_surf():
board_surf = pygame.Surface((TILESIZE*8, TILESIZE*8))
dark = False
for y in range(8):
for x in range(8):
rect = pygame.Rect(x*TILESIZE, y*TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(board_surf, pygame.Color('darkgrey' if dark else 'beige'), rect)
dark = not dark
dark = not dark
return board_surf
def get_square_under_mouse(board):
mouse_pos = pygame.Vector2(pygame.mouse.get_pos()) - BOARD_POS
x, y = [int(v // TILESIZE) for v in mouse_pos]
try:
if x >= 0 and y >= 0: return (board[y][x], x, y)
except IndexError: pass
return None, None, None
def create_board():
board = []
for y in range(8):
board.append([])
for x in range(8):
board[y].append(None)
for x in range(0, 8):
board[1][x] = ('black', 'pawn')
for x in range(0, 8):
board[6][x] = ('white', 'pawn')
return board
def draw_pieces(screen, board, font, selected_piece):
sx, sy = None, None
if selected_piece:
piece, sx, sy = selected_piece
for y in range(8):
for x in range(8):
piece = board[y][x]
if piece:
selected = x == sx and y == sy
color, type = piece
s1 = font.render(type[0], True, pygame.Color('red' if selected else color))
s2 = font.render(type[0], True, pygame.Color('darkgrey'))
pos = pygame.Rect(BOARD_POS[0] + x * TILESIZE+1, BOARD_POS[1] + y * TILESIZE + 1, TILESIZE, TILESIZE)
screen.blit(s2, s2.get_rect(center=pos.center).move(1, 1))
screen.blit(s1, s1.get_rect(center=pos.center))
def draw_selector(screen, piece, x, y):
if piece != None:
rect = (BOARD_POS[0] + x * TILESIZE, BOARD_POS[1] + y * TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(screen, (255, 0, 0, 50), rect, 2)
def draw_drag(screen, board, selected_piece, font):
if selected_piece:
piece, x, y = get_square_under_mouse(board)
if x != None:
rect = (BOARD_POS[0] + x * TILESIZE, BOARD_POS[1] + y * TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(screen, (0, 255, 0, 50), rect, 2)
color, type = selected_piece[0]
s1 = font.render(type[0], True, pygame.Color(color))
s2 = font.render(type[0], True, pygame.Color('darkgrey'))
pos = pygame.Vector2(pygame.mouse.get_pos())
screen.blit(s2, s2.get_rect(center=pos + (1, 1)))
screen.blit(s1, s1.get_rect(center=pos))
selected_rect = pygame.Rect(BOARD_POS[0] + selected_piece[1] * TILESIZE, BOARD_POS[1] + selected_piece[2] * TILESIZE, TILESIZE, TILESIZE)
pygame.draw.line(screen, pygame.Color('red'), selected_rect.center, pos)
return (x, y)
def main():
pygame.init()
font = pygame.font.SysFont('', 32)
screen = pygame.display.set_mode((640, 480))
board = create_board()
board_surf = create_board_surf()
clock = pygame.time.Clock()
selected_piece = None
drop_pos = None
while True:
piece, x, y = get_square_under_mouse(board)
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
if e.type == pygame.MOUSEBUTTONDOWN:
if piece != None:
selected_piece = piece, x, y
if e.type == pygame.MOUSEBUTTONUP:
if drop_pos:
piece, old_x, old_y = selected_piece
board[old_y][old_x] = 0
new_x, new_y = drop_pos
board[new_y][new_x] = piece
selected_piece = None
drop_pos = None
screen.fill(pygame.Color('grey'))
screen.blit(board_surf, BOARD_POS)
draw_pieces(screen, board, font, selected_piece)
draw_selector(screen, piece, x, y)
drop_pos = draw_drag(screen, board, selected_piece, font)
pygame.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()
Of course there's a lot that can be improved (like using better datatypes than tuples, extract common logic into functions etc), but this should give you a good start on how to implement such things.
Always keep in mind:
write a single game loop that handles events, game logic, and drawing
make sure to only call pygame.display.flip once per frame
seperate your game state from your drawing functions
never call time.sleep or pygame.time.wait
use the build-in classes like Vector2 and Rect, they'll make your live easier (I didn't the Sprite class in this code, but it's also very usefull)
use functions to clean up your code
avoid global variables, except for constants
PyGame is low-level library, not game engine, and you have to make almost all from scratch.
This example drags two images but for more images it could use list or pygame.sprite.Group with pygame.sprite.Sprites and then code is getting longer and longer but as I said PyGame is not game engine like ie. Godot Engine (which uses language similar to Python). Maybe with Pyglet could be easier because you don't have to write mainloop from scratch but it still need some work. In PyGame you have to write from scratch even main element - mainloop.
import pygame
# --- constants ---
RED = (213, 43, 67)
# --- main ---
pygame.init()
screen = pygame.display.set_mode((800,600))
chew1 = pygame.image.load("chew.png")
chew1_rect = chew1.get_rect(x=400, y=400)
chew2 = pygame.image.load("chew.png") # use different image
chew2_rect = chew1.get_rect(x=200, y=200)
drag = 0
# --- mainloop ---
clock = pygame.time.Clock()
game_exit = False
while not game_exit:
# - events -
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_exit = True
elif event.type == pygame.MOUSEBUTTONDOWN:
drag = 1
elif event.type == pygame.MOUSEBUTTONUP:
drag = 0
elif event.type == pygame.MOUSEMOTION:
if drag:
chew1_rect.move_ip(event.rel)
chew2_rect.move_ip(event.rel)
# - draws -
screen.fill(RED)
screen.blit(chew1, chew1_rect)
screen.blit(chew2, chew2_rect)
pygame.display.update()
# - FPS -
clock.tick(30)
# --- end ---
pygame.quit()
EDIT: the same with Group and Sprite and now you have to only add images to group items and rest of the code doesn't need to be changed.
import pygame
# --- constants ---
RED = (213, 43, 67)
# --- classes ---
class Item(pygame.sprite.Sprite):
def __init__(self, image, x, y):
super().__init__()
self.image = pygame.image.load(image)
self.rect = self.image.get_rect(x=x, y=y)
def update(self, rel):
self.rect.move_ip(rel)
# --- main ---
pygame.init()
screen = pygame.display.set_mode((800,600))
items = pygame.sprite.Group(
Item("chew.png", 200, 200),
Item("chew.png", 400, 200),
Item("chew.png", 200, 400),
Item("chew.png", 400, 400),
)
drag = 0
# --- mainloop ---
clock = pygame.time.Clock()
game_exit = False
while not game_exit:
# - events -
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_exit = True
elif event.type == pygame.MOUSEBUTTONDOWN:
drag = 1
elif event.type == pygame.MOUSEBUTTONUP:
drag = 0
elif event.type == pygame.MOUSEMOTION:
if drag:
items.update(event.rel)
# - draws -
screen.fill(RED)
items.draw(screen)
pygame.display.update()
# - FPS -
clock.tick(30)
# --- end ---
pygame.quit()
EDIT: this version use Group to move only clicked image(s). If you click in place where two (or more) images are overlapped then it will drag two (or more) images.
import pygame
# --- constants ---
RED = (213, 43, 67)
# --- classes ---
class Item(pygame.sprite.Sprite):
def __init__(self, image, x, y):
super().__init__()
self.image = pygame.image.load(image)
self.rect = self.image.get_rect(x=x, y=y)
def update(self, rel):
self.rect.move_ip(rel)
# --- main ---
pygame.init()
screen = pygame.display.set_mode((800,600))
items = pygame.sprite.Group(
Item("chew.png", 150, 50),
Item("chew.png", 400, 50),
Item("chew.png", 150, 300),
Item("chew.png", 400, 300),
)
dragged = pygame.sprite.Group()
# --- mainloop ---
clock = pygame.time.Clock()
game_exit = False
while not game_exit:
# - events -
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_exit = True
elif event.type == pygame.MOUSEBUTTONDOWN:
dragged.add(x for x in items if x.rect.collidepoint(event.pos))
elif event.type == pygame.MOUSEBUTTONUP:
dragged.empty()
elif event.type == pygame.MOUSEMOTION:
dragged.update(event.rel)
# - draws -
screen.fill(RED)
items.draw(screen)
pygame.display.update()
# - FPS -
clock.tick(30)
# --- end ---
pygame.quit()
EDIT: similar program in Pyglet - it moves two images with less code.
Pyglet has point (0,0) in left bottom corner.
To create red background it has to draw rectangle (QUADS) in OpenGL.
import pyglet
window = pyglet.window.Window(width=800, height=600)
batch = pyglet.graphics.Batch()
items = [
pyglet.sprite.Sprite(pyglet.resource.image('chew.png'), x=200, y=100, batch=batch),
pyglet.sprite.Sprite(pyglet.resource.image('chew.png'), x=400, y=300, batch=batch),
]
#window.event
def on_draw():
#window.clear()
pyglet.graphics.draw(4, pyglet.gl.GL_QUADS, ('v2f', [0,0, 800,0, 800,600, 0,600]), ('c3B', [213,43,67, 213,43,67, 213,43,67, 213,43,67])) #RED = (213, 43, 67)
batch.draw()
#window.event
def on_mouse_drag(x, y, dx, dy, buttons, modifiers):
for i in items:
i.x += dx
i.y += dy
pyglet.app.run()

Categories

Resources