Pygame Boundary Not Working - python

I'm a kid in middle school and hope to be a programmer when I grow up.I'm going to a summer school coding class and learning python and pygame.I already knew enough python but just got my hands wet in pygame.I was adding trying to add a boundary for my game but it's able to block the left and top of the screen here is my code:
import pygame,sys
from pygame.locals import *
pygame.init()
WIDTH = 400
HEIGHT = 400
pg = "player.gif"
bg = "y.gif"
screen=pygame.display.set_mode((WIDTH,HEIGHT))
background = pygame.image.load(bg)
player = pygame.image.load(pg)
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit
x,y = pygame.mouse.get_pos()
screen.blit(background,[0,0])
screen.blit(player,(x,y))
pygame.display.update()
if x <= WIDTH:
x = 0
if y <= HEIGHT:
y = 0

if x <= WIDTH:
x = 0
if y <= HEIGHT:
y = 0
Is this really what you want to do? Set x and y to zero whenever the mouse is positioned within the boundaries? OR do you want to limit x and y to only existing within the range from 0 to WIDTH or HEIGHT respectively?
x = min(max(x, 0), WIDTH)
y = min(max(y, 0), HEIGHT)
Furthermore, note that your player sprite has a width and height of its own. The x,y coordinate represents the location of the top-left corner of the sprite. If you want to restrict the sprite's position such that the entire thing is always on the screen, you first need to get the size of the sprite
spriteWidth, spriteHeight = player.get_rect().size
And then factor that size into your boundary calculation
x = min(max(x, 0), WIDTH - spriteWidth)
y = min(max(y, 0), HEIGHT - spriteHeight)
Additionally, you need to make sure you do this before you call screen.blit(player, (x, y)), or else the sprite will be drawn with the original, unbounded coordinates.

Related

how can i move a rectangle in py game without mouse and keys?

I have code to move a rectangle in pygame from left to right, up and down.
But I want to move my rectangle around the screen that I created..
can someone help me please?
import pygame
from pygame.locals import *
pygame.init()
FPS = 70
fpsclock = pygame.time.Clock()
SIZE = (1000, 700)
form1 = pygame.display.set_mode(SIZE)
WHITE=(255, 255, 255)
x = 0
y = 0
w = 50
h = 60
direction = 'right'
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
form1.fill(WHITE)
pygame.draw.rect(form1, (255, 0, 0), (x, y, w, h), 1)
pygame.display.update()
fpsclock.tick(FPS)
if x,y==0,0:
direction='right'
if x,y==1200-50,0:
direction='down'
if x,y==1200-50,700-60:
direction='left'
if x,y==0,1200-50:
direction='right'
So the first thing you have to look at is the spacing. Even though your code works (after proper indentation) the square goes out of bounds.
The same thing applies to y as well if the square should go up and down.
If you want the square to go around you just need to go left, right, up, or down at the correct time. So if you want to start at the left upper corner and go around you just need to check if the square is in a corner and then change the direction accordingly.
Keep in mind that going down actually increases and going up decreases y.
EDIT:
Here you can see the result of my proposed concept
EDIT 2:
I've copied your code and refactored and completed it. I tried to explain why I did what I did.
import pygame
# Only import the things you need it makes debugging and documenting
# alot easier if you know where everything is coming from
from pygame.locals import QUIT
FPS = 70
# Use variables to define width and height in case you want to
# change it later
width = 200
height = 200
# I would group variables by usage, for example, I would group width,
# height of the screen and w, h of the box so I can easily manipulate
# the numbers if want to.
w = 50
h = 60
# Using an offset variable reduces the hardcoded numbers even more
# if its 0 it will just move along the border but if it >0 it will move
# the square to the centre
offset = 20
# You can declare it as a variable if you need the SIZE tuple somewhere
# else, if you don't need it you can just set it as
# pygame.display.set_mode((width, height))
SIZE = (width, height)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
x = offset
y = offset
direction = 'right'
# This is just my preference but I like to have the variables that are
# changeable at the top for easy access. I think this way the code is
# cleaner.
pygame.init()
fpsclock = pygame.time.Clock()
form1 = pygame.display.set_mode(SIZE)
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
form1.fill(WHITE)
# Try to avoid hardcoded numbers as much as possible, hardcoded
# numbers are hard to change later on when the code gets to certain
# size and complexity.
pygame.draw.rect(form1, RED, (x, y, w, h), 1)
pygame.display.update()
fpsclock.tick(FPS)
# Don't harcode conditions, use variables so you can easily change
# them later
if x == offset and y == offset:
direction='right'
if x == width - w - offset and y == offset:
direction='down'
if x == width - w - offset and y == height - h - offset:
direction='left'
if x == offset and y == height - h - offset:
direction='up'
if direction == 'right':
x += 5
elif direction == 'down':
#Keep in mind going down actually means to increment y
y += 5
elif direction == 'left':
x -= 5
elif direction == 'up':
y -= 5

How to solve ValueError raised when drawing a circle with a width value greater than its radius is pygame module?

I am coding for a fourier transform simulation. Here I have to draw many epicycles. I have a some values of radius which are less than 1 like: 7x10^-14, 0 etc. So, when I draw the circles and assign border width to 1 , I get a value error: width greater than radius. If I put the border width to zero then the circle becomes filled up in color and seems very ugly. So, please show me a way to how I can draw a cricle with a border and radius values less than 1. Here is the code:
radius_list = [0.0, 8.539660890638339e-15, 66.66666666666669, 3.3275832379191784e-14, `
1.1234667099445444e-14, 2.534379764899661e-14, 33.333333333333336, 1.018719954857117e-14,
2.0236265985141534e-14, 2.4825216024150285e-14, 66.66666666666674, 1.5953403096630258e-13`]
run = False
while not run:
clock.tick(fps)
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
x = x_pos
y = y_pos
for i in range(iteration):
prevx = x
prevy = y
frequency = freq_list[i]
radius = radius_list[i]
phase = phase_list[i]
print(radius)
x+= int(radius*math.cos((frequency*time) + phase + math.pi/2))
y+= int(radius*math.sin((frequency*time) + phase + math.pi/2))
**pygame.draw.circle(screen, white, (prevx, prevy), int(radius),1)**
[...] please show me a way to how I can draw a circle with a border and radius values less than 1
You can't. The unit of drawing in PyGame is pixels. PyGame cannot draw half a pixel. It makes no sens to draw something with a size less than 1, because PyGame can't do it:
Ensure that the minimum radius is 1:
pygame.draw.circle(screen, white, (prevx, prevy), min(1, round(radius)), 1)
All you can do is to skip the circles with a radius less than 0.5:
if radius >= 0.5;
pygame.draw.circle(screen, white, (prevx, prevy), min(1, round(radius)), 1)

Detect collision between textbox and circle in pygame

I'm trying to create a game in python where one can drag a textbox around the screen, but whenever it touches the borders of a circle around it, I want the loop to start over, but with a different text (by storing all text-strings in a list, but I'm not that far, yet). This is how far I have come:
import pygame
import ptext
pygame.init()
gameDisplay = pygame.display.set_mode((500, 500))
gameDisplay.fill((255,255,255))
x = 190
y = 230
a = 250
b = 250
text = "ExampleText 1."
def textbox(x,y):
ptext.draw(text, (x,y), color = (0,0,0))
def circle(a,b):
pygame.draw.circle(gameDisplay, (0,0,0), (250, 250), 210, 5)
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.MOUSEMOTION:
if event.buttons[0]:
x += event.rel[0]
y += event.rel[1]
textbox(x,y)
circle(a,b)
pygame.display.flip()
pygame.quit()
quit()
Now I understand I will need to detect collision of the borders of my objects, but here I'm pretty lost. I tried to store the variables of my objects in rectangles and then produce another if statement that recognizes whether or not my objects collide (I used a print command because I haven't gotten to the actual command I want, yet), but that won't print anything and I'm sure I'm on the wrong path, but it is my best effort...
For that I have defined:
text_rect = pygame.Rect(x, y, 10, 30)
circle_rect = pygame.Rect(a,b, 300, 300)
and then in my loop:
if circle_rect.colliderect(text_rect):
print("COLLIDE")
Does anybody have any tip on a better way to define the objects and to create the function I want?
(Edit: Btw.: I'm not too concerned about the fact that when I drag my textbox, it leaves a print of the text, since that doesn't happen in my original script, but would be thankful if anyone knows why it does that in my current example.)
A rectangle has 4 corner points. If the rectangle is "smaller" then the circle (the diameter of the circle is greater than the diagonal of the rectangle), then the rectangle collides with the contour of a circle, if at least one point is out of the circle and at least one point is in the circle.
Define the rectangle and setup a list of the corner points. Further you've to know the radius of the circle:
w, h = 10, 30
rect = pygame.Rect(x, y, 10, 30)
corners = [rect.bottomleft, rect.bottomright, rect.topleft, rect.topright]
radius = 210
Calculate the Euclidean distance of each corner point to the center of the circle (a, b):
import math
dist = [math.sqrt((p[0]-a)**2 + (p[1]-b)**2) for p in corners]
Create to lists, one with the points in the circle (p_in) and one with the points out of the circle (p_out):
p_out = [i for i, d in enumerate(dist) if d > radius]
p_in = [i for i, d in enumerate(dist) if d < radius]
If both list contain any element, then the rectangle intersects the circle contour:
if any(p_in) and any(p_out):
print("COLLIDE")
If len(p_in) is 4, then the rectangle is completely in the circle. If len(p_out) is 4 then the rectangle is completely out of the circle.
if any(p_in) and any(p_out):
print("COLLIDE")
elif len(p_in) == 4:
print("IN")
elif len(p_out) == 4:
print("OUT")
See the simple example, which is based on your code snippet and demonstrates the collision test. The rectangle is attached to the mouse:
import pygame
import math
pygame.init()
gameDisplay = pygame.display.set_mode((500, 500))
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
x, y = pygame.mouse.get_pos()
w, h = 10, 30
rect = pygame.Rect(x, y, 10, 30)
a, b = 250, 250
radius = 210
corners = [rect.bottomleft, rect.bottomright, rect.topleft, rect.topright]
dist = [math.sqrt((p[0]-a)**2 + (p[1]-b)**2) for p in corners]
p_out = [i for i, d in enumerate(dist) if d > radius]
p_in = [i for i, d in enumerate(dist) if d < radius]
if any(p_in) and any(p_out):
print("COLLIDE")
elif len(p_in) == 4:
print("IN")
elif len(p_out) == 4:
print("OUT")
gameDisplay.fill((255,255,255))
pygame.draw.rect(gameDisplay, (255, 0, 0), rect)
pygame.draw.circle(gameDisplay, (0,0,0), (a, b), radius, 5)
pygame.display.flip()
pygame.quit()
quit()

Pygame lags with pygame rectangles added

I'm running a python game with approximately 2k rectangles being drawn on screen each frame.
The problem I have is that it runs on 12 fps and I have no idea how to fix that. When I remove all the rectangles it shifts to 100 fps. I'm not rendering all of them at once, but only the ones camera can see currently. How to fix this lag spike issue, is it because I'm using pygame rectangles or because I'm using them wrong?
here's the code
import pygame
black = (0,0,0)
pygame.init()
gameDisplay = pygame.display.set_mode((0,0),pygame.FULLSCREEN)
gameDisplay.fill(black)
gameDisplay.convert()
clock = pygame.time.Clock()
display_width = 1920
display_height = 1080
from opensimplex import OpenSimplex
tmp = OpenSimplex()
dimensions = [100,100]
size = 40
def mapping(x):
y = (x + 1) * 10 + 40
return y
class GroundCell:
def __init__(self,x,y,dim):
self.x = x
self.y = y
self.dim = dim
tempcells = []
allCells = []
for a in range(0,dimensions[0]):
tempcells = []
for b in range(0,dimensions[1]):
tempcells.append(GroundCell(a*size,b*size,mapping(tmp.noise2d(a*0.11,b*0.11))))
allCells.append(tempcells)
font = pygame.font.Font("freesansbold.ttf", 20)
while True:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
pygame.quit()
quit()
for a in allCells:
for b in a:
if b.x in range(0,display_width) \
and b.y in range(0,display_height) or\
b.x+size in range(0,display_width) \
and b.y+size in range(0,display_height) :
pygame.draw.rect(gameDisplay,(b.dim,b.dim,b.dim),(b.x,b.y,size,size))
fps = font.render('FPS: ' + str(int(clock.get_fps())), 1, (0, 0, 0))
gameDisplay.blit(fps, (20, 20))
pygame.display.update()
clock.tick(120)
gameDisplay.fill(black)
Instead of drawing all those rects to the screen every tick, create a Surface with the noise once, and reuse it.
Some more notes:
Text rendering is expensive. If you use a lot of text rendering, better cache the surfaces created by Font.render. Note there's also the new freetype module in pygame, which is better than the font module in every way.
Your check if a rect is inside the screen is very strange. You could just use something like if 0 < b.x < display_width ... or even use pygame's Rect class, which offers nice methods like contains().
A good and fast way to create a Surface from arbitary data is to use numpy and pygame's surfarray module. Don't get intimidated, it isn't that hard to use.
Here's a running example based on your code:
import pygame
import numpy as np
black = (0,0,0)
pygame.init()
display_width = 1000
display_height = 1000
gameDisplay = pygame.display.set_mode((display_width, display_height))
gameDisplay.fill(black)
clock = pygame.time.Clock()
from opensimplex import OpenSimplex
tmp = OpenSimplex()
dimensions = [100,100]
size = 16
def mapping(x):
y = (x + 1) * 10 + 40
return y
# create an 2d array from the noise
def get_array():
rgbarray = np.zeros((dimensions[0], dimensions[1]))
for x in range(dimensions[0]):
for y in range(dimensions[1]):
c = int(mapping(tmp.noise2d(x*0.11, y*0.11)))
# simple way to convert the color value to all three (r,g,b) channels
rgbarray[x, y] = c | c << 8 | c << 16
return rgbarray
# create the array and copy it into a Surface
data = get_array()
surface = pygame.Surface((dimensions[0], dimensions[1]))
pygame.surfarray.blit_array(surface, data)
# scale the Surface to the desired size
scaled = pygame.transform.scale(surface, (dimensions[0]*size, dimensions[1]*size))
# simple way to cache font rendering
font = pygame.font.Font("freesansbold.ttf", 20)
cache = {}
def render(text):
if not text in cache:
cache[text] = font.render(text, 1, (0, 0, 0))
return cache[text]
x = 0
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
x-=1
if x < -1000:
x = 0
gameDisplay.blit(scaled, (x, 0))
fps = render('FPS: ' + str(int(clock.get_fps())))
gameDisplay.blit(fps, (20, 20))
pygame.display.update()
clock.tick(120)
As #sloth explained, there are better ways to do the same, but if you want to know what the actual problems are:
You're not drawing 2000 rectangles, you're drawing 10.000 of them, because your dimension is 100x100
You're doing a check if the rectangle is visible in the worst possible way in terms of performance. Just check what happens if you leave the check and don't draw the rectangles. You'll see that the performance is improved but it's still far from 120fps. That's because for every rectangle you generate a list of numbers from 0 to the width of the screen and another list from zero to the height of the screen. You also do that twice. That means, on a 1920x1080 screen: (1920 * 10000) + (1920 * 10000) + (1080 * 10000) + (1080*10000) = 60000000. So, 60 million checks. If you have 120fps, that means 60 million * 120 = 7.2 billion checks per second.
Just changing the check to something similar to if b.x+size < display_width and b.y+size < display_height and b.x > 0 and b.y > 0: will already improve the performance.
That said, it's still 10000 rectangles, and it's still 120fps, which means 1200000 rectangles per second, done with basically no HW acceleration and with a high level language. Don't expect top performance.

Ellipse Tool in Python part 2

I am currently making changes to the way my ellipse tool works as it was not working the correct way previously. I am creating it for my paint program using python 2.7.5 and pygame. I have recently encountered this error:
Traceback (most recent call last):
File "C:\Users\Wisdom1\Desktop\Comp Science Files\Canvas.py", line 164, in <module>
draw.ellipse(screen,(c),(x,y,radx,rady),sz2)
ValueError: width greater than ellipse radius
This occurs when I try to create an ellipse going in every direction except downward to the right from a point. I understand the error I just do not know how to fix it. Here is my ellipse tool:
if mb[0] == 1 and canvas.collidepoint(mx,my):
screen.set_clip(canvas)
if tool == "ellipse":
screen.blit(copy,(0,0))
radx = max(mx-x,1)
rady = max(my-y,1)
draw.ellipse(screen,(c),(x,y,radx,rady),sz2)
screen.set_clip(None)
Sz2 is a size variable that begins at 10 and decreases or increases by 3 each time the mouse wheel is moved down or up. Any help is appreciated. Thank you
You could also use a ternary statement in the form of:
draw.ellipse(screen,(c),(x,y,radx,rady), sz2 if sz2 < max(radx, raxy) else 0)
Sincerely,
Another Massey student working on a Sunday night ;)
If sz2, the thickness of the curve, is greater than the minor radius of the ellipse, pygame raises a ValueError. So you could protect against this by using an if-statement:
if sz2 < min(radx, rady)//2:
pygame.draw.ellipse(self.screen, green, box, sz2)
else:
# sz2=0 fills the ellipse
pygame.draw.ellipse(self.screen, green, box, 0)
radx, rady is the width and height of the Rect bounding the ellipse. So the minor radius is half the smaller of radx and rady.
Here is some runnable code showing that the if-statement works:
"""Based on http://www.pygame.org/docs/tut/intro/intro.html"""
import sys
import pygame
pygame.init()
size = (width, height) = (320, 240)
screen = pygame.display.set_mode(size)
black = (0,0,0)
green = [0, 255, 0]
radx, rady = 50, 70
box = [160-radx//2, 120-rady//2, radx, rady]
width = 1
delta = 2
while True:
for event in pygame.event.get():
if ((event.type == pygame.QUIT) or
(event.type == pygame.KEYDOWN and
event.key == pygame.K_ESCAPE)):
sys.exit()
screen.fill(black)
if 0 < width < min(radx, rady)//2:
pygame.draw.ellipse(screen, green, box, width)
else:
if width > 0:
pygame.draw.ellipse(screen, green, box, 0)
delta *= -1
width += delta
pygame.display.flip()
pygame.time.delay(100)

Categories