Pygame Collision Explained - python-3.x

I am brand new to Python/Pygame. I have been able to piece together a few things in small test programs but trying to get a grasp of collision is just not happening. I've tried working through multiple tutorials but I feel like I need it completely broken down before I get a good sense of it.
Can someone explain how something like colliderect(Rect) works?
Do I need a class for the collide functions or can I just take two rectangles and get a result?
The code I am trying to work with is below. I was able to make two images, each surrounded by a rect, I am assuming the collision only works with sprites and not images? I can control one of the images with my mouse, and the would like to trigger a collision detection when I mouse over and click on the static image.
Any help would be greatly appreciated.
Thx
~T
pygame.init()
clock = pygame.time.Clock()
size=width, height= 600,400
screen = pygame.display.set_mode(size)
rageface = pygame.image.load("rageface.jpg")
rageface = pygame.transform.scale(rageface,(100,100))
rageface2 = pygame.transform.scale(rageface,(100,100))
black = (100,0,0)
green = (0,150,150)
r=0
x=50
y=50
mx,my = pygame.mouse.get_pos()
while 1:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == MOUSEBUTTONDOWN:
mx,my = pygame.mouse.get_pos()
#colliderect(Rect) ????????????????????
screen.fill((r,0,0))
pygame.draw.rect(screen, black, [50, 50, 250, 100], 0)
pygame.draw.rect(screen, green, [mx-50, my-50, 250, 100], 0)
screen.blit(rageface2,(mx-50,my-50))
screen.blit(rageface,(x,y))
clock.tick(60)
pygame.display.flip()

colliderect works like this:
rect1.colliderect(rect2)
This will return True if the two rects are colliding, False otherwise.
here is the documentation
colliderect is a method of the pygame Rect class, sprites also have collision methods
In pygame an image that is loaded is a surface just like the pygame screen/display, to get a rect from a surface you use:
Surface.get_rect()
So to get one for say rageface just do rage_rect = rageface.get_rect() and now you can use rage_rect like a rect
Tell me if this is clear or if you need more help!

Related

Generation of color fade-in effect using Pygame

When playing around with opacity in pygame, I noticed that when you neglect to fill the primary Surface created by pygame.display.set_mode() with a color. You can create a pretty neat color fade-in effect by blitting another Surface on top of it. Oddly, it seems this affect is controlled by the value of alpha and the tick rate of pygame.time.Clock()
If the value of alpha is >= the tick rate, the effect is instant, and when the value of alpha is < the tick rate, the speed of the effect seems to lengthen as the difference between tick rate - alpha increases. I currently have no idea why this is the case, so I'm hoping someone more experienced with the library can provide insight into the cause of the effect.
import pygame
def main():
button_rect = pygame.Rect(0, 0, 200, 40)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit()
# When the window is not filled with a color you get a cool
# fade-in effect.
# window.fill((0, 0, 0))
container = pygame.Surface((848, 480), pygame.SRCALPHA)
container.fill((30, 30, 30))
# Place button in center of screen.
button_rect.center = window.get_rect().center
mouse_pos = pygame.mouse.get_pos()
# The value of alpha affects the speed of the effect
alpha = 10
if button_rect.collidepoint(mouse_pos):
button = pygame.draw.rect(container,
(255, 0, 0, alpha),
button_rect)
else:
button = pygame.draw.rect(container,
(255, 255, 255, alpha),
button_rect)
window.blit(container, (0, 0))
window.blit(text, text.get_rect(center=button.center))
pygame.display.update(container.get_rect())
# Clock speed affects fade-in time as well.
clock.tick(60)
if __name__ == "__main__":
initalized, unintialized = pygame.init()
window = pygame.display.set_mode((848, 480), pygame.RESIZABLE)
clock = pygame.time.Clock()
font = pygame.font.Font("../assets/fonts/terminal.ttf", 20)
text = font.render("Hello", 0, (0,0,0))
main()
You draw a transparent surface over the window surface in each frame. This creates a fade in effect. The drawing in the window becomes more and more solid the more often you put the transparent surface over it. Increasing the tick rate and doing it more often will make the fade in faster. Increasing the alpha value makes the Surface you're laying over the window less transparent, and the fad-in becomes faster as well.
See also How to fade in a text or an image with PyGame.

Blit function not working for displaying an image

I've currently been following along with a video over pygame and creating a space invaders type of game that I will modify for my own implementation of the game. However I've gotten to the section where you have to use the blit function in order to display an image for the background of the game and it's not working for me. Once the program is executed, the window is launched but without the image displayed for the background. I believe that I have properly loaded the image and stepped into the function that will use the blit function for displaying the background image as well. Please let me know if I have overlooked anything, any help will be greatly appreciated.
import pygame
import os
import time
import random
#Setting window frame
WIDTH, HEIGHT = 1000, 600
WINDOW = pygame.display.set_mode((WIDTH,HEIGHT))
pygame.display.set_caption("Space Battle")
#Image Loading
RED_SPACESHIP = pygame.image.load(os.path.join("assets", "pixel_ship_red_small.png"))
GREEN_SPACESHIP = pygame.image.load(os.path.join("assets", "pixel_ship_green_small.png"))
BLUE_SPACESHIP = pygame.image.load(os.path.join("assets", "pixel_ship_blue_small.png"))
#Ship for player
YELLOW_SPACESHIP = pygame.image.load(os.path.join("assets", "pixel_ship_yellow.png"))
#Laser loading
YELLOW_LASER = pygame.image.load(os.path.join("assets", "pixel_laser_yellow.png"))
RED_LASER = pygame.image.load(os.path.join("assets", "pixel_laser_red.png"))
GREEN_LASER = pygame.image.load(os.path.join("assets", "pixel_laser_green.png"))
BLUE_LASER = pygame.image.load(os.path.join("assets", "pixel_laser_blue.png"))
Here is the code for loading in the back ground image
#Loading Backgrnd Img
BG = pygame.image.load(os.path.abspath("assets/background-black.png"))
formatedBG = pygame.transform.scale(BG, (WIDTH,HEIGHT))
Here is the code for the main function and handling background display.
def window_Redraw():
WINDOW.blit(formatedBG, (0,0))
pygame.display.update()
def main():
run = True
FPS = 60
clock = pygame.time.Clock()
while run:
clock.tick(FPS)
window_Redraw()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
main()
Here is a screenshot of the window after execution:
enter image description here
Your example is much more complicated that it should be. RED_SPACESHIP, BLUE_SPACESHIP... etc are irrelevant in this case and you should remove them.
You are displaying only a black background - nothing else, so it should be displayed. In order to check if everything is working, start from this example:
import pygame
# initialize the pygame module
pygame.init()
WIDTH, HEIGHT = 1000, 600
WINDOW = pygame.display.set_mode((WIDTH, HEIGHT))
formatedBG = pygame.Surface((WIDTH, HEIGHT))
formatedBG.fill(pygame.color.Color(255, 0, 0, 0))
# BG = pygame.image.load(os.path.abspath("assets/background-black.png"))
# formatedBG = pygame.transform.scale(BG, (WIDTH, HEIGHT))
run = True
while run:
WINDOW.blit(formatedBG, (0, 0))
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
And then uncomment the background to check whether the background changed to black color.
My previous version of pygame which was 1.9.4 was causing my initial issues with displaying the background image using blit and the problem with the window not being able to display any color with the fill function. I solved these issues by running the command 'pip install==2.0.0.dev4' to upgrade pygame. Everything seems to be working properly now.

PySimpleGUI how to make transparent click-through window?

I want to draw a circle on the screen but it should be click-through and I should be able to click somethings behind in this circle. So far, I achieved to draw circle and made it transparent but i couldn't make it click-through. I know that it is possible with pywin32 win32con.WS_EX_LAYERED flag but there is no information about this on PySimpleGUI documentation. How can I solve this problem?
My current window configuration:
window = sg.Window('Sample window', layout,
keep_on_top=True,
auto_size_buttons=False,
grab_anywhere=False,
no_titlebar=True,
return_keyboard_events=False,
alpha_channel=0.8,
use_default_focus=False,
transparent_color='red',
finalize=True)
If it is not possible with PySimpleGUI, is it possible to do with pywin32 module combination?
The transparent_color property can be used to create "click-through" windows. All you need to do is make whatever you want to be click through the same color as the transparent_color. Here is an example where you can toggle the transparency of a shape in a canvas to the transparent color and make it click through.
import PySimpleGUI as sg
layout = [
[sg.Canvas(size=(100, 100), key= 'canvas',)],
[sg.T('Change circle color to:'), sg.Button('Red'), sg.Button('Blue')]
]
window = sg.Window('Sample window', layout,
keep_on_top=True,
auto_size_buttons=False,
grab_anywhere=False,
no_titlebar=True,
return_keyboard_events=False,
alpha_channel=0.8,
use_default_focus=False,
transparent_color='red',
finalize=True)
window.Finalize()
canvas = window['canvas']
cir = canvas.TKCanvas.create_oval(50, 50, 100, 100)
while True:
event, values = window.read()
if event is None:
break
if event == 'Blue':
canvas.TKCanvas.itemconfig(cir, fill="Blue")
elif event == 'Red':
#Here is where the cirle changes to a recolor making it transparent and click through
canvas.TKCanvas.itemconfig(cir, fill="Red")
layout = [[sg.Button("Button) ],
[sg.Graph(canvas_size =(400,100),
graph_bottom_left=(-400,-400),
graph_top_right=(400,400),
background_color='red') ]]
window = sg.Window('Click through transparent',
layout,background_color='red',keep_on_top=True,
transparent_color='red', alpha_channel=.5,
grab_anywhere=True, resizable=True).Finalize()
while True:
event, values = window.Read()
if event == sg.WIN_CLOSED:
break
if event == 'Click':
print(" Button Clicked")
Remember to put keep_on_top = True
This works well on windows, don't know if other os

pygame.display.flip() seems to not be working?

I've been working on a project, and when I tested it this happened. After a little testing I soon realized it was something wrong in the pygame display flip part of the code. I really don't see what's wrong here, so I hope one of you do.
import pygame
pygame.init()
screen = pygame.display.set_mode((200, 200))
img = pygame.image.load("test.png")
while 1:
screen.fill((0, 0, 0))
screen.blit(img, (0, 0))
pygame.display.flip()
pygame.time.delay(10)
Now, the result is a blank white screen that's 200 by 200. I hope someone sees what's wrong here. Also, my png doesn't matter here because I get the same result with just the fill(black), so I hope someone knows.
Couple things here:
1) I would recommend using the pygame clock instead of time.delay. Using the clock sets the frames per second to run the code, where the time.delay just sits and waits for the delay.
2) Pygame is event driven, so you need to be checking for events, even if you don't have any yet. otherwise it is interpreted as an infinite loop and locks up. for a more detailed explanation: click here
3) I would offer a way out of the game loop with a flag that can be set to false, that way the program can naturally terminate
import pygame
pygame.init()
screen = pygame.display.set_mode((200, 200))
img = pygame.image.load("test.png")
clock = pygame.time.Clock()
game_running = True
while game_running:
# evaluate the pygame event
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_running = False # or anything else to quit the main loop
screen.fill((0, 0, 0))
screen.blit(img, (0, 0))
pygame.display.flip()
clock.tick(60)

Pygame sprite clamping isse

So Ive been having issues with getting a sprite to stay withing the bounds of the screen. I got it to work with a simple rect(0,0,16,16), but i cant seem to get it to work with a sprite being blit onto the screen. What do i need to change in order to keep my sprite clamped within the screen res? I only just started today using classes to orgonize code so any input is appreciated and helpful.
import pygame
from pygame.locals import *
from pygame import Color
class Game():
""" Lets try to get this going by simple steps
One by one. First step, lets figure how to make a class
that can do the display stuff. Lord have mercy on my soul"""
def __init__(self, wi=256, hi=224, multii=3):
"""Initialization"""
pygame.init()
self.runGame = True
self.width = wi*multii
self.height = hi*multii
self.spritesize = 16*multii
self.clock = pygame.time.Clock()
self.fps = self.clock.get_fps()
self.screen = pygame.display.set_mode((self.width, self.height))
self.kl = []
self.walk = [0, 0]
self.speed = multii*1.5
self.x,self.y = self.width/2, self.height/2
self.playerSpr = pygame.image.load('images/'+'link1.png').convert_alpha()
self.playerRec = Rect(self.playerSpr.get_rect())
def mainLoop(self):
"""Loop through the main game routines
1. Drawing 2. Input handling 3. Updating
Then loop through it until user quits"""
while self.runGame:
self.clock.tick(60)
self.events()
self.draw()
def events(self):
"""Time to handle some events"""
for e in pygame.event.get():
if (e.type == pygame.QUIT) or (e.type == KEYDOWN and e.key == K_ESCAPE):
self.runGame = False
break
if e.type==KEYDOWN:
if e.key==pygame.K_a: self.kl.append(1)
if e.key==pygame.K_d: self.kl.append(2)
if e.key==pygame.K_w: self.kl.append(3)
if e.key==pygame.K_s: self.kl.append(4)
if e.type==pygame.KEYUP:
if e.key==pygame.K_a: self.kl.remove(1)
if e.key==pygame.K_d: self.kl.remove(2)
if e.key==pygame.K_w: self.kl.remove(3)
if e.key==pygame.K_s: self.kl.remove(4)
if self.kl[-1:]==[1]: self.walk=[-self.speed, 0]
elif self.kl[-1:]==[2]: self.walk=[ self.speed, 0]
elif self.kl[-1:]==[3]: self.walk=[0,-self.speed]
elif self.kl[-1:]==[4]: self.walk=[0, self.speed]
else: self.walk=[0, 0]
self.x+=self.walk[0]
self.y+=self.walk[1]
def draw(self):
"""Draw and update the main screen"""
self.fps = self.clock.get_fps()
self.screen.fill(Color('purple'))
#print self.screen.get_rect()
#print player_rect
self.playerSpr.clamp_ip(self.screen.get_rect())
#pygame.draw.rect(self.screen, (255, 255, 255), self.playerrect)
self.screen.blit(self.playerSpr, (self.x,self.y), self.playerRec)
pygame.display.set_caption('Grid2. FPS: '+str(self.fps))
pygame.display.update()
game = Game()
game.mainLoop()
Why not use playerRec to keep track of the position of your player instead of the additional x and y attributes?
I suggest also using the move method (or move_ip):
def events(self):
for e in pygame.event.get():
...
self.playerRec.move_ip(*self.walk) # instead of self.x+=self.walk[0] / self.y+=self.walk[1]
def draw(self):
...
# probably do this right after 'move_ip'
self.playerRec.clamp_ip(self.screen.get_rect())
# note that 'blit' accepts a 'Rect' as second parameter
self.screen.blit(self.playerSpr, self.playerRec)
as a side note: You should consider using a Sprite, since it basically combines an Image and a Rect.
Two things:
You are not stopping movement of the sprite when it comes off the screen.
Make an move functions that will get a direction and will decide if it can move more to the side. That way when the right side of the sprite will be of screen, you will not move more to the right.
Since you put your direction keys in a list that works like a stack, you are only getting 1 direction per keypress. If you also want to move diagonally either make two lists one for both directions or use a easier method such as this:
if KEYDOWN == K_LEFT: direction_x = -1
if KEYUP == K_LEFT AND direction_x == -1: direction_x = 0
do this for every key.

Resources