I'm french so pardon my language error.
I try to make a little pygame-zero game with ball physic.
So I want to control precisely a ball object in time.
I created a Ball's class, which implement built-in Rect object (to use default collision in case).
import pygame
WIDTH = 300
HEIGHT = 300
class Color:
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
class Ball(pygame.Rect):
def __init__(self, cx, cy, radius, color: Color = Color.WHITE):
position = (cx - radius, cy - radius)
size = (radius * 2, radius * 2)
super(Ball, self).__init__(position, size)
self.radius = radius
self.color = color
#property
def cx(self):
return self.x + self.radius
#cx.setter
def cx(self, cx):
self.x = cx - self.radius
#property
def cy(self):
return self.y + self.radius
#cy.setter
def cy(self, cy):
self.y = cy - self.radius
def draw(self):
position = (self.cx, self.cy)
screen.draw.filled_circle(position, self.radius, self.color);
# screen.draw.rect(self, Color.RED) # draw hitbox
I instance a ball object and want make it go at x position 0 to 300. (the width of the window).
I use the default delta time (it's the time in millisecond elapsed between each frame 1/60) in update's function.
With a speed of 100 pixel * delta time, the ball will traverse the screen at 3 seconds (logical).
But it's not ... after some manipulation I created a second ball (I switch ball's name) and when I use variable instead all works fine.
cl = 0 # get time elpased at launch
ball = Ball(0, 10, 10, Color.RED)
x = ball.cx
ball2 = Ball(0, 30, 10, Color.BLUE)
speed = 100
def draw():
screen.clear()
ball.draw()
ball2.draw()
screen.draw.text(f"{cl:7.2f}", (0, 150))
def update(dt):
global cl, x
if ball.cx <= WIDTH:
cl += dt
# the ball make 3 seconds exactly
x += speed * dt
ball.cx = x
ball2.cx += speed * dt # don't make 3 seconds WHY ?
My question ... why ?
Create a custom property, it's much slower than classic variable definition ?
If you want to ensure time precise calculations, then you've to do all the calculations with floating point values. Only convert the coordinates to integral data types when drawing the objects on is integer pixel position.
The .cx property has an integral data type. Every time when the position is incremented, then the fraction part of the number which is added is lost. This causes an increasing inaccuracy. The object appears to be slower than expected.
Note, if the frame rate would be higher, then the object will even stand still, if the step per frame is smaller than 1.0. The integer part of a value < 1.0 is 0.
Note
ball2.cx += speed * dt
does the same as
ball2.cx += int(speed * dt)
The x variable is a floating point value. It is incremented with full accuracy. The exact value is cast to int when it is assigned to the .cx property, but the inaccuracy is always smaller than 1.0 and thus almost negligible.
x += speed * dt
ball.cx = x
does the same as
x += speed * dt
ball.cx = int(x)
Related
I created a class that handles particles in pygame. In the emit function which moves and displays the particles, I also have it set to gradually darken each particle. It darkens each particle but very minimally. This is because somehow the color of each particle, stored in a list with the particle's other data, is only changed by a small amount. For example, its default value is (255, 255, 255), after a single darken, it becomes around (245, 245, 245). It should continue to darken, but it just stops instead. When I change the other values of the particle, like its position, they don't reset, and each particle has its own list of values, so I have no idea why it is resetting. Here is part of the code(inside a class):
def emit(self, screen, delta_time):
if self.particles: # If there is anything in the particles list:
self.null_particles() # Delete all faded particles from list
for particle in self.particles: # Loop through all particles in list
# Shift particle spawn location
particle[0][0] += random.randint(-20, 20)
particle[0][1] += random.randint(-10, 10)
# If the direction isn't set, make it random
if particle[2] == [0, 0]:
particle[2] = [random.randint(-3, 3), random.randint(-3, 3)]
elif particle[2][0] == 0:
particle[2][0] = random.randint(-3, 3)
elif particle[2][1] == 0:
particle[2][1] = random.randint(-3, 3)
# Move particle based off of direction parameters
particle[0][0] += particle[2][0]
particle[0][1] += particle[2][1]
# Make particle smaller (fade out effect)
particle[1] -= 50*delta_time
# Make color gradually get darker
proxy_color = (particle[3][0]-10*delta_time, particle[3][1]-10*delta_time, particle[3][2]-10*delta_time)
if proxy_color[0] < 0 or proxy_color[1] < 0 or proxy_color[2] < 0:
pass
else:
particle[3] = proxy_color
# Display particle
pygame.draw.circle(screen, particle[3], particle[0], int(particle[1]))
def add_particles(self, pos_x, pos_y, direction_x=0, direction_y=0, color=(255, 255, 255)):
pos_x = pos_x
pos_y = pos_y
radius = 7
particle_circle = [[pos_x, pos_y], radius, [direction_x, direction_y], color]
self.particles.append(particle_circle)
def null_particles(self):
particle_copy = [particle for particle in self.particles if particle[1] > 0]
self.particles = particle_copy
def delete_particles(self):
self.particles = []
Next part, inside a player class, where the particle object is being used:
def particle_display(self, screen, delta_time):
if pygame.time.get_ticks() - self.time_jumping <= 200: # While player jumped within less than .2 seconds ago:
dust_particle.add_particles(self.rect.midbottom[0], self.ground_level, 0, -10) # Add more particles
dust_particle.add_particles(self.rect.midbottom[0], self.ground_level, 0, -10) # Add more particles
dust_particle.add_particles(self.rect.midbottom[0], self.ground_level, 0, -10) # Add more particles
dust_particle.emit(screen, delta_time) # Display said particles
else:
# Delay added to give particles time to disperse, so particles dont get deleted when buttons get deleted
if pygame.time.get_ticks() - self.jump_ended >= 200:
# Delete all particles after .2 sec of jump, or before jumping happened
dust_particle.delete_particles()
The above code, inside a player class, is called in an update method inside the class. This update method is called in a while 1 loop(infinite game loop).
Particle[3] (particles color data), is never manually (or purposefully), reset by me.
Sample output when particle[3] (the part of the list that stores the color) is printed:
Blockquote
(253.68000000000006, 253.68000000000006, 253.68000000000006)
(253.68000000000006, 253.68000000000006, 253.68000000000006)
(253.68000000000006, 253.68000000000006, 253.68000000000006)
(253.85000000000005, 253.85000000000005, 253.85000000000005)
(253.85000000000005, 253.85000000000005, 253.85000000000005)
(253.85000000000005, 253.85000000000005, 253.85000000000005)
(254.02000000000004, 254.02000000000004, 254.02000000000004)
(254.02000000000004, 254.02000000000004, 254.02000000000004)
(254.02000000000004, 254.02000000000004, 254.02000000000004)
(254.19000000000003, 254.19000000000003, 254.19000000000003)
(254.19000000000003, 254.19000000000003, 254.19000000000003)
(254.19000000000003, 254.19000000000003, 254.19000000000003)
(254.36, 254.36, 254.36)
(254.36, 254.36, 254.36)
(254.36, 254.36, 254.36)
(254.52, 254.52, 254.52)
(254.52, 254.52, 254.52)
(254.52, 254.52, 254.52)
(254.68, 254.68, 254.68)
(254.68, 254.68, 254.68)
(254.68, 254.68, 254.68)
(254.84, 254.84, 254.84)
(254.84, 254.84, 254.84)
(254.84, 254.84, 254.84)
(255, 255, 255)
(255, 255, 255)
(255, 255, 255)
All help is appreciated.
For this issue, as with many other things related to delta_time. If something doesn't work as expected, simply raise the value you are multiplying by delta_time, as when you multiply by delta_time, it makes the change over time consistent at different frame rates, but it is a consistently smaller change than before on the same frame rate with delta_time.
In this case, all I had to do was lower the value from -10 to -800.
So : proxy_color = (particle[3][0]-10*delta_time, particle[3][1]-10*delta_time, particle[3][2]-10*delta_time) -> proxy_color = (particle[3][0]-800*delta_time, particle[3][1]-800*delta_time, particle[3][2]-800*delta_time)
Goal: export this output as a GIF.
This is working code, using Processing and Python.
This actually runs forever, how do I stop it after a while so as I can save as GIF?
import random
radius = 0
def setup():
global displayWidth, displayHeight, radius
size(displayWidth, displayHeight)
background(0)
noFill()
stroke(255, 25)
radius = height / 2
def draw():
global radius
center_x = width / 2
center_y = height / 2
beginShape()
for i in range(360):
_noise = noise(i * 0.02, float(frameCount) / 50)
x = center_x + radius * cos(radians(i)) * _noise
y = center_y + radius * sin(radians(i)) * _noise
curveVertex(x, y)
if radius == 0:
stroke(225, 0, 0, 10)
if radius == 100:
stroke(0, 225, 0, 25)
if radius == 200:
stroke (0, 0, 225, 25)
endShape(CLOSE)
radius -= 1
Please let me know if there is anything else I should add to post.
You can't. GIFs are not supported in processing. However you can use saveFrame() to saves a numbered sequence of images. Just call saveFrame() at the end of draw. There are numerous tools that can create a GIF from a list of images.
This question already has answers here:
Changing ememy's color to show that it is aking damage?
(1 answer)
Is it possible to change sprite colours in Pygame?
(1 answer)
Changing colour of a surface without overwriting transparency
(1 answer)
Closed 2 years ago.
I'm making a 2D game in python using the module pygame. I would like to create a red vignette/bleed effect whenever the player takes damage in my game. This is seen in many games today, where the edges of the screen will flash red for a second and quickly disappear.
I have tried blitting an image I made in photoshop and scaling it accordingly during an animation cycle, but this was a really performance heavy operation, subsequently causing a lot of lag. I'm looking for alternatives to this method.
Code declaring a few variables:
bgX = 0
bgY = 0
damage = pygame.image.load("defensiveGameHUD.png").convert_alpha()
dimensions = [1920,1080]
Then I have this in the main loop of my game:
win.blit(background,(0,0))
if dimensions[0] != 4020:
dimensions[0] += 30
bgX -= 15
if dimensions[1] != 4600:
dimensions[1] += 40
bgY -= 20
if dimensions[1] != 4600:
screenDamage = pygame.transform.scale(damage, dimensions)
win.blit(screenDamage, (bgX, bgY))
else:
screenDamage = None
That is simply an animation that will scale the image in, however, the scaling is improper and this is very costly on performance.
def smmothstep(edge0, edge1, x):
t = min(1, max(0, (x - edge0) / (edge1 - edge0)))
return t * t * (3.0 - 2.0 * t)
def gen_damage_image(scale, source):
dest = source.copy()
img_size = dest.get_size()
for i in range(img_size[0]):
for j in range(img_size[1]):
fx = smmothstep(0, img_size[0]/2*scale, min(i, img_size[0]-i))
fy = smmothstep(0, img_size[1]/2*scale, min(j, img_size[1]-j))
color = dest.get_at((i, j))
fade_color = [int(255 - (1-fx*fy)*(255 - c)) for c in color]
dest.set_at((i, j), fade_color)
return dest
def tintDamage(surface, scale):
i = min(len(dmg_list)-1, max(0, int(scale*(len(dmg_list)-0.5))))
c.blit(dmg_list[i], (0, 0), special_flags = pygame.BLEND_MULT)
damage = pygame.image.load("defensiveGameHUD.png").convert_alpha()
max_dmg_img = 10
dmg_list = [gen_damage_image((i+1)/max_dmg_img, damage) for i in range(max_dmg_img)]
start_time = 0
tint = 0
damage_effect = False
To tint the screen in red can be achieved by pygame.Surface.fill(), by setting special_flags = BLEND_MULT.
The following function "tints" the entire surface in red, by a scale from 0 to 1. If scale is 0, the surface is not tinted and if scale is 1 the entire surface is tinted by the (red) color (255, 0, 0):
def tintDamage(surface, scale):
GB = min(255, max(0, round(255 * (1-scale))))
surface.fill((255, GB, GB), special_flags = pygame.BLEND_MULT)
The function has to be called right before pygame.display.flip() or pygame.display.update():
e.g.
tintDamage(win, 0.5)
pygame.display.flip()
Note, the special_flags = BLEND_MULT can also be set when using pygame.Surface.blit():
win.blit(damage, (bgX, bgY), special_flags = pygame.BLEND_MULT)
Or even both effects can be combined.
That's not exactly the effect I was looking for [...] I would like this effect to sort of scale itself inwards and then outwards, ...
What you want to do is tricky, because you would have to change each pixel of the damage surface dynamically. That would be much to slow.
But you can precalculate different damage surfaces, depending on an effect scale:
def smmothstep(edge0, edge1, x):
t = min(1, max(0, (x - edge0) / (edge1 - edge0)))
return t * t * (3.0 - 2.0 * t)
def gen_damage_image(scale, source):
dest = source.copy()
img_size = dest.get_size()
for i in range(img_size[0]):
for j in range(img_size[1]):
fx = smmothstep(0, img_size[0]/2*scale, min(i, img_size[0]-i))
fy = smmothstep(0, img_size[1]/2*scale, min(j, img_size[1]-j))
color = dest.get_at((i, j))
fade_color = [int(255 - (1-fx*fy)*(255 - c)) for c in color]
dest.set_at((i, j), fade_color)
return dest
damage = pygame.image.load("defensiveGameHUD.png").convert_alpha()
max_dmg_img = 10
dmg_list = [gen_damage_image((i+1)/max_dmg_img, damage) for i in range(max_dmg_img)]
tintDamage choose a damage image of the list, dependent on the scale:
def tintDamage(surface, scale):
i = min(len(dmg_list)-1, max(0, int(scale*(len(dmg_list)-0.5))))
c.blit(dmg_list[i], (0, 0), special_flags = pygame.BLEND_MULT)
The inwards / outwards effect can be achieved by a sine function. See the example, which starts the effect when x is pressed:
run = True
start_time = 0
tint = 0
damage_effect = False
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_x:
damage_effect = True
tint = 0
win.fill((196, 196, 196))
# [...]
if damage_effect:
scale = math.sin(tint)
tintDamage(win, scale)
tint += 0.1
damage_effect = scale >= 0
pygame.display.flip()
Since the computation of the images is very slow, I provide a solution, which generated a scale mask on a 20x20 image. The mask is scaled to the size of the damage image and blended with the damage image:
def gen_damage_image(scale, source):
scale_size = (20, 20)
scale_img = pygame.Surface(scale_size, flags = pygame.SRCALPHA)
for i in range(scale_size[0]):
for j in range(scale_size[1]):
fx = smmothstep(0, scale_size[0]/2*scale, min(i, scale_size[0]-i))
fy = smmothstep(0, scale_size[1]/2*scale, min(j, scale_size[1]-j))
fade_color = [int(max(0, 255 - (1-fx*fy)*255)) for c in range(4)]
scale_img.set_at((i, j), fade_color)
dest = source.copy()
scale_img = pygame.transform.smoothscale(scale_img, dest.get_size())
dest.blit(scale_img, (0, 0), special_flags = pygame.BLEND_ADD)
return dest
I am trying to make my circle bounce off of my rectangle using Zelle graphics.py. Once the circle bounces off of the rectangle I wanted it to keep moving randomly. Here is my code so far, and it's working!
Also I know that each circle graphics technically can use the points of the smallest possible square that would fit around the circle to do the collision but I'm having trouble with doing that.
from graphics import *
import random
def delay(d):
for i in range(d):
for i in range(50):
pass
#-------------------------------------------------
def main():
win=GraphWin("Moving Circle",500,400)
win.setBackground('white')
pt= Point(100,200)
cir=Circle(pt,30)
#changes the color of the circle for each game
r = random.randrange(256)
b = random.randrange(256)
g = random.randrange(256)
color = color_rgb(r, g, b)
cir.setFill(color)
cir.draw(win)
#rectangle
rec = Rectangle(Point(450,450), Point(275, 425))
rec.draw(win)
rec.setFill('black')
#-------------------------------------------------
pt5 = Point(250,30)
instruct1=Text(pt5, "click multiple times to start(rectangle can take multiple clicks to move)")
instruct1.setTextColor('black')
instruct1.draw(win)
#-------------------------------------------------
p=cir.getCenter()
p2=win.getMouse()
dx=1
dy=1
keepGoing=True
while keepGoing:
d = 100
delay(d)
cir.move(dx,dy)
p=cir.getCenter()
p2=win.checkMouse()
instruct1.setText("")
#rectanlge
isClicked= win.checkMouse()
if isClicked:
rp = isClicked
rc = rec.getCenter()
rdx = rp.getX() - rc.getX()
rdy = rp.getY() - rc.getY()
rec.move(rdx,rdy)
#circle
if((p.getX()-30)<=0.0) or ((p.getX()+30)>=500):
dx= -dx
if((p.getY()-30)<=0.0) or ((p.getY()+30)>=400):
dy=-dy
p3=win.checkMouse()
main()
I know that each circle graphics technically can use the points of the
smallest possible square that would fir around the circle to do the
collision
I'm playing with an alternate idea -- we could consider a circle around the rectangle instead of a square around the circle. The issue for me is that we not only need to detect collision, but come out with a sense of which way to move away from the other object. It's not just True and False but rather a (dx, dy) type of result.
Obviously, a circle around the rectangle is too crude, but suppose it were lots of smaller circles making up the rectangle and we measure circle center to center distance to detect a hit:
A hit on just a central (green) rectangle circle means reverse the vertical direction of the big circle. A hit on just the end (red) circle means reverse the horizontal direction of the big circle. And we can detect both kinds of hits and reverse the big circle completely.
Here's my rework of your code with the above in mind -- I also fixed your multiple clicking issue and made lots of style changes:
from random import randrange
from graphics import *
WIDTH, HEIGHT = 500, 400
RADIUS = 30
def delay(d):
for _ in range(d):
for _ in range(50):
pass
def distance(p1, p2):
return ((p2.getX() - p1.getX()) ** 2 + (p2.getY() - p1.getY()) ** 2) ** 0.5
def intersects(circle, rectangle):
dx, dy = 1, 1 # no change
center = circle.getCenter()
rectangle_radius = (rectangle.p2.getY() - rectangle.p1.getY()) / 2
rectangle_width = rectangle.p2.getX() - rectangle.p1.getX()
y = rectangle.getCenter().getY()
for x in range(int(rectangle_radius * 2), int(rectangle_width - rectangle_radius * 2) + 1, int(rectangle_radius)):
if distance(center, Point(rectangle.p1.getX() + x, y)) <= rectangle_radius + RADIUS:
dy = -dy # reverse vertical
break
if distance(center, Point(rectangle.p1.getX() + rectangle_radius, y)) <= rectangle_radius + RADIUS:
dx = -dx # reverse horizontal
elif distance(center, Point(rectangle.p2.getX() - rectangle_radius, y)) <= rectangle_radius + RADIUS:
dx = -dx # reverse horizontal
return (dx, dy)
def main():
win = GraphWin("Moving Circle", WIDTH, HEIGHT)
circle = Circle(Point(WIDTH / 5, HEIGHT / 2), RADIUS)
# change the color of the circle for each game
color = color_rgb(randrange(256), randrange(256), randrange(256))
circle.setFill(color)
circle.draw(win)
# rectangle
rectangle = Rectangle(Point(275, 425), Point(450, 450)) # off screen
rectangle.setFill('black')
rectangle.draw(win)
dx, dy = 1, 1
while True:
delay(100)
circle.move(dx, dy)
# rectangle
isClicked = win.checkMouse()
if isClicked:
point = isClicked
center = rectangle.getCenter()
rectangle.move(point.getX() - center.getX(), point.getY() - center.getY())
# circle
center = circle.getCenter()
if (center.getX() - RADIUS) <= 0.0 or (center.getX() + RADIUS) >= WIDTH:
dx = -dx
if (center.getY() - RADIUS) <= 0.0 or (center.getY() + RADIUS) >= HEIGHT:
dy = -dy
# collision bounce
x, y = intersects(circle, rectangle)
dx *= x
dy *= y
main()
Not perfect, but something to play around with, possibly plugging in a better intersects() implementation.
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.