I finally figured out how to animate my sprite, but now I have a new problem. When running the game at my desired FPS (60), the character animation is way too quick. The animation looks smooth at around 10FPS, but the game looks choppy at that framerate. It is possible for my game to run at 60FPS, while the animation runs at a seperate FPS (ex. 10)? Any help appreciated!
Images and Sound FX Dowload
My code:
import pygame
import random
import time
import os
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d, %d" %(0, 20)
pygame.init()
SIZE = W, H = 400, 700
screen = pygame.display.set_mode(SIZE)
clock = pygame.time.Clock()
# colours
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
BACKGROUND = (94, 194, 222)
STRIPE = (60, 160, 190)
LANELINE = (255, 255, 255)
x1 = 30
x2 = 330
lane1 = 30
lane2 = 130
lane3 = 230
lane4 = 330
y = 530
width = 40
height = 64
toggle1 = 0
toggle2 = 0
target_x1 = 30
target_x2 = 330
vel_x = 10
def drawScene():
screen.fill(BACKGROUND)
pygame.draw.polygon(screen, STRIPE, ((200, 700), (300, 700), (400, 600), (400, 500)))
pygame.draw.polygon(screen, STRIPE, ((0, 700), (100, 700), (400, 400), (400, 300)))
pygame.draw.polygon(screen, STRIPE, ((0, 500), (0, 600), (400, 200), (400, 100)))
pygame.draw.polygon(screen, STRIPE, ((0, 300), (0, 400), (400, 0), (300, 0)))
pygame.draw.polygon(screen, STRIPE, ((0, 100), (0, 200), (200, 0), (100, 0)))
pygame.draw.line(screen, LANELINE, (100, 0), (100, 700), 2)
pygame.draw.line(screen, LANELINE, (200, 0), (200, 700), 4)
pygame.draw.line(screen, LANELINE, (300, 0), (300, 700), 2)
mainsheet = pygame.image.load("dolphinSheet.png").convert()
sheetSize = mainsheet.get_size()
horiz_cells = 6
vert_cells = 1
cell_width = int(sheetSize[0] / horiz_cells)
cell_height = int(sheetSize[1] / vert_cells)
cellList = []
for vert in range(0, sheetSize[1], cell_height):
for horz in range(0, sheetSize[0], cell_width):
surface = pygame.Surface((cell_width, cell_height))
surface.blit(mainsheet, (0, 0),
(horz, vert, cell_width, cell_height))
colorkey = surface.get_at((0, 0))
surface.set_colorkey(colorkey)
cellList.append(surface)
cellPosition = 0
# main loop
while True:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_a:
pygame.mixer.music.load('percussiveHit.mp3')
pygame.mixer.music.play()
toggle1 += 1
if toggle1 % 2 == 1:
target_x1 += 100
else:
target_x1 -= 100
elif event.key == pygame.K_d:
pygame.mixer.music.load('percussiveHit.mp3')
pygame.mixer.music.play()
toggle2 += 1
if toggle2 % 2 == 1:
target_x2 -= 100
else:
target_x2 += 100
if x1 < target_x1:
x1 = min(x1 + vel_x, target_x1)
else:
x1 = max(x1 - vel_x, target_x1)
if x2 < target_x2:
x2 = min(x2 + vel_x, target_x2)
else:
x2 = max(x2 - vel_x, target_x2)
if cellPosition < len(cellList) - 1:
cellPosition += 1
else:
cellPosition = 0
drawScene()
pygame.draw.rect(screen, RED, (x1, y, width, height))
pygame.draw.rect(screen, RED, (x2, y, width, height))
screen.blit(cellList[cellPosition], (x1 + 4, y - 1))
screen.blit(cellList[cellPosition], (x2 + 4, y - 1))
# players
# screen.blit(playerImg, (x1 + 4, y - 5))
# screen.blit(playerImg, (x2 + 4, y - 5))
pygame.display.update()
pygame.quit()
Update the image based on a real-time millisecond delay, rather than every frame. Use pygame.time.get_ticks() (returns the number of milliseconds since pygame.init()) to update the image based on a specific time.
For example:
MS_FRAME_TIME = 100 # Mow many milliseconds a frame is shown for
...
last_paint_at = 0 # Start condition to ensure first paint
...
while True:
clock.tick(60)
ticks = pygame.time.get_ticks() # millliseconds since start
# If enough milliseconds have elapsed since the last frame, update!
if ( ticks - last_paint_at > MS_FRAME_TIME ):
last_paint_at = ticks
if ( cellPosition < len(cellList) - 1 ):
cellPosition += 1
else:
cellPosition = 0
screen.blit(cellList[cellPosition], (x1 + 4, y - 1))
screen.blit(cellList[cellPosition], (x2 + 4, y - 1))
If it fits your code, it may also be possible to simply look at the modulus of the time to select the animation frame.
Related
I want to make a Person/Car/Truck count program using Python and Pycharm using webcam.
The video source is from a webcam (private link).
When the 'Person' button (on the left side) is clicked, from that moment, everytime a person walk accross the red line (green line is the starting parameter), the program will count it as 1 person and continue counting until the same
Person' button (on the left side) is clicked. Same goes for 'Car' button and 'Truck' button. The total count for Car and Truck is also shown at the window.
I already have a working code for to do this but whenever I run the program (py file), for unknown reason, when I clicked the 'Person/Car/Truck/Bus button, the video will crash/stop or sometime later after that. Also, the video output is lagging from actual second from the private webcam.
A) How do I add buffer to the video output because the video output is lagging behind?
B) How to solve the video output crash?
Crash error:
Traceback (most recent call last):
File "C:/Users/admin/Desktop/Fadhli/License Plate Recognition/Mark1/mark5.py", line 109, in <module>
img_frame_90 = rescale_frame(frame, 90)
File "C:/Users/admin/Desktop/Fadhli/License Plate Recognition/Mark1/mark5.py", line 30, in rescale_frame
width = int(image.shape[1] * percentage / 100)
AttributeError: 'NoneType' object has no attribute 'shape'
[rtsp # 0000011437d43840] RTP: PT=60: bad cseq 4512 expected=36e9
[hevc # 0000011437e2ea40] Could not find ref with POC 42
Process finished with exit code 1
Full coding:
import cv2
import numpy as np
# Resolution Pixel Size
# 640 x 480 - Standard Definition(SD)
# 1280 x 720 - High Definition(HD)
# 1920 x 1080 - Ultra High Definiion(HD+)
# 3840 x 2160 - Ultra High Definition(UHD or 2K)
cyanColor = (255, 255, 0)
pinkColor = (255, 0, 255)
yellowColor = (0, 255, 255)
greenColor = (0, 255, 0)
blueColor = (255, 0, 0)
redColor = (0,0,255)
path_vid = "Resources/video/license_plate.mp4"
path_main_ent = 'rtsp://~~~/Streaming/Channels/101'
path_parking_Lot = 'rtsp://~~~/Streaming/Channels/101'
button_person = False
button_car = False
button_truck = False
counter = 0
nmsThreshold = 0.3
confThreshold = 0.4
def rescale_frame(image, percentage):
width = int(image.shape[1] * percentage / 100)
height = int(image.shape[0] * percentage / 100)
new_frame = (width, height)
return cv2.resize(image, new_frame, interpolation=cv2.INTER_AREA)
def click_button(event,x,y,flags,params):
global button_person
global button_car
global button_truck
if event == cv2.EVENT_LBUTTONDOWN:
print(x,y)
# ------ Person Button Clicking ---------
polygon_person = np.array([[(20, 160), (200, 160), (200, 230), (20, 230)]])
is_inside_person_button = cv2.pointPolygonTest(polygon_person,(x,y),False)
if is_inside_person_button>0:
print("YAYYYY, We're clicking inside Button!!",x,y)
if button_person is False:
button_person = True
else:
button_person = False
print("Now Person Button is: ",button_person)
# ------ Car Button Clicking ---------
polygon_car = np.array([[(20, 250), (200, 250), (200, 320), (20, 320)]])
is_inside_car_button = cv2.pointPolygonTest(polygon_car, (x, y), False)
if is_inside_car_button > 0:
print("YAYYYY, We're clicking inside Button!!", x, y)
if button_car is False:
button_car = True
else:
button_car = False
print("Now Car Button is: ", button_car)
# ------ Truck Button Clicking ---------
polygon_truck = np.array([[(20, 340), (200, 340), (200, 410), (20, 410)]])
is_inside_truck_button = cv2.pointPolygonTest(polygon_truck, (x, y), False)
if is_inside_truck_button > 0:
print("YAYYYY, We're clicking inside Button!!", x, y)
if button_truck is False:
button_truck = True
else:
button_truck = False
print("Now Truck Button is: ", button_truck)
# net = cv2.dnn.readNet("dnn_model/yolov3.weights", "dnn_model/yolov3.cfg")
# net = cv2.dnn.readNet("dnn_model/yolov3-spp.weights", "dnn_model/yolov3-spp.cfg")
# net = cv2.dnn.readNet("dnn_model/yolov3-tiny.weights", "dnn_model/yolov3-tiny.cfg")
# net = cv2.dnn.readNet("dnn_model/yolov4.weights", "dnn_model/yolov4.cfg")
net = cv2.dnn.readNet("dnn_model/yolov4-tiny.weights", "dnn_model/yolov4-tiny.cfg")
model = cv2.dnn_DetectionModel(net)
model.setInputParams(size=(640,480), scale=1/255)
classes = []
with open("dnn_model/classes.txt","r") as file_object:
for class_name in file_object.readlines():
class_name = class_name.strip()
classes.append(class_name)
# print("- Object List -")
# print(classes[0])
cap = cv2.VideoCapture(path_main_ent)
# cap = cv2.VideoCapture(path_parking_Lot)
cv2.namedWindow("Frame") # The name should be same with cv2.imshow("_Name_")
cv2.setMouseCallback("Frame",click_button)
while True:
ret,frame = cap.read()
# print(type(frame))
img_frame_90 = rescale_frame(frame, 90)
line_frame_above = cv2.line(img_frame_90, (190, 430), (1220, 470), yellowColor, 2)
line_frame = cv2.line(img_frame_90, (180, 440), (1220, 480), blueColor, 4)
line_frame_bottom = cv2.line(img_frame_90, (170, 450), (1220, 490), yellowColor, 2)
polygon_person = np.array([[(20, 160), (200, 160), (200, 230), (20, 230)]])
cv2.fillPoly(img_frame_90, polygon_person, greenColor)
cv2.putText(img_frame_90, "Person", (50, 200), cv2.FONT_HERSHEY_PLAIN, 2, blueColor, 2)
polygon_car = np.array([[(20, 250), (200, 250), (200, 320), (20, 320)]])
cv2.fillPoly(img_frame_90, polygon_car, greenColor)
cv2.putText(img_frame_90, "Car", (50, 290), cv2.FONT_HERSHEY_PLAIN, 2, blueColor, 2)
polygon_truck = np.array([[(20, 340), (200, 340), (200, 410), (20, 410)]])
cv2.fillPoly(img_frame_90, polygon_truck, greenColor)
cv2.putText(img_frame_90, "Truck", (50, 380), cv2.FONT_HERSHEY_PLAIN, 2, blueColor, 2)
polygon_truck = np.array([[(20, 430), (200, 430), (200, 500), (20, 500)]])
cv2.fillPoly(img_frame_90, polygon_truck, greenColor)
cv2.putText(img_frame_90, "Bus", (50, 470), cv2.FONT_HERSHEY_PLAIN, 2, blueColor, 2)
(class_ids, scores, bboxes) = model.detect(img_frame_90,nmsThreshold=nmsThreshold, confThreshold=confThreshold)
if len(class_ids) != 0:
for class_id, score, bbox in zip(class_ids,scores,bboxes):
(x,y,w,h) = bbox
class_name = classes[class_id]
xmid = int((x + (x + w)) / 2)
ymid = int((y + (y + h)) / 2)
if class_name == "person" and button_person is True:
cv2.rectangle(img_frame_90, (x, y), (x + w, y + h), pinkColor, 2)
cv2.circle(img_frame_90, (xmid, ymid), 3, redColor, -1)
# cv2.putText(img_frame_90,str(class_name),(x,y-10),cv2.FONT_HERSHEY_SIMPLEX,1,yellowColor,2)
if ymid > 431 and ymid < 500 and xmid > 170 and xmid <1220:
line_frame = cv2.line(img_frame_90, (350, 440), (1220, 480), greenColor, 4)
counter += 1
if class_name == "car" and button_car is True:
cv2.rectangle(img_frame_90, (x, y), (x + w, y + h), greenColor, 2)
cv2.circle(img_frame_90, (xmid, ymid), 3, redColor, -1)
# cv2.putText(img_frame_90,str(class_name),(x,y-10),cv2.FONT_HERSHEY_SIMPLEX,1,yellowColor,2)
if ymid > 431 and ymid < 507:
line_frame = cv2.line(img_frame_90, (350, 440), (1220, 480), greenColor, 4)
counter += 1
if class_name == "truck" and button_truck is True:
cv2.rectangle(img_frame_90, (x, y), (x + w, y + h), cyanColor, 2)
cv2.circle(img_frame_90, (xmid, ymid), 3, redColor, -1)
# cv2.putText(img_frame_90,str(class_name),(x,y-10),cv2.FONT_HERSHEY_SIMPLEX,1,yellowColor,2)
if ymid > 431 and ymid < 507:
line_frame = cv2.line(img_frame_90, (350, 440), (1220, 480), greenColor, 4)
counter += 1
cv2.putText(img_frame_90, 'Total Vehicles : {}'.format(counter), (0, 100), cv2.FONT_HERSHEY_SIMPLEX, 2, yellowColor,2)
cv2.imshow("Frame",img_frame_90)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
I'm not managing to get this math correct, and it's a little bit difficult to explain in words. I have managed to create a isometric grid, which you can select the tiles with the mouse perfectly, and I have managed to implement a camera movement using wasd keys and still get the tiles correctly selected, but there is a slightly bug which I can not figure out where is coming from.
This is what happens, but only sometimes, depend where the camera offset is:
when this happens, it is only on the x axis, and not in every tile.
I'm almost giving up on this cause I can't find the bug, thought of posting here to see if anyone had similar problem.
import time
import pygame
import sys
import math
from os import path
from settings import *
from sprites import *
# ------------------------- SETTINGS ---------------------------- #
# COLORS (r, g, b)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
DARKGREY = (40, 40, 40)
LIGHTGREY = (100, 100, 100)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
RED = (255, 0, 0)
YELLOW = (255, 255, 0)
# game settings
WIDTH = 1024
HEIGHT = 768
FPS = 60
title = "Isometric-Based game"
BGCOLOUR = DARKGREY
TILE_X = 80
TILE_Y = 40
WORLD_X, WORLD_Y = 14, 10
ORIGIN_X, ORIGIN_Y = 5, 1
# Debug
pygame.init()
font = pygame.font.Font(None, 25)
CAMERA_SPEED = 300
def get_info(info_list):
display_surface = pygame.display.get_surface()
for i, key in enumerate(info_list):
text = font.render(str(key) + " : " + str(info_list[key]), True, (255, 255, 255), (0, 0, 0))
text_rect = text.get_rect()
text_rect.y = 20 * i
display_surface.blit(text, text_rect)
# ------------------------- SPRITES ---------------------------- #
class Camera:
def __init__(self, game, x, y):
self.game = game
self.x, self.y = self.game.to_screen(x, y)
self.vx, self.vy = 0, 0
def update(self):
self.get_keys()
self.x += self.vx * self.game.dt
self.y += self.vy * self.game.dt
def get_keys(self):
self.vx, self.vy = 0, 0
keys = pygame.key.get_pressed()
if keys[pygame.K_w]:
self.vy = -CAMERA_SPEED
if keys[pygame.K_s]:
self.vy = CAMERA_SPEED
if keys[pygame.K_a]:
self.vx = -CAMERA_SPEED
if keys[pygame.K_d]:
self.vx = CAMERA_SPEED
if self.vx != 0 and self.vy != 0:
self.vx *= 1.0
self.vy *= 0.50
class MouseSelection:
def __init__(self, game, image):
self.game = game
self.image = image
def update(self):
# get mouse x and y
self.mouse_x, self.mouse_y = pygame.mouse.get_pos()
# get the mouse offset position inside the tile
self.offset_x, self.offset_y = self.mouse_x % TILE_X, self.mouse_y % TILE_Y
self.offset_x += self.game.scroll_x % TILE_X # Add camera scroll to offset
self.offset_y += self.game.scroll_y % TILE_Y
# get the cell number
self.cell_x, self.cell_y = (self.mouse_x // TILE_X), (self.mouse_y // TILE_Y)
self.cell_x += int((self.game.scroll_x // TILE_X)) # Add camera scroll to cell
self.cell_y += int((self.game.scroll_y // TILE_Y))
# get the selected cell in iso grid
self.selected_x = (self.cell_y - ORIGIN_Y) + (self.cell_x - ORIGIN_X)
self.selected_y = (self.cell_y - ORIGIN_Y) - (self.cell_x - ORIGIN_X)
# height and width of a quarter of a tile, select the corner of the tile to nodge to a direction
h, w = TILE_Y / 2, TILE_X / 2
if self.offset_y < (h / w) * (w - self.offset_x):
self.selected_x -= 1
if self.offset_y > (h / w) * self.offset_x + h:
self.selected_y += 1
if self.offset_y < (h / w) * self.offset_x - h:
self.selected_y -= 1
if self.offset_y > (h / w) * (2 * w - self.offset_x) + h:
self.selected_x += 1
# translate the selected cell to world coordinate
self.selectedWorld_x, self.selectedWorld_y = self.game.to_screen(self.selected_x, self.selected_y)
def draw(self):
# Draw the selected tile with the camera scroll offset
self.game.screen.blit(self.image, (self.selectedWorld_x - self.game.scroll_x,
self.selectedWorld_y - self.game.scroll_y))
class SpriteSheet:
def __init__(self, image):
self.image = image
self.frames = []
def get_image(self):
for row in range(2):
for col in range(4):
if row == 0:
image = pygame.Surface((TILE_Y, TILE_Y / 2)).convert_alpha()
image.blit(self.image, (0, 0), (col * TILE_X / 2, row * TILE_Y / 2, TILE_X, TILE_Y))
image = pygame.transform.scale(image, (TILE_X, TILE_Y))
else:
image = pygame.Surface((TILE_Y, TILE_Y)).convert_alpha()
image.blit(self.image, (0, 0), (col * TILE_X / 2, row * TILE_Y / 2, TILE_X, TILE_Y * 2))
image = pygame.transform.scale(image, (TILE_X, TILE_Y * 2))
image.set_colorkey(WHITE)
self.frames.append(image)
return self.frames
# ------------------------- GAME LOOP ---------------------------- #
class Game:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption(title)
self.clock = pygame.time.Clock()
pygame.key.set_repeat(400, 100)
self.debug = {}
self.sprite_sheet_image = pygame.image.load("isometric_whitebg - Copy.png")
self.index = 1
self.scroll_x, self.scroll_y = 0, 0
def new(self):
# initialize all variables and do all the setup for a new game
self.sprite_sheet = SpriteSheet(self.sprite_sheet_image)
self.tile_selected = self.sprite_sheet.get_image()[0]
self.tiles = self.sprite_sheet.get_image()
self.mouse_selection = MouseSelection(self, self.tile_selected)
self.camera = Camera(self, 1, 1)
def run(self):
# game loop - set self.playing = False to end the game
self.playing = True
while self.playing:
self.dt = self.clock.tick(FPS) / 1000
self.events()
self.update()
self.draw()
def quit(self):
pygame.quit()
sys.exit()
def update(self):
# update portion of the game loop
self.camera.update()
self.mouse_selection.update()
self.mx, self.my = pygame.mouse.get_pos()
# -------------------------------------------------- CAMERA SCROLLING ----------------------------------------#
if self.camera.x - self.scroll_x != WIDTH / 2:
self.scroll_x += (self.camera.x - (self.scroll_x + WIDTH / 2)) / 10
if self.camera.y - self.scroll_y != HEIGHT / 2:
self.scroll_y += (self.camera.y - (self.scroll_y + HEIGHT / 2)) / 10
# -------------------------------------------------- CAMERA SCROLLING ----------------------------------------#
self.debug_info()
def to_screen(self, x, y):
screen_x = (ORIGIN_X * TILE_X) + (x - y) * (TILE_X / 2)
screen_y = (ORIGIN_Y * TILE_Y) + (x + y) * (TILE_Y / 2)
return screen_x, screen_y
def draw_world(self):
for y in range(WORLD_Y):
for x in range(WORLD_X):
vWorld_x, vWorld_y = self.to_screen(x, y)
# Invisible tile
if self.index == 0:
self.screen.blit(self.tiles[1], (vWorld_x, vWorld_y))
# Grass
elif self.index == 1:
self.screen.blit(self.tiles[2], (vWorld_x - self.scroll_x, vWorld_y - self.scroll_y))
def draw(self):
self.screen.fill(BGCOLOUR)
self.draw_world()
self.mouse_selection.draw()
get_info(self.debug)
pygame.display.flip()
def debug_info(self):
self.debug["FPS"] = int(self.clock.get_fps())
self.debug["Cell"] = self.mouse_selection.cell_x, self.mouse_selection.cell_y
self.debug["Selected"] = int(self.mouse_selection.selected_x), int(self.mouse_selection.selected_y)
self.debug["Scroll"] = int(self.scroll_x), int(self.scroll_y)
self.debug["Mouse"] = int(self.mx), int(self.my)
self.debug["Mouse_offset"] = int(self.mouse_selection.offset_x), int(self.mouse_selection.offset_y)
def events(self):
# catch all events here
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
self.quit()
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
pass
game = Game()
while True:
game.new()
game.run()
Define the corner points of the map:
map_outline = [
pygame.math.Vector2(left_x, left_y),
pygame.math.Vector2(top_x, top_y),
pygame.math.Vector2(right_x, right_y,
pygame.math.Vector2(bottom_x, bottom_y)
]
With this information you can calculate the x and y axis of the map:
origin = map_outline[0]
x_axis = (map_outline[1] - map_outline[0]) / columns
y_axis = (map_outline[3] - map_outline[0]) / rows
You can use the x-axis and the y-axis to calculate a point in the map as a function of the row and column:
def transform(p, mat2x2):
x = p[0] * mat2x2[0][0] + p[1] * mat2x2[1][0]
y = p[0] * mat2x2[0][1] + p[1] * mat2x2[1][1]
return pygame.math.Vector2(x, y)
p_position = transform((column + 0.5, row + 0.5), (x_axis, y_axis)) + origin
If you want to get the row and column depending on the mouse cursor, you need to do the opposite. You need to calculate the inverse 2x2 matrix from the x and y axis. Using the inverse matrix, you can calculate the row and column as a function of a point on the map:
def inverseMat2x2(m):
a, b, c, d = m[0].x, m[0].y, m[1].x, m[1].y
det = 1 / (a*d - b*c)
return [(d*det, -b*det), (-c*det, a*det)]
m_pos = pygame.mouse.get_pos()
m_grid_pos = transform(pygame.math.Vector2(m_pos) - origin, point_to_grid)
m_col, m_row = int(m_grid_pos[0]), int(m_grid_pos[1])
Also see PyGameExamplesAndAnswers - Isometric
Minimal example:
replit.com/#Rabbid76/Pygame-IsometircMap
import pygame
pygame.init()
window = pygame.display.set_mode((500, 300))
clock = pygame.time.Clock()
colors = {'g': (40, 128, 40), 'd': (90, 60, 40)}
tilemap = [
'gdddg',
'dgddd',
'ggddg',
'ggddg',
'ddddg',
'dgggd'
]
columns, rows = len(tilemap[0]), len(tilemap)
isometric_tiles = {}
for key, color in colors.items():
tile_surf = pygame.Surface((50, 50), pygame.SRCALPHA)
tile_surf.fill(color)
tile_surf = pygame.transform.rotate(tile_surf, 45)
isometric_size = tile_surf.get_width()
tile_surf = pygame.transform.scale(tile_surf, (isometric_size, isometric_size//2))
isometric_tiles[key] = tile_surf
tile_size = (isometric_size, isometric_size//2)
def tileRect(column, row, tile_size):
x = (column + row) * tile_size[0] // 2
y = ((columns - column - 1) + row) * tile_size[1] // 2
return pygame.Rect(x, y, *tile_size)
game_map = pygame.Surface(((columns+rows) * isometric_size // 2, (columns+rows) * isometric_size // 4), pygame.SRCALPHA)
for column in range(columns):
for row in range(rows):
tile_surf = isometric_tiles[tilemap[row][column]]
tile_rect = tileRect(column, row, tile_size)
game_map.blit(tile_surf, tile_rect)
map_rect = game_map.get_rect(center = window.get_rect().center)
map_outline = [
pygame.math.Vector2(0, columns * isometric_size / 4),
pygame.math.Vector2(columns * isometric_size / 2, 0),
pygame.math.Vector2((columns+rows) * isometric_size // 2, rows * isometric_size / 4),
pygame.math.Vector2(rows * isometric_size / 2, (columns+rows) * isometric_size // 4)
]
for pt in map_outline:
pt += map_rect.topleft
origin = map_outline[0]
x_axis = (map_outline[1] - map_outline[0]) / columns
y_axis = (map_outline[3] - map_outline[0]) / rows
def inverseMat2x2(m):
a, b, c, d = m[0].x, m[0].y, m[1].x, m[1].y
det = 1 / (a*d - b*c)
return [(d*det, -b*det), (-c*det, a*det)]
point_to_grid = inverseMat2x2((x_axis, y_axis))
def transform(p, mat2x2):
x = p[0] * mat2x2[0][0] + p[1] * mat2x2[1][0]
y = p[0] * mat2x2[0][1] + p[1] * mat2x2[1][1]
return pygame.math.Vector2(x, y)
font = pygame.font.SysFont(None, 30)
textO = font.render("O", True, (255, 255, 255))
textX = font.render("X", True, (255, 0, 0))
textY = font.render("Y", True, (0, 255, 0))
p_col, p_row = 2, 2
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_a and p_col > 0:
p_col -= 1
if event.key == pygame.K_d and p_col < columns-1:
p_col += 1
if event.key == pygame.K_w and p_row > 0:
p_row -= 1
if event.key == pygame.K_s and p_row < rows-1:
p_row += 1
p_position = transform((p_col + 0.5, p_row + 0.5), (x_axis, y_axis)) + origin
m_pos = pygame.mouse.get_pos()
m_grid_pos = transform(pygame.math.Vector2(m_pos) - origin, point_to_grid)
m_col, m_row = int(m_grid_pos[0]), int(m_grid_pos[1])
window.fill((0, 0, 0))
window.blit(game_map, map_rect)
pygame.draw.lines(window, (164, 164, 164), True, map_outline, 3)
pygame.draw.line(window, (255, 0, 0), origin, origin+x_axis, 3)
pygame.draw.line(window, (0, 255, 0), origin, origin+y_axis, 3)
pygame.draw.circle(window, (255, 255, 255), origin, 5)
pygame.draw.circle(window, (255, 0, 0), origin+x_axis, 5)
pygame.draw.circle(window, (0, 255, 0), origin+y_axis, 5)
window.blit(textO, textO.get_rect(topright = origin+(-5, 5)))
window.blit(textX, textX.get_rect(bottomright = origin+x_axis+(-5, -5)))
window.blit(textY, textX.get_rect(topright = origin+y_axis+(-5, 5)))
pygame.draw.ellipse(window, (255, 255, 0), (p_position[0]-16, p_position[1]-8, 32, 16))
if 0 <= m_grid_pos[0] < columns and 0 <= m_grid_pos[1] < rows:
tile_rect = tileRect(m_col, m_row, tile_size).move(map_rect.topleft)
pts = [tile_rect.midleft, tile_rect.midtop, tile_rect.midright, tile_rect.midbottom]
pygame.draw.lines(window, (255, 255, 255), True, pts, 4)
pygame.display.update()
pygame.quit()
exit()
This question already has answers here:
How do I detect collision in pygame?
(5 answers)
Collision between masks in pygame
(1 answer)
Closed 1 year ago.
So, I'm making an app/game in python using pygame module, and my problem is that, I cannot find a way to check if a sprite is entirely touching another sprite or not.
What I mean is
if (sprite is touching anything else that some white rectangle):
do some code
Here is my messy code :
main.py :
import pygame, Classes, Groups, Images, random, time
pygame.init()
pygame.font.init()
# App Variables
run_bool = False
# Fonts
ARAL_20_ITALIC = pygame.font.SysFont('Arial' , 20, italic=True)
# Colors
BG = (30, 30, 30)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
# Window
size = (800, 600)
scr = pygame.display.set_mode(size)
pygame.display.set_caption('Simulation by Cold Fire (0.1)')
# App Loop
while True:
time.sleep(0.01)
# Graphics
scr.fill(BG)
"""TERRAIN"""
terrain_hitbox = pygame.draw.rect(scr, WHITE, (300, 100, size[0] - 350, size[1] - 150))
pygame.draw.rect(scr, BLACK, (300, 100, size[0] - 350, size[1] - 150), width=10)
"""SUBJECTS"""
for subject in Groups.G_Subject:
if run_bool == True:
subject.update__()
scr.blit(subject.img, subject.rect)
"""GUI"""
pygame.draw.line(scr, WHITE, (200, 0), (200, size[1]), width=3)
scr.blit(ARAL_20_ITALIC.render('Subjects' ,False, WHITE), (30, 10))
add_hitbox = scr.blit(Images.add_img.convert_alpha(), (30, 50))
remove_hitbox = scr.blit(Images.remove_img.convert_alpha(), (30, 90))
scr.blit(ARAL_20_ITALIC.render(f'Subjects: {len(Groups.G_Subject)}' , False, WHITE), (30, 130))
if run_bool == False:
run_hitbox = scr.blit(Images.run_img.convert_alpha(), (210, size[1] - 40))
else:
run_hitbox = scr.blit(Images.stop_img.convert_alpha(), (210, size[1] - 40))
# Updating Screen
pygame.display.flip()
# Events
for event in pygame.event.get():
# Quitting App
if event.type == pygame.QUIT:
pygame.quit()
quit()
# Clicking
if event.type == pygame.MOUSEBUTTONDOWN:
mouse = pygame.mouse.get_pos()
if add_hitbox.collidepoint(mouse[0], mouse[1]):
rand_x = random.randint(terrain_hitbox[0], terrain_hitbox[0] + terrain_hitbox[2])
rand_y = random.randint(terrain_hitbox[1], terrain_hitbox[1] + terrain_hitbox[3])
Classes.Subject(rand_x, rand_y, Images.subject0_img.convert_alpha())
if remove_hitbox.collidepoint(mouse[0], mouse[1]) and not 1 > len(Groups.G_Subject):
Groups.G_Subject.remove(random.choice(Groups.G_Subject.sprites()))
if run_hitbox.collidepoint(mouse[0], mouse[1]):
if run_bool == True:
run_bool = False
else:
run_bool = True
Classes.py :
import pygame, Groups, random, math
class Subject(pygame.sprite.Sprite):
def __init__(self, x, y, img=pygame.image.load('assets/img/subject0.png').convert_alpha(pygame.display.set_mode((800, 600))), speed=5):
super().__init__()
self.img = img
self.rect = self.img.get_rect()
self.rect.x = x
self.rect.y = y
self.oldx = self.rect.x
self.oldy = self.rect.y
self.speed = speed
Groups.G_Subject.add(self)
def calculat_new_xy(self, old_xy, speed, angle_in_radians):
new_x = old_xy.x + (speed * math.cos(angle_in_radians))
new_y = old_xy.y + (speed * math.sin(angle_in_radians))
return new_x, new_y
def update__(self):
self.oldx, self.oldy = self.rect.x, self.rect.y
self.rect.x, self.rect.y = self.calculat_new_xy(self.rect, self.speed, random.randint(1, 360))
Images.py :
import pygame
add_img = pygame.transform.scale(pygame.image.load('assets/img/gui/add.png'), (90, 30))
remove_img = pygame.transform.scale(pygame.image.load('assets/img/gui/remove.png'), (90, 30))
subject0_img = pygame.image.load('assets/img/subject0.png')
run_img = pygame.transform.scale(pygame.image.load('assets/img/gui/run.png'), (90, 30))
stop_img = pygame.transform.scale(pygame.image.load('assets/img/gui/stop.png'), (90, 30))
Groups.py :
import pygame
G_Subject = pygame.sprite.Group()
I do know my code is a mess, so if you don't wanna help, it's ok ! Thx in advance :D
I want that when I click on one of the possible squares an X is printed.
I have made a list of all the coordinates of the tic-tac-toe grid. I've also added the centre points of each of the rectangles coordinates. I'm trying to make that if I click within the area of one of the squares that the an X button shows up. The eventual aim is to make that a double-click results in a lodged result.
import matplotlib.pyplot as plt
import pygame
import sys
import pygame
pygame.font.init()
size = 320, 240
black = 0, 0, 0
white = 255,255,255
red = 255, 0, 0
x1y1 = [(100, 0), (100, 300)]
x2y2 = [(200, 0), (200, 300)]
x3y3 = [(0, 100), (300, 100)]
x4y4 = [(0, 200), (300, 200)]
def centroid(coord1, coord2):
xx = 50
yy = 50
coords = []
for a in range(0,3):
for b in range(0,3):
if a + 1 == int(coord1) and b + 1 == int(coord2):
coords += tuple([xx + a*100, yy + b*100])
return tuple(coords)
def fourCorners(a,b,length,width):
center = (a, b)
corner3 = (int(a + length/2), int(b + width/2))
corner2 = (int(a + length/2), int(b - width/2))
corner4 = (int(a - length/2), int(b + width/2))
corner1 = (int(a - length/2), int(b - width/2))
return [corner1 ,corner2 ,corner3 ,corner4]
def withinRect(a,b,corners):
if len(corners) != 4:
print('Pass a list parameter of length 4.')
elif int(corners[0][0]) >= int(a) >= int(corners[1][0]) and int(corners[0][1]) >= int(b) >= int(corners[1][1]):
return True
screen = pygame.display.set_mode((300,300))
screen.fill(white)
pygame.draw.line(screen, (0, 0, 255), x1y1[0], x1y1[1], 3)
pygame.draw.line(screen, (0, 0, 255), x2y2[0], x2y2[1], 3)
pygame.draw.line(screen, (0, 0, 255), x3y3[0], x3y3[1], 3)
pygame.draw.line(screen, (0, 0, 255), x4y4[0], x4y4[1], 3)
while True:
ev = pygame.event.get()
for event in ev:
# handle get_pressed
if event.type == pygame.MOUSEBUTTONUP:
pos = pygame.mouse.get_pos()
x, y = event.pos
v = fourCorners(centroid(1,1)[0],centroid(1,1)[1],100,100)
button = pygame.Rect(v[1][0], v[1][1] ,100,100)
if (withinRect(x,y,v) == True and button.collidepoint(event.pos)):
print('X')
pygame.display.flip()
else:
break
pygame.display.flip()
Traverse all the 9 fields by 2 nested loos and define a pygame.Rect for the corresponding field:
for a in range(3):
for b in range(3):
button = pygame.Rect(a*100, b*100, 100, 100)
If you want you can define a margin, to limit the area to the center of a field:
margin = 4
button = pygame.Rect(a*100+margin, b*100+margin, 100-2*margin, 100-2*margin)
Use colliderect() to check if the click was inside the area:
if button.collidepoint(pos):
# [...]
Draw a line from the .bottomleft to the .topright and a line from the .topleft to the .bottomright, to form a cross:
pygame.draw.line(screen, (0, 0, 255), button.bottomleft, button.topright, 3)
pygame.draw.line(screen, (0, 0, 255), button.topleft, button.bottomright, 3)
ev = pygame.event.get()
for event in ev:
if event.type == pygame.MOUSEBUTTONUP:
pos = pygame.mouse.get_pos()
margin = 4
for a in range(3):
for b in range(3):
button = pygame.Rect(a*100+margin, b*100+margin, 100-2*margin, 100-2*margin)
if button.collidepoint(pos):
pygame.draw.line(screen, (0, 0, 255), button.bottomleft, button.topright, 3)
pygame.draw.line(screen, (0, 0, 255), button.topleft, button.bottomright, 3)
I'm playing with pygame and I'm making a memory game.
In my memory game I need to have a small Menu on the middle-bottom of the screen and the player list on top of the screen. The game manager will output the message in the upper left corner of the screen.
The playable board where the cards will be situated should be in the center of the screen with a small margin on the right and the left and about 200px from the top and the bottom.(e.g. if the screen width and height are 1200 x 800, the playable board should be from 2 to 1998 width and 200 to 600 height.
I've made a screenshot of the idea I have:
I've put up the idea of the scrollable board with the cards, although it's hardcoded for now, I mostly need the idea not the code since I just have a hard time figuring out how it should work.
The playable board (game board on the screenshot), should be moved through the keyboard(so up, right, down, left arrows). The thing I want to do is to allow more cards than 4 in each column and 12 in each row, in a way that when there are more cards than the 12 x 4, the gameboard could be moved, but without exiting the red square marked on the screenshot(so it's a scrollable surface). I want to limit the scrollable area to a width of 1200 and height of 400 px's, but in a way that the area is scrollable only inside the red area.
If that can't work or is planned otherwise, is there any way to register clicks differently? Let's say when I move the keys in a way that the Menu area is above the game board and the menu is clicked, I don't want the card under the Menu to be clicked as well. Is there any way to lock the content under the Menu?
Adding the code sample below:
from pygame import *
import sys
init()
screen_width = 1200
screen_height = 800
screen = display.set_mode((screen_width, screen_height))
board_width = 2000
board_height = 1000
playable_board = Surface((board_width, board_height))
playable_board = playable_board.convert()
screen.fill((255, 255, 255))
#draw.rect(playable_board, (125, 125, 125), (2, 200, board_width, board_height))
for x in range(0, 50):
draw.rect(playable_board, (100, 100, 100), (x * 100 + 5, 200, 90, 90))
draw.rect(playable_board, (100, 100, 100), (x * 100 + 5, 310, 90, 90))
draw.rect(playable_board, (100, 100, 100), (x * 100 + 5, 420, 90, 90))
draw.rect(playable_board, (100, 100, 100), (x * 100 + 5, 530, 90, 90))
# draw.rect(playable_board, (100, 100, 100), (x * 100 + 5, 640, 90, 90))
point_x = 0
point_y = 0
point_change = -30
animationTimer = time.Clock()
endProgram = False
while not endProgram:
for e in event.get():
pass
key_pressed = key.get_pressed()
if e.type == QUIT:
endProgram = True
if e.type == MOUSEBUTTONUP:
mouse_x, mouse_y = e.pos
display.set_caption(('Coordinates: ' + str(mouse_x + abs(point_x)) + 'x' + str(mouse_y + abs(point_y))))
draw.rect(playable_board, (50, 200, 50), (mouse_x + abs(point_x), mouse_y + abs(point_y), 10, 10))
if key_pressed[K_LEFT]:
point_x -= point_change
if key_pressed[K_RIGHT]:
point_x += point_change
if key_pressed[K_UP]:
point_y -= point_change
if key_pressed[K_DOWN]:
point_y += point_change
if point_x > 0:
point_x = 0
if point_x < -(board_width - screen_width):
point_x = -(board_width - screen_width)
if point_y > 0:
point_y = 0
if point_y < -(board_height - screen_height):
point_y = -(board_height - screen_height)
screen.blit(playable_board, (point_x, point_y, screen_width, screen_height))
draw.rect(screen, (100, 255, 100), (500, 650, 200, 150))
draw.rect(screen, (100, 255, 100), (100, 0, 1000, 50))
draw.rect(screen, (100, 255, 100), (0, 70, 250, 100))
animationTimer.tick(60)
display.update()
I'd create a subsurface of the playable_board and then blit it at the desired coordinates. You need to define a rect (called area here) with the desired size of the subsurface and scroll it by incrementing the x and y attributes. To keep the area rect inside of the board_rect (to prevent ValueErrors), you can call the pygame.Rect.clamp or clamp_ip methods.
import pygame as pg
pg.init()
screen = pg.display.set_mode((1200, 800))
board = pg.Surface((2000, 1000))
board.fill((20, 70, 110))
board_rect = board.get_rect()
# Append the rects to a list if you want to use
# them for collision detection.
rects = []
for y in range(11):
for x in range(22):
rect = pg.draw.rect(board, (100, 100, 100), (x*95, y*95, 90, 90))
rects.append(rect)
# The blit position of the subsurface.
pos = (20, 200)
point_change = -10
# This rect is needed to create a subsurface with the size (1160, 400).
area = pg.Rect(0, 0, 1160, 400)
# Use the area rect to create a subsurface of the board.
board_subsurface = board.subsurface(area)
clock = pg.time.Clock()
endProgram = False
while not endProgram:
for e in pg.event.get():
if e.type == pg.QUIT:
endProgram = True
key_pressed = pg.key.get_pressed()
# Scroll by changing the x and y coordinates of the area rect.
if key_pressed[pg.K_LEFT]:
area.x += point_change
elif key_pressed[pg.K_RIGHT]:
area.x -= point_change
if key_pressed[pg.K_UP]:
area.y += point_change
elif key_pressed[pg.K_DOWN]:
area.y -= point_change
# Check if one of the movement keys was pressed.
if any(key_pressed[key] for key in (pg.K_LEFT, pg.K_RIGHT, pg.K_UP, pg.K_DOWN)):
# Clamp the area rect to the board_rect, otherwise calling
# `board.subsurface` could raise a ValueError.
area.clamp_ip(board_rect)
# Create a new subsurface with the size of the area rect.
board_subsurface = board.subsurface(area)
screen.fill((30, 30, 30))
screen.blit(board_subsurface, pos)
pg.draw.rect(screen, (100, 255, 100), (500, 650, 200, 150))
pg.draw.rect(screen, (100, 255, 100), (100, 0, 1000, 50))
pg.draw.rect(screen, (100, 255, 100), (0, 70, 250, 100))
clock.tick(60)
pg.display.update()