Python/Pygame - Importing spritesheet as an array of indexed rects [duplicate] - python

So I've been testing out this code, I found a tutorial on how I can add spritesheets in pygame and decided to try this one:
https://www.spriters-resource.com/3ds/dragonballzextremebutoden/sheet/67257/
I did as the video said and counted the columns and rows, this is my code:
pygame.init()
CLOCK = pygame.time.Clock()
DS = pygame.display.set_mode((W, H))
FPS = 60
class spritesheet:
def __init__(self, filename, cols, rows):
self.sheet = pygame.image.load(filename).convert_alpha()
self.cols = cols
self.rows = rows
self.totalCellCount = cols * rows
self.rect = self.sheet.get_rect()
w = self.cellWidth = self.rect.width / cols
h = self.cellHeight = self.rect.height / rows
hw, hh = self.cellCenter = (w / 2, h / 2)
self.cells = list([(index % cols * w, index / cols * h, w, h) for index in range(self.totalCellCount)])
self.handle = list([
(0,0), (-hw, 0), (-w, 0),
(0, -hh), (-hw, -hh), (-w, -hh),
(0, -h), (-hw, -h), (-w, -h),])
def draw(self, surface, cellIndex, x, y, handle = 0):
surface.blit(self.sheet,
(x + self.handle[handle][0], y + self.handle[handle][1],
self.cells[cellIndex][2], self.cells[cellIndex][3]))
s = spritesheet('Number18.png', 58, 6)
CENTER_HANDLE = 6
Index = 0
#mainloop
run = True
while run:
s.draw(DS, Index % s.totalCellCount, HW, HH, CENTER_HANDLE)
Index +=1
#pygame.draw.circle(DS, WHITE, (HW, HW), 20, 10)
DS.blit(bg,(0,0))
pygame.display.update()
CLOCK.tick(FPS)
DS.fill(BLACK)
The line s = spritesheet("Number18.png", 58, 6) has the numbers 58, 6 which are basically the number of rows and columns I counted on this spritesheet block, but I'm getting problems such as the pygame window on "not responding", the image does not load up and I can't move the pygame screen.

I'm getting problems such as the pygame window on "not responding", [...]
The fist thing what you've to do is to add en event loop to the main loop of the application.
pygame.event removes a pending event message from the queue and returns it.
At least you should handle the QUIT event. Set the value of control variable for the main loop False:
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
The tiles in the sprite sheet don't have equal size. Limit the cells list to some parts of the sheet which have equal size.
Try the following:
class spritesheet:
def __init__(self, filename, py, tw, th, tiles):
self.sheet = pygame.image.load(filename).convert_alpha()
self.py = py
self.tw = tw
self.th = th
self.totalCellCount = tiles
self.rect = self.sheet.get_rect()
w, h = tw, th
hw, hh = self.cellCenter = (w / 2, h / 2)
self.cells = [(1+i*tw, self.py, tw-1, th-1) for i in range(tiles)]
self.handle = list([
(0,0), (-hw, 0), (-w, 0),
(0, -hh), (-hw, -hh), (-w, -hh),
(0, -h), (-hw, -h), (-w, -h),])
s = spritesheet('Number18.png', 1085, 80, 134, 8)
[...] the image does not load up [...]
Make sure that the image is located int the working directory of the application.
If you want to draw a sub-image of a spritsheet then you've to set the area parameter (3rd parameter) of pygame.Surface.blit to the rectangle area of the sub-image:
def draw(self, surface, cellIndex, x, y, handle = 0):
hdl = self.handle[handle]
surface.blit(self.sheet, (x + hdl[0], y + hdl[1]), area=self.cells[cellIndex])
[...] I can't move [...]
You have to change the position of the sprite. Handle the KEYDOWN event. Store the position of the sprite (px, py). Change the position when the K_UP, K_DOWN, K_LEFT or K_RIGHT key is pressed:
run = True
px, py, speed = HW, HH, 10
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
py -= speed
elif event.key == pygame.K_DOWN:
py += speed
elif event.key == pygame.K_LEFT:
px -= speed
elif event.key == pygame.K_RIGHT:
px += speed
Minimal Sprite Sheet example:
import os
import pygame
class SpriteSheet:
def __init__(self, filename, px, py, tw, th, m, tiles, color_key = None):
self.sheet = pygame.image.load(filename)
if color_key:
self.sheet = self.sheet.convert()
self.sheet.set_colorkey(color_key)
else:
self.sheet = self.sheet.convert_alpha()
self.cells = [(px + tw * i, py, tw-m, th) for i in range(tiles)]
self.index = 0
def update(self):
self.tile_rect = self.cells[self.index % len(self.cells)]
self.index += 1
def draw(self, surface, x, y):
rect = pygame.Rect(self.tile_rect)
rect.center = (x, y)
surface.blit(self.sheet, rect, self.tile_rect)
pygame.init()
window = pygame.display.set_mode((400, 300))
clock = pygame.time.Clock()
sprite_sheet = SpriteSheet('awesomepossum sheet.bmp', 18, 580, 64, 66, 0, 6, (0, 128, 0))
run = True
while run:
clock.tick(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
sprite_sheet.update()
window.fill(0)
sprite_sheet.draw(window, *window.get_rect().center)
pygame.display.update()
pygame.quit()
exit()
Sprite sheet form OpenGameArt.org:

Related

Projectile Motion Simulation is crashing whenever I right or left click due to it getting an "integer argument expected, got float" What do I do?

Whenever I right or left click the program instantly closes and shows the error "line 87, in update
pygame.draw.circle(win, self.color, self.path[-1], 5)
TypeError: integer argument expected, got float". Did I draw my circle wrong in a way?
I am new to coding and have tried to see whats wrong with it. But I dont really see it.
Code:
# Projectile Motion
import math
import random
import pygame
from functions import *
pygame.init()
SCREEN = WIDTH, HEIGHT = 288, 512
info = pygame.display.Info()
width = info.current_w
height = info.current_h
if width >= height:
win = pygame.display.set_mode(SCREEN, pygame.NOFRAME)
else:
win = pygame.display.set_mode(SCREEN, pygame.NOFRAME | pygame.SCALED | pygame.FULLSCREEN)
clock = pygame.time.Clock()
FPS = 60
BLACK = (18, 18, 18)
WHITE = (217, 217, 217)
RED = (252, 91, 122)
GREEN = (29, 161, 16)
BLUE = (78, 193, 246)
ORANGE = (252,76,2)
YELLOW = (254,221,0)
PURPLE = (155,38,182)
AQUA = (0,249,182)
COLORS = [RED, GREEN, BLUE, ORANGE, YELLOW, PURPLE]
font = pygame.font.SysFont('verdana', 12)
origin = (20, 340)
radius = 250
u = 50
g = 9.8
class Projectile(pygame.sprite.Sprite):
def __init__(self, u, theta):
super(Projectile, self).__init__()
self.u = u
self.theta = toRadian(abs(theta))
self.x, self.y = origin
self.color = random.choice(COLORS)
self.ch = 0
self.dx = 2
self.f = self.getTrajectory()
self.range = self.x + abs(self.getRange())
self.path = []
def timeOfFlight(self):
return round((2 * self.u * math.sin(self.theta)) / g, 2)
def getRange(self):
range_ = ((self.u ** 2) * 2 * math.sin(self.theta) * math.cos(self.theta)) / g
return round(range_, 2)
def getMaxHeight(self):
h = ((self.u ** 2) * (math.sin(self.theta)) ** 2) / (2 * g)
return round(h, 2)
def getTrajectory(self):
return round(g / (2 * (self.u ** 2) * (math.cos(self.theta) ** 2)), 4)
def getProjectilePos(self, x):
return x * math.tan(self.theta) - self.f * x ** 2
def update(self):
if self.x >= self.range:
self.dx = 0
self.x += self.dx
self.ch = self.getProjectilePos(self.x - origin[0])
self.path.append((self.x, self.y-abs(self.ch)))
self.path = self.path[-50:]
pygame.draw.circle(win, self.color, self.path[-1], 5)
pygame.draw.circle(win, WHITE, self.path[-1], 5, 1)
for pos in self.path[:-1:5]:
pygame.draw.circle(win, WHITE, pos, 1)
projectile_group = pygame.sprite.Group()
clicked = False
currentp = None
theta = -30
end = getPosOnCircumeference(theta, origin)
arct = toRadian(theta)
arcrect = pygame.Rect(origin[0]-30, origin[1]-30, 60, 60)
running = True
while running:
win.fill(BLACK)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE or event.key == pygame.K_q:
running = False
if event.key == pygame.K_r:
projectile_group.empty()
currentp = None
if event.type == pygame.MOUSEBUTTONDOWN:
clicked = True
if event.type == pygame.MOUSEBUTTONUP:
clicked = False
pos = event.pos
theta = getAngle(pos, origin)
if -90 < theta <= 0:
projectile = Projectile(u, theta)
projectile_group.add(projectile)
currentp = projectile
if event.type == pygame.MOUSEMOTION:
if clicked:
pos = event.pos
theta = getAngle(pos, origin)
if -90 < theta <= 0:
end = getPosOnCircumeference(theta, origin)
arct = toRadian(theta)
pygame.draw.line(win, WHITE, origin, (origin[0] + 250, origin[1]), 2)
pygame.draw.line(win, WHITE, origin, (origin[0], origin[1] - 250), 2)
pygame.draw.line(win, AQUA, origin, end, 2)
pygame.draw.circle(win, WHITE, origin, 3)
pygame.draw.arc(win, AQUA, arcrect, 0, -arct, 2)
projectile_group.update()
# Info *******************************************************************
title = font.render("Projectile Motion", True, WHITE)
fpstext = font.render(f"FPS : {int(clock.get_fps())}", True, WHITE)
thetatext = font.render(f"Angle : {int(abs(theta))}", True, WHITE)
degreetext = font.render(f"{int(abs(theta))}°", True, YELLOW)
win.blit(title, (80, 30))
win.blit(fpstext, (20, 400))
win.blit(thetatext, (20, 420))
win.blit(degreetext, (origin[0]+38, origin[1]-20))
if currentp:
veltext = font.render(f"Velocity : {currentp.u}m/s", True, WHITE)
timetext = font.render(f"Time : {currentp.timeOfFlight()}s", True, WHITE)
rangetext = font.render(f"Range : {currentp.getRange()}m", True, WHITE)
heighttext = font.render(f"Max Height : {currentp.getMaxHeight()}m", True, WHITE)
win.blit(veltext, (WIDTH-150, 400))
win.blit(timetext, (WIDTH-150, 420))
win.blit(rangetext, (WIDTH-150, 440))
win.blit(heighttext, (WIDTH-150, 460))
pygame.draw.rect(win, (0,0,0), (0, 0, WIDTH, HEIGHT), 5)
clock.tick(FPS)
pygame.display.update()
pygame.quit()

Error using pygame wiki outline for 'spritesheets' [duplicate]

So I've been testing out this code, I found a tutorial on how I can add spritesheets in pygame and decided to try this one:
https://www.spriters-resource.com/3ds/dragonballzextremebutoden/sheet/67257/
I did as the video said and counted the columns and rows, this is my code:
pygame.init()
CLOCK = pygame.time.Clock()
DS = pygame.display.set_mode((W, H))
FPS = 60
class spritesheet:
def __init__(self, filename, cols, rows):
self.sheet = pygame.image.load(filename).convert_alpha()
self.cols = cols
self.rows = rows
self.totalCellCount = cols * rows
self.rect = self.sheet.get_rect()
w = self.cellWidth = self.rect.width / cols
h = self.cellHeight = self.rect.height / rows
hw, hh = self.cellCenter = (w / 2, h / 2)
self.cells = list([(index % cols * w, index / cols * h, w, h) for index in range(self.totalCellCount)])
self.handle = list([
(0,0), (-hw, 0), (-w, 0),
(0, -hh), (-hw, -hh), (-w, -hh),
(0, -h), (-hw, -h), (-w, -h),])
def draw(self, surface, cellIndex, x, y, handle = 0):
surface.blit(self.sheet,
(x + self.handle[handle][0], y + self.handle[handle][1],
self.cells[cellIndex][2], self.cells[cellIndex][3]))
s = spritesheet('Number18.png', 58, 6)
CENTER_HANDLE = 6
Index = 0
#mainloop
run = True
while run:
s.draw(DS, Index % s.totalCellCount, HW, HH, CENTER_HANDLE)
Index +=1
#pygame.draw.circle(DS, WHITE, (HW, HW), 20, 10)
DS.blit(bg,(0,0))
pygame.display.update()
CLOCK.tick(FPS)
DS.fill(BLACK)
The line s = spritesheet("Number18.png", 58, 6) has the numbers 58, 6 which are basically the number of rows and columns I counted on this spritesheet block, but I'm getting problems such as the pygame window on "not responding", the image does not load up and I can't move the pygame screen.
I'm getting problems such as the pygame window on "not responding", [...]
The fist thing what you've to do is to add en event loop to the main loop of the application.
pygame.event removes a pending event message from the queue and returns it.
At least you should handle the QUIT event. Set the value of control variable for the main loop False:
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
The tiles in the sprite sheet don't have equal size. Limit the cells list to some parts of the sheet which have equal size.
Try the following:
class spritesheet:
def __init__(self, filename, py, tw, th, tiles):
self.sheet = pygame.image.load(filename).convert_alpha()
self.py = py
self.tw = tw
self.th = th
self.totalCellCount = tiles
self.rect = self.sheet.get_rect()
w, h = tw, th
hw, hh = self.cellCenter = (w / 2, h / 2)
self.cells = [(1+i*tw, self.py, tw-1, th-1) for i in range(tiles)]
self.handle = list([
(0,0), (-hw, 0), (-w, 0),
(0, -hh), (-hw, -hh), (-w, -hh),
(0, -h), (-hw, -h), (-w, -h),])
s = spritesheet('Number18.png', 1085, 80, 134, 8)
[...] the image does not load up [...]
Make sure that the image is located int the working directory of the application.
If you want to draw a sub-image of a spritsheet then you've to set the area parameter (3rd parameter) of pygame.Surface.blit to the rectangle area of the sub-image:
def draw(self, surface, cellIndex, x, y, handle = 0):
hdl = self.handle[handle]
surface.blit(self.sheet, (x + hdl[0], y + hdl[1]), area=self.cells[cellIndex])
[...] I can't move [...]
You have to change the position of the sprite. Handle the KEYDOWN event. Store the position of the sprite (px, py). Change the position when the K_UP, K_DOWN, K_LEFT or K_RIGHT key is pressed:
run = True
px, py, speed = HW, HH, 10
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
py -= speed
elif event.key == pygame.K_DOWN:
py += speed
elif event.key == pygame.K_LEFT:
px -= speed
elif event.key == pygame.K_RIGHT:
px += speed
Minimal Sprite Sheet example:
import os
import pygame
class SpriteSheet:
def __init__(self, filename, px, py, tw, th, m, tiles, color_key = None):
self.sheet = pygame.image.load(filename)
if color_key:
self.sheet = self.sheet.convert()
self.sheet.set_colorkey(color_key)
else:
self.sheet = self.sheet.convert_alpha()
self.cells = [(px + tw * i, py, tw-m, th) for i in range(tiles)]
self.index = 0
def update(self):
self.tile_rect = self.cells[self.index % len(self.cells)]
self.index += 1
def draw(self, surface, x, y):
rect = pygame.Rect(self.tile_rect)
rect.center = (x, y)
surface.blit(self.sheet, rect, self.tile_rect)
pygame.init()
window = pygame.display.set_mode((400, 300))
clock = pygame.time.Clock()
sprite_sheet = SpriteSheet('awesomepossum sheet.bmp', 18, 580, 64, 66, 0, 6, (0, 128, 0))
run = True
while run:
clock.tick(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
sprite_sheet.update()
window.fill(0)
sprite_sheet.draw(window, *window.get_rect().center)
pygame.display.update()
pygame.quit()
exit()
Sprite sheet form OpenGameArt.org:

How to Fill a rectangle with color every time the user clicks it? pygame

I know that there are several similar questions online, but none of them really helped me. I simply want to draw a grid and give the user the option to click into those grid cells. Every time the user clicks, the color/fill of the cell should change from black to white.
What I'm doing at the moment is the following:
BLACK = (0, 0, 0)
WHITE = (200, 200, 200)
def drawGrid(h, w, blocksize):
for x in range(w):
for y in range(h):
rect = pygame.Rect(x*blocksize, y*blocksize,
blocksize, blocksize)
pygame.draw.rect(SCREEN, WHITE, rect, 1)
def handle_events():
col = WHITE
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == MOUSEBUTTONDOWN:
col = WHITE
# determine mouse position
mpos_x, mpos_y = event.pos
# determine cell number
coord = mpos_x // blocksize, mpos_y // blocksize
rect = pygame.Rect(coord[0]*blocksize, coord[1]*blocksize,
blocksize, blocksize)
pygame.draw.rect(SCREEN, col, rect, 1)
pygame.display.update()
def main():
global SCREEN, CLOCK, blocksize
w = int( sys.argv[1] )
h = int( sys.argv[2] )
blocksize = int( sys.argv[3] )
pygame.init()
SCREEN = pygame.display.set_mode((h, w))
CLOCK = pygame.time.Clock()
SCREEN.fill(BLACK)
drawGrid(h,w,blocksize)
handle_events()
if __name__ == "__main__":
main()
The program is printing the grid. However, when I click somewhere nothing happens. I know this is not the best code, so I would appreciate for any suggestion.
I changed the code a little and it worked properly, pygame.draw.rect(SCREEN, col, rect, 1) you draw same thing and you can't see the change. You should use pygame.draw.rect(SCREEN, col, rect):
import pygame
import sys
BLACK = (0, 0, 0)
WHITE = (200, 200, 200)
# WINDOW_HEIGHT = 400
# WINDOW_WIDTH = 400
def drawGrid(h, w, blocksize):
#blockSize = 20 #Set the size of the grid block
for x in range(w):
for y in range(h):
rect = pygame.Rect(x*blocksize, y*blocksize,
blocksize, blocksize)
pygame.draw.rect(SCREEN, WHITE, rect, 1)
def handle_events():
#coords_list = []
col = WHITE
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
col = WHITE
# determine mouse position
mpos_x, mpos_y = event.pos
# determine cell number
coord = mpos_x // 32, mpos_y // 32
rect = pygame.Rect(coord[0]*32, coord[1]*32,
32, 32)
pygame.draw.rect(SCREEN, col, rect)
#coords_list.append(coord)
pygame.display.update()
#return coords_list
def main():
global SCREEN, CLOCK, blocksize
pygame.init()
SCREEN = pygame.display.set_mode((480, 640))
CLOCK = pygame.time.Clock()
SCREEN.fill(BLACK)
drawGrid(480,640,32)
handle_events()
if __name__ == "__main__":
main()
As a suggestion, I think you should use sprite for every cell. For example like this:
class Cell(pygame.sprite.Sprite):
def __init__(self, sprite_group, x, y, cell_dimension, color=BLACK):
self.groups = sprite_group
pygame.sprite.Sprite.__init__(self, self.groups)
self.image = pygame.Surface((cell_dimension, cell_dimension))
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.x = x * cell_dimension
self.rect.y = y * cell_dimension
self.clicked = False
def update(self):
if self.clicked:
self.image.fill(WHITE)

Pyinstaller pygame window closes with no error message [duplicate]

This question already has answers here:
PyInstaller, spec file, ImportError: No module named 'blah'
(3 answers)
Pyinstaller Unable to access Data Folder
(1 answer)
Python - pygame error when executing exe file
(3 answers)
Closed 2 years ago.
I have been at this for about 2 hours now and I am getting quite frustrated, I have created a pygame file which doesn't use any external fonts or images (which i know is the main issue on why the game screen usually crashes) however I run mine and in the console I get the usual Hello from pygame community and then my game window pops up for roughly a second ad then disappears again with no error messages in the console.
I even installed OBS just to check if the error message was popping up too fast for me to see however there is no error message in sight. I also tried turning on Debug in the .spec file however everything seemed normal (To be fair this is my first time converting a .py to a .exe so i dont really know what I am looking for when it comes to the debug)
I also tried using cx_freeze but I just kept getting this error when building
"TypeError: expected str, bytes or os.PathLike object, not NoneType" and all the fixes I could find were for Pyinstaller not cx_freeze, ideally i would prefer to use cx_freeze but I cant even build an exe with that at least with pyinstaller I have an exe to show (even if it is broken)
This is my command window when I run the exe just before it crashes, nothing more is printed after this
My code is as follows :
import random
import pygame
#import matplotlib.pyplot as plt
from pygame.locals import *
import time
import numpy as np
import sys
import os
pygame.init()
# Window details
windowWidth = 1000
windowHeight = 600
pixSize = 2
FPS = 60
numberofcells = 100
screen = pygame.display.set_mode((windowWidth, windowHeight))
pygame.display.set_caption("Infection Game")
class Cell:
def __init__(self):
self.xPos = random.randrange(1, windowWidth)
self.yPos = random.randrange(1, windowHeight)
self.speed = 2
self.isInfected = False
self.infectionRange = 5
self.move = [None, None]
self.direction = None
def cellDraw(self):
if not self.isInfected:
pygame.draw.rect(screen, (255, 255, 255), (self.xPos, self.yPos, pixSize, pixSize), 0)
else:
pygame.draw.rect(screen, (0, 255, 0), (self.xPos, self.yPos, pixSize, pixSize), 0)
def cellMovement(self):
directions = {"S": ((-1, 2), (1, self.speed)), "SW": ((-self.speed, -1), (1, self.speed)),
"W": ((-self.speed, -1), (-1, 2)), "NW": ((-self.speed, -1), (-self.speed, -1)),
"N": ((-1, 2), (-self.speed, -1)), "NE": ((1, self.speed), (-self.speed, -1)),
"E": ((1, self.speed), (-1, 2)),
"SE": ((1, self.speed), (1, self.speed))} # ((min x, max x)(min y, max y))
directionsName = ("S", "SW", "W", "NW", "N", "NE", "E", "SE") # possible directions
if random.randrange(0, 5) == 2: # move about once every 5 frames
if self.direction is None: # if no direction is set, set a random one
self.direction = random.choice(directionsName)
else:
a = directionsName.index(self.direction) # get the index of direction in directions list
b = random.randrange(a - 1,
a + 2) # set the direction to be the same, or one next to the current direction
if b > len(directionsName) - 1: # if direction index is outside the list, move back to the start
b = 0
self.direction = directionsName[b]
self.move[0] = random.randrange(directions[self.direction][0][0], directions[self.direction][0][1]) + 0.35
self.move[1] = random.randrange(directions[self.direction][1][0], directions[self.direction][1][1]) + 0.35
if self.xPos < 5 or self.xPos > windowWidth - 5 or self.yPos < 5 or self.yPos > windowHeight - 5: # if cell is near the border of the screen, change direction
if self.xPos < 5:
self.direction = "E"
elif self.xPos > windowWidth - 5:
self.direction = "W"
elif self.yPos < 5:
self.direction = "S"
elif self.yPos > windowHeight - 5:
self.direction = "N"
self.move[0] = random.randrange(directions[self.direction][0][0], directions[self.direction][0][1]) + 0.35
self.move[1] = random.randrange(directions[self.direction][1][0], directions[self.direction][1][1]) + 0.35
if self.move[0] is not None: # add the relative coordinates to the cells coordinates
self.xPos += self.move[0]
self.yPos += self.move[1]
def Infect(self, uninfected, uninfected_array):
indices = np.greater(uninfected_array[:, 0], self.xPos - self.infectionRange) * \
np.greater(self.xPos + self.infectionRange, uninfected_array[:, 0]) * \
np.greater(uninfected_array[:, 1], self.yPos - self.infectionRange) * \
np.greater(self.yPos + self.infectionRange, uninfected_array[:, 1])
for i in np.where(indices)[0]:
uninfected[i].isInfected = True
font = pygame.font.SysFont(None, 20)
def drawText(text, font ,colour, surface, x, y):
textobj = font.render(text, 1 , colour)
textrect = textobj.get_rect()
textrect.topleft = (x,y)
surface.blit(textobj,textrect)
def main_menu(numberofcells):
while True:
screen.fill((0,0,0))
drawText('main menu', font, (255, 255, 255), screen, 20, 20)
mx, my = pygame.mouse.get_pos()
button_1 = pygame.Rect(50, 100, 200, 50)
button_2 = pygame.Rect(50, 200, 200, 50)
if button_1.collidepoint((mx, my)):
if click:
gameLoop(numberofcells)
if button_2.collidepoint((mx, my)):
if click:
numberofcells = options(numberofcells)
pygame.draw.rect(screen, (255, 0, 0), button_1)
drawText('start sim', font, (0,0,0), screen, 127, 120)
pygame.draw.rect(screen, (255, 0, 0), button_2)
drawText('options', font, (0,0,0), screen, 130, 220)
click = False
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
pygame.quit()
if event.type == MOUSEBUTTONDOWN:
if event.button == 1:
click = True
pygame.display.update()
pygame.time.Clock().tick(FPS)
def options(numberofcells):
running = True
while running:
screen.fill((0,0,0))
drawText('options - press ESC to return to menu', font, (255, 255, 255), screen, 20, 20)
mx, my = pygame.mouse.get_pos()
button_1 = pygame.Rect(50, 300, 200, 50)
button_2 = pygame.Rect(50, 400, 200, 50)
pygame.draw.rect(screen, (255, 0, 0), button_1)
drawText('100 cells', font, (0,0,0), screen, 127, 320)
pygame.draw.rect(screen, (255, 0, 0), button_2)
drawText('1000 cells', font, (0,0,0), screen, 127, 420)
if button_1.collidepoint((mx, my)):
if optclick:
numberofcells = 100
pygame.draw.rect(screen, (139, 0, 0), button_1)
if button_2.collidepoint((mx, my)):
if optclick:
numberofcells = 1000
pygame.draw.rect(screen, (139, 0, 0), button_2)
optclick = False
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
running = False
if event.type == MOUSEBUTTONDOWN:
if event.button == 1:
optclick = True
pygame.display.update()
pygame.time.Clock().tick(FPS)
return(numberofcells)
def gameLoop(numberofcells):
startTime = time.time()
xgraph = []
ygraph = []
cellList = []
for i in range(numberofcells):
cell = Cell()
cellList.append(cell)
cellList[0].isInfected = True
running = True
while running:
infectList = []
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
running = False
screen.fill((0, 0, 0))
for i in cellList:
i.cellDraw()
i.cellMovement()
infected = 0
uninfected = [i for i in cellList if not i.isInfected]
uninfected_array = np.array([[i.xPos, i.yPos] for i in uninfected])
if len(uninfected) > 0:
for i in cellList:
if i.isInfected:
i.Infect(uninfected, uninfected_array)
infected += 1
if infected == 0:
infected = len(cellList) - len(uninfected)
xgraph.append(time.time() - startTime)
ygraph.append(infected)
pygame.display.update() # update display
pygame.time.Clock().tick(FPS) # limit FPS
# figured this is what you wanted to do ;)
#plt.plot(xgraph, ygraph)
#plt.xlabel('time (s)')
#plt.ylabel('infected')
#plt.show()
main_menu(numberofcells)

get position of sprite, and move it in response to it being clicked on [duplicate]

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