I've been going through some Python tutorials using Python 2.7 and Pygame and I decided to challenge myself. The tutorial showed how to make a ball move (right) across the screen, then pop back to the other (left) side of the screen at a specific speed. I wanted to make the ball bounce back and forth from left to right, so I wrote this:
bif = "bg.jpg"
mif = "ball1.png"
import pygame, sys
from pygame import *
from pygame.locals import *
pygame.init()
screen = pygame.display.set_mode((816,460),0,32)
background = pygame.image.load(bif).convert()
ball = pygame.image.load(mif).convert_alpha()
x = 0
clock = pygame.time.Clock()
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
screen.blit(background, (0,0))
screen.blit(ball, (x, 160))
speed = 500
milli = clock.tick() #A tick is 1 millisecond
seconds = milli/1000.000000
dm = seconds * speed
if x == 0:
a = dm
elif x == 770:
a = -dm
x += a
pygame.display.update()
"bg.jpg" is a jpeg image that is 816 x 460 pixels and "bif.png" is a png image of a ball with a 50 pixel radius. Instead of moving back and forth at 500 pixels per second, the ball moves at a random speed to the right, then bounces off of the right side of the screen at a random speed to the left, and repeats this a random number of times. Then the ball keeps going in one direction and doesn't come back. I can't figure out why it's doing this. It behaves differently every time I run it. If anybody can figure out why, I'd be really thankful.
tick(), without arguments returns time which passed since last call. In your use it depends on rendering speed which will always be different that is why you get different speed each time.
Replace from speed = 500 to the end with:
speed = 1
if x == 0 or x == 770:
speed = -speed
x += speed
pygame.display.update()
clock.tick(60)
Related
I'm trying to simulate elastic collision using the One-dimensional Newtonian equation (https://en.wikipedia.org/wiki/Elastic_collision) with pygame.
The thing is that even if I transcribed the solved equation to change the velocity of the 2 bodies, it is not working (or probably there is something wrong with the code).
Here it is the code:
import pygame
pygame.init()
clock = pygame.time.Clock()
screenx,screeny = 1000,800
screen = pygame.display.set_mode([screenx,screeny])
pygame.display.set_caption("Elastic collision")
# Rectangles
small= pygame.Rect(70,433,17,17)
big = pygame.Rect(220,400,50,50)
# Masses
m_small = 1
m_big = 1
# Velocity
small_vel = 0
big_vel = -1
count = 0
start = False
sumM = m_small+m_big
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
run = False
screen.fill((0,0,0))
big.x += big_vel
small.x+=small_vel
# If collision between small and big...
if big.x==small.x+17:
start=True
if start == True:
# ...change velocity of small and big,
# using the elastic collision equation at each collision
if small.x==0 or big.bottomleft<=small.bottomright:
small_vel = (m_small-m_big)/sumM*small_vel+2*m_big/sumM*big_vel
big_vel = (m_big-m_small)/sumM*big_vel+2*m_small/sumM*small_vel
count += 1
print("Small vel:", small_vel, " Big vel:", big_vel)
# Draw the rectangles
pygame.draw.rect(screen, (255,255,255), small)
pygame.draw.rect(screen, (255,255,255), big)
pygame.draw.line(screen, (255,0,0), (0,450), (1000,450),width=1)
pygame.display.flip()
clock.tick(60)
pygame.display.update()
Since the 2 masses are equal, after the collision the moving one should stop and the other one should start moving. This is not happening. I also tried to write, in the if statment where the velocity should change due to the collision, small_vel=-1 and big_vel=0 and it worked fine.
It seems to me that the problem is due to these two lines:
small_vel = (m_small-m_big)/sumM*small_vel+2*m_big/sumM*big_vel
big_vel = (m_big-m_small)/sumM*big_vel+2*m_small/sumM*small_vel
Here big_vel is updated according to the new value of small_vel which is not what we want. Saving the new value of small_vel in a temporary variable and restoring it after the assignment to big_vel seems to fix the problem:
tmp_small_vel = (m_small-m_big)/sumM*small_vel+2*m_big/sumM*big_vel
big_vel = (m_big-m_small)/sumM*big_vel+2*m_small/sumM*small_vel
small_vel = tmp_small_vel
To be honest I haven't looked at the math stuff and I'm not sure this is exactly what you want but the result looks at least like the video of the wikipedia page.
On a side note you should have a look at this post:
i get an pygame.error: video system not initialised error everytime i run the program to fix the bug in your code occuring when you close the window (the exception raised is not the same but the source of the problem and the solution to fix it are the same).
I am just getting started with Pygame, and I did a little test of just printing pixels in random spots. However, I noticed that the pixels don't seem to be single pixels at all, but 'fuzzy' blobs of a few pixels, as shown in the image. Here's the code I used to draw the pixels:
Is there any way to just display single pixels?
Edit: Here's the whole code I used:
import pygame.gfxdraw
import pygame
import random
width = 1000
height = 1000
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
running = True
while running:
x = random.randint(0,1000)
y = random.randint(0,1000)
pygame.gfxdraw.pixel(screen, x, y, (225,225,225))
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.display.flip()
clock.tick(240)
more fuzzy pixels
pygame.gfxdraw.pixel(surface, x, y, color) will draw a single pixel on the given surface.
Also you will need to add import pygame.gfxdraw.
EDIT: Full code:
import pygame.gfxdraw
import pygame
import random
width = 1680
height = 1050
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
running = True
x = [i for i in range(width - 10)]
x_full = [i for i in range(width)]
y = 100
y_full = [i for i in range(height // 2)]
while running:
for i in x:
for j in y_full:
pygame.gfxdraw.pixel(screen, i, j, pygame.Color("red"))
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.display.flip()
clock.tick(1)
Try to test it this way, I set the window size to fit my monitor resolution so it goes fullscreen. x_full and y should give you horizontal line. And if you subtract, for example, 10 you will get slightly shorter line, and vice-versa with y_full and some random x. Also using (width//2, height//2) will cover exactly quarter of the screen. I think it is accurate and that pygame.gfxdraw.pixel(screen, i, j, pygame.Color("red")) displays only single pixel as it should.
In your code you are using random to display pixels and it adds them 240 per second so you are very fast ending up with bunch of pixels at random positions resulting to have pixels close to each other looking as a "bigger one". I think this is what was happening here. Please someone correct me if I am wrong.
Also make small window e.g. (100, 100) and draw one pixel at (50, 50) this way it can be more easily seen. If you are on windows use magnifier to test it.
IMPORTANT:
While testing this with huge number of pixels do it OUTSIDE of the loop because it will consume much processor power to display them.
Hope this answers your question
I have an Agar.io like test game where the square player touches the "bit"s and grows. The problem is the bit generation. The bit generates every second and has its own surface. But, the program will not blit the bit (no pun intended) into the initial surface.
Here is the code:
import pygame
import random
pygame.init()
clock = pygame.time.Clock()
display = pygame.display
screen = display.set_mode([640,480])
rect_x = 295
rect_y = 215
display.set_caption("Agar")
d = False
bits = []
size = 50
steps = 0
class Bit:
cscreen = display.set_mode([640,480])
circ = pygame.draw.circle(cscreen, (65, 76, 150), (random.randrange(40, 600), random.randrange(40, 440)), 5)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
d = True
if d == True:
break
# Fill background
screen.fill((5, 58, 0))
# Create player object
player = pygame.draw.rect(screen, (250, 197, 255), [rect_x, rect_y, size, size])
# Moving
if pygame.key.get_pressed()[pygame.K_UP]:
rect_y -= 2
if pygame.key.get_pressed()[pygame.K_DOWN]:
rect_y += 2
if pygame.key.get_pressed()[pygame.K_RIGHT]:
rect_x += 2
if pygame.key.get_pressed()[pygame.K_LEFT]:
rect_x -= 2
# Bit generation
if steps == 60:
new_bit = Bit()
bits.append(new_bit.circ)
screen.blit(new_bit.cscreen, (0,0))
steps = 0
steps += 1
# Collision detection
collision = player.collidelist(bits)
if collision != -1:
bits[collision].cscreen.fill((0,0,0,0))
# Make player larger
size += 10
# FPS limit
clock.tick(60)
# Refresh
display.flip()
P.S. There is no error message.
Thanks in advance.
To have each Bit object have different locations and instance variables, you need to have an __init__ method in your bit class. By adding an __init__, and adding self. to the variables, the blitting works.
But a word of warning. When you make a separate cscreen for each object, and then blit that cscreen to your actual screen, you get rid of every other cscreen thats been blitted, making only 1 Bit viewable at a time. Even if you had cscreen outside of __init__, you would have an issue erasing older ones now, so I advise you take a different approach.
My recommendation is to find a small picture of a dot, and write these lines in the Bit's __init__ method.
self.image = pygame.image.load("reddot.png")
self.rect = self.image.get_rect()
Once you create your bit, add it to a pygame.sprite.Group(). These groups have very handy built in methods. One of them being .draw(). It will go through your Group of Bits and use each's self.image and self.rect to draw them in the correct place every time, without you having to worry about it. When a Bit is eaten, you can delete the Bit from the Group and it is now erased for you.
A random side note: I recommend you change your event loop to just break when it event.type == pygame.QUIT. That way, Python does not need to check the if d == True statement on every frame, while making the code slightly more readable. If it only breaks out of the for loop (for me it exited fine), you can use sys.exit() after importing sys
I am a beginner in Pygame. I have coded a function for moving two balls in different direction and I follow the instructions coding it but it seems to be not working. I can draw two balls in screen but they will not move. I fixed it for almost 1 hour but no idea why balls aren't moving.
So, Can someone helps me check my code and just give me some hints. I will really appreciate anyone who helps me!
My code shows below
import pygame,sys,time
from pygame.locals import *
# User define function
def ball_move(Surface,white,pos,rad,speed):
size=Surface.get_size()
for item in [0,1]:
pos[item]=pos[item]+speed[item]
if pos[item]<rad:
speed[item]=-speed[item]
if pos[item]+rad>size[item]:
speed[item]=-speed[item]
# Open a brand-new window
pygame.init()
Screen_size = (500,400)
Title = ('Pong')
Frame_Delay = 0.01
Surface= pygame.display.set_mode(Screen_size,0,0)
pygame.display.set_caption(Title)
# Set up white color for drawing balls
white=pygame.Color('white')
# Now, we start to draw two balls
pos1=(100,200)
pos2=(400,200)
rad=10
ball1=pygame.draw.circle(Surface,white,pos1,rad,0)
ball2=pygame.draw.circle(Surface,white,pos2,rad,0)
pygame.display.update()
# Now, define speed
speed1=(2,-2)
speed2=(-2,2)
# Now, we define a loop
while ball1:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
# Now, we move the ball
ball1move=ball_move(Surface,white,pos1,rad,speed1)
ball2move=ball_move(Surface,white,pos2,rad,speed2)
pygame.draw.circle(Surface,white,pos1,rad,0,0)
pygame.draw.circle(Surface,white,pos2,rad,0,0)
surface.fill(pygame.Color('black'))
Part of saulspatz answer is correct, part is incorrect. You don't have to use sprites if you dont want to. pygame.draw is not pretty but perfectly usable. The main problem does seem to be your understanding of what to do in your event loop. All this should go in it:
while running:
# Handdle your events
# update your state
# draw to your display
pygame.display.update()
Also I notice in your unreachable code after the loop you are filling after your draws. Remember whether you fill, blit, or draw the latest thing goes over the rest. So for your example:
import pygame ,sys, time
from pygame.locals import *
# User define function
def ball_move(surface, pos, rad, speed):
def _add(l_pos, l_speed, l_size):
l_pos += l_speed
if l_pos <= rad or l_pos >= l_size - rad:
l_speed = -l_speed
return l_pos, l_speed
size = surface.get_size()
pos_x, speed_x = _add(pos[0], speed[0], size[0])
pos_y, speed_y = _add(pos[1], speed[1], size[1])
return (pos_x, pos_y), (speed_x, speed_y)
pygame.init()
screen_size = (500, 400)
screen = pygame.display.set_mode(screen_size)
pygame.display.set_caption('Pong')
running = True
pos1 = (100, 200)
pos2 = (400, 200)
speed1 = (2, -2)
speed2 = (-2, 2)
rad = 10
while running:
# Handdle your events
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# update your state
pos1, speed1 = ball_move(screen, pos1, rad, speed1)
pos2, speed2 = ball_move(screen, pos2, rad, speed2)
# draw to your display
screen.fill(pygame.Color('black'))
pygame.draw.circle(screen, pygame.Color('white'), pos1, rad)
pygame.draw.circle(screen, pygame.Color('white'), pos2, rad)
pygame.display.update()
There are a lot of problems with your code. The most basic is that you haven't figured out how event-driven programming works. You need to put the code that moves the balls inside your loop.
Another problem is that I don't think you want to use the pygame.draw module. It's been a long time since I wrote any pygame scripts, but as I remember, this module is useful for drawing fixed objects, like the background. A quick look at the docs seems to confirm this.
For moving objects, I think you need to look at the pygame.sprite module. Even if you got this code to work, it wouldn't move the balls. It would just draw a new ball at another position. So you would have first two balls, then four, then eight, ... . Sprites actually move. Not only is the object drawn at the new position, but it's erased at the old position. Your code don't address erasure at all.
Hope this helps.
I have a pygame program that is meant to fill the pygame window with Grass.png:
import pygame, sys
from pygame.locals import *
pygame.init()
screen = pygame.display.set_mode([600, 500])
def DrawBackground(background, xpos, ypos):
screen.blit(background, [xpos, ypos])
background = pygame.image.load('Grass.png')
xpos = 0
ypos = 0
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
while ypos >= -500:
while xpos <= 600:
DrawBackground(background, xpos, ypos)
xpos += 100
ypos -= 100
pygame.display.flip()
Only problem is, it only fills the first 100 pixel row with the image. What is wrong with the code? Thanks.
You're much better off using for loops rather than while loops to do that.
for y in range(5):
for x in range(6):
DrawBackground(background, x*100, y*100)
Makes the code much more readable and easier to debug.
But in answer to your question, like frr171 said, the origin point (0, 0) is in the top left corner of the screen. As you go right, the x axis increases and as you go down the y axis increases.
The y-axis is positive as you go down the screen - so while your first row is at a y-position of 0, the next row will be at a y-position of 100. Basically, you should be adding to the y-coordinate, not subtracting.