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).
Related
I just started playing around with python a bit and tried to do some silly grafix stuff to see how it worked. Now I got stuck with one precise problem: I am trying to read the color value of a pixel from a pygame surface - but I can't. The below code - which I found in several (perhaps outdated) manuals and samples - throws an error I don't understand:
(Line 38 is the one containing the "screen.get_at()" part)
File "/home/mark/devel/python/./test.py", line 38, in
color = screen.get_at((x, y))
TypeError: 'list' object cannot be interpreted as an integer
What I guess from the stuff I found online: pygame.surface changed the type of the return value for get_at() a short while ago. Now it doesn't return four integers for R, G, B and alpha. but it returns a type "color". However, I was unable to find an explanation what this type "color" actually is or how it works, resp. how I get just the RGB values out of it.
A shortened sample of my code which throws the above error is as follows:
#!/usr/bin/python3
import pygame
from pygame.locals import *
import numpy as np
WIDTH = 1200
HEIGHT = 800
WHITE = (255, 255, 255)
pygame.init()
# Set width and height of the screen
screen = pygame.display.set_mode((WIDTH,HEIGHT))
pygame.display.set_caption("Ant")
pygame.draw.rect(screen, WHITE, (0,0,WIDTH,HEIGHT))
pos = ([0],[0])
# Loop until the user clicks the close button.
done = False
clock = pygame.time.Clock()
# Loop as long as done == False
while not done:
for event in pygame.event.get(): # User did something
if event.type == pygame.QUIT or event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: # If user wants to exit
done = True # Flag that we are done so we exit this loop
x = pos[0] # horizontal position
y = pos[1] # vertical position
color = screen.get_at((x, y))
print (color)
pygame.display.flip()
# This limits the while loop to a max of 60 times per second.
# Leave this out and we will use all CPU we can.
clock.tick(600)
# Be IDLE friendly
pygame.quit()
I'd really appreciate if someone could give me a hint how to get the RGB (integer) values from this "color" type get_at() returns.
thx,
Mark
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
Recently I code a pygame 'Pong', I am not finish it yet because the paddles cannot move yet.
However, I occurred a problem here. Because I want to get the score which is equal to the times that ball hits the edge of the window. For my program, when the ball hits the wall, score will not add and the ball will stop moving. I don't know the reason and I want to find answer here.
Thanks a lot!
Code:
import pygame, sys, time,math
from pygame.locals import *
# User-defined functions
def main():
# Initialize pygame
pygame.init()
# Set window size and title, and frame delay
surfaceSize = (500, 400) # window size
windowTitle = 'Pong' #window title
frameDelay = 0.005 # smaller is faster game
# Create the window
surface = pygame.display.set_mode(surfaceSize, 0, 0)
pygame.display.set_caption(windowTitle)
# create and initialize red dot and blue dot
gameOver = False
color1=pygame.Color('white')
center1 = [250, 200]
radius1=10
speed1=[1,-1]
location1=(50, 150)
location2=(450, 150)
size=(5, 100)
rect1=pygame.Rect(location1,size)
rect2=pygame.Rect(location2,size)
r1=pygame.draw.rect(surface, color1, rect1)
r2=pygame.draw.rect(surface, color1, rect2)
# Draw objects
pygame.draw.circle(surface, color1, center1, radius1, 0)
# Refresh the display
pygame.display.update()
# Loop forever
while True:
# Handle events
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
# Handle additional events
# Update and draw objects for the next frame
gameOver = update(surface,color1,center1,radius1,speed1,rect1,rect2)
# Refresh the display
pygame.display.update()
# Set the frame speed by pausing between frames
time.sleep(frameDelay)
def update(surface,color1,center1,radius1,speed1,rect1,rect2):
# Check if the game is over. If so, end the game and
# returnTrue. Otherwise, erase the window, move the dots and
# draw the dots return False.
# - surface is the pygame.Surface object for the window
eraseColor=pygame.Color('Black')
surface.fill(eraseColor)
moveDot(surface,center1,radius1,speed1)
pygame.draw.circle(surface,color1,center1,radius1,0)
r1=pygame.draw.rect(surface, color1, rect1)
r2=pygame.draw.rect(surface, color1, rect2)
if r1.collidepoint(center1) and speed1[0]<0:
speed1[0]=-speed1[0]
if r2.collidepoint(center1) and speed1[0]>0:
speed1[0]=-speed1[0]
def moveDot(surface,center,radius,speed):
#Moves the ball by changing the center of the ball by its speed
#If dots hits left edge, top edge, right edge or bottom edge
#of the window, the then the ball bounces
size=surface.get_size()
for coord in range(0,2):
center[coord]=center[coord]+speed[coord]
if center[coord]<radius:
speed[coord]=-speed[coord]
if center[coord]+radius>size[coord]:
speed[coord]=-speed[coord]
def Score(center1):
# Score is how many times that ball hits the wall.
Score=0
while center1[0]==10:
Score=Score+1
return Score
def drawScore(center1,surface):
FontSize=60
FontColor=pygame.Color('White')
score=str(Score(center1))
String='Score : '
font=pygame.font.SysFont(None, FontSize, True)
surface1=font.render(String+score, True, FontColor,0)
surface.blit(surface1,(0,0))
main()
There are a number of issues. First of all, you're never calling any of the code that deals with the score. You need something to call printscore, I suppose (either in update or in main).
The next issue is that your Score function contains an infinite loop. Your while should probably be an if, since you only ever need to count one point per frame:
def Score(center1):
# Score is how many times that ball hits the wall.
Score=0
while center1[0]==10:
Score=Score+1
return Score
Even if you fix that, it still won't really work properly. The Score function is only ever going to return 0 or 1, since you're resetting the Score local variable each time the function is called (note, it's also a very bad idea to have a local variable with the same name as the function it's in). You probably need to use a global variable (and a global statement), or you need to get rid of the separate function and put the score tracking logic in your main loop where it can modify a local variable. Or you could return a specific value indicating that the score should be increased, rather than the total score. There are lots of ways to do it.
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'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)