drawing a point on the screen every 17ms in Python? - python

I managed to string together a script that receives commands from an iOS app setting velocity and a direction.
The thing is I do not have the actual device, so my app instead sends commands to a little python web socket server I built that uses tornado...
Essentially what I would ideally need is a way to:
Display a window
Every 17ms, clear the window, read a global variable with x and y and draw a point or circle at x and y.
Is there a convenient way to do this so I can visually see what's going on?
If I can get something to draw a circle in a window every X ms, I can handle the rest.
What needs to be added:
-create a window
-create a timer
on timer callback: clear screen and draw a circle in the window.

You should try using pygame for graphics work.
First download pygame
Here is a sample code
import pygame,sys
from pygame import *
WIDTH = 480
HEIGHT = 480
WHITE = (255,255,255) #RGB
BLACK = (0,0,0) #RGB
pygame.init()
screen = display.set_mode((WIDTH,HEIGHT),0,32)
display.set_caption("Name of Application")
screen.fill(WHITE)
timer = pygame.time.Clock()
pos_on_screen, radius = (50, 50), 20
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
timer.tick(60) #60 times per second you can do the math for 17 ms
draw.circle(screen, BLACK, pos_on_screen, radius)
display.update()
HOPE THAT HELPS. Remember you need to download pygame first.
You should also read up on pygame. It is really helpful.

You could use your terminal as a "window" and draw a "circle" in it. As a very simple (and unreliable) "timer", time.sleep() function could be used:
#!/usr/bin/env python
"""Print red circle walking randomly in the terminal."""
import random
import time
from blessings import Terminal # $ pip install blessings colorama
import colorama; colorama.init() # for Windows support (not tested)
directions = [(-1, -1), (-1, 0), (-1, 1),
( 0, -1), ( 0, 1),
( 1, -1), ( 1, 0), ( 1, 1)]
t = Terminal()
with t.fullscreen(), t.hidden_cursor():
cur_y, cur_x = t.height // 2, t.width // 2 # center of the screen
nsteps = min(cur_y, cur_x)**2 # average distance for random walker: sqrt(N)
for _ in range(nsteps):
y, x = random.choice(directions)
cur_y += y; cur_x += x # update current coordinates
print(t.move(cur_y, cur_x) +
t.bold_red(u'\N{BLACK CIRCLE}')) # draw circle
time.sleep(6 * 0.017) # it may sleep both less and more time
print(t.clear) # clear screen
To try it, save the code into random-walker.py and run it:
$ python random-walker.py
I don't know whether it works on Windows.

1. Create a window
2. Set a timer instance and repetition tasks
3. Draw a circle

Related

Tearing / dropped frames in pygame

The following code draws a small white disc orbiting the center of the screen. There is noticeable tearing on my machine (macOS Monterey) and 6 or 7 frames are dropped on average on each revolution. Is there any way to avoid that? I've tried adding flags such as vsync, fullscreen, scaled etc, nothing works. The opengl flag doesn't seem to work on macos.
import pygame
import sys
import math
pygame.init()
screen = pygame.display.set_mode((1920,1080))
clock = pygame.time.Clock()
while True:
for e in pygame.event.get():
if e.type == pygame.QUIT:
sys.exit()
t = pygame.time.get_ticks()
screen.fill('Black')
pygame.draw.circle(screen, 'White', (
960 + 300 * math.cos(t / 800),
540 + 300 * math.sin(t / 800)),
20)
pygame.display.update()
clock.tick(60)
To be clear: IMHO the frames are not dropping because of heavy or background CPU usage. A modified version of this code runs approximately 1000 such circles all over the screen pretty smoothly, with only the regular hiccup here and there (same as above). I only start to get real slowdowns when I go over 5000 circles.
Firstly - check that there is not some CPU-heavy task running on your machine. For the moment I would just write your program, and then worry about if/when it's dropping frames later. If it's still an issue at completion, then optimise it.
One way to speed this up is to pass the rectangle for changed screen area to the pygame.display.update( dirty_rectangle ) function. That way only the part of the screen that has been "dirtied" by changes will be updated. This means that typically a smaller part of the screen will be re-drawn.
I also modified the code to draw the circle to an off-screen surface, and just blit() that image to the screen, rather than re-calculate the circle each loop. That makes it easy to work out the corners of the changed area too.
For your example, it's possible to calculate this rectangle based on the position of the circle, and where it has been previously. So before repositioning the circle to the new co-ordinate, we make a copy of circle_rect into previous_rect. The new position is calculated, the drawing position updated. The maximum extent of the update is then the minimum top-left corner of both rectangles, down to the maximum of the bottom-right corners. The min & max is used to populate the update rectangle.
import pygame
import sys
import math
pygame.init()
screen = pygame.display.set_mode((1920,1080))
clock = pygame.time.Clock()
# Create a sprite for the circle
CIRCLE_RAD=20
circle = pygame.Surface( (CIRCLE_RAD*2, CIRCLE_RAD*2), pygame.SRCALPHA, 32 )
circle.fill( (0,0,0) )
pygame.draw.circle( circle, (255,255,255), (CIRCLE_RAD,CIRCLE_RAD), CIRCLE_RAD )
circle_rect = circle.get_rect()
update_rect = circle_rect.copy()
while True:
for e in pygame.event.get():
if e.type == pygame.QUIT:
sys.exit()
previous_rect = circle_rect.copy()
t = pygame.time.get_ticks()
t800 = t/800
circle_rect.center = ( 960 + 300 * math.cos(t800), 540 + 300 * math.sin(t800) )
# work out the size of the update
min_x = min( previous_rect.x, circle_rect.x )
min_y = min( previous_rect.y, circle_rect.y )
max_x = max( previous_rect.x + previous_rect.width, circle_rect.x + circle_rect.width )
max_y = max( previous_rect.y + previous_rect.height, circle_rect.y + circle_rect.height )
update_rect.update( min( previous_rect.x, circle_rect.x ),
min( previous_rect.y, circle_rect.y ),
max_x-min_x,
max_y-min_y )
screen.fill('Black')
screen.blit( circle, circle_rect )
pygame.display.update( update_rect )
ms_since_previous = clock.tick(60)
if ( ms_since_previous > 17 ): # 60FPS is about 17ms between frames
print( "Slow update: %d milliseconds" % ( ms_since_previous ) )

Pygame display only updates when I quit

Fiddling around with pygame and I'm not sure what's happening.
code is just supposed to make a red box and bounce it around a gray screen. It works, but only when I quit the display.
I've checked out questions that are similar but none seem to have an answer that applies to me (might be wrong about that). Does anyone know how this code could be improved?
import pygame
from pygame.locals import *
from rect import *
##from pygame.font import *
RED = (255, 0, 0)
GRAY = (150, 150, 150)
width = 500
height = 200
pygame.init()
screen = pygame.display.set_mode((width, height))
rect = Rect(100, 50, 50, 50)
v = [2, 2]
##moving = False
running = True
while running:
for event in pygame.event.get():
if event.type == QUIT:
running = False
rect.move_ip(v)
if rect.left < 0 or rect.right > width:
v[0] *= -1
if rect.top < 0 or rect.bottom > height:
v[1] *= -1
screen.fill(GRAY)
pygame.draw.rect(screen, RED, rect)
pygame.display.flip()
pygame.quit()
It seems that the problem is the program running too fast. Since so little work is being done in each loop, the rectangle ends up moving around too quickly to clearly see what is happening.
You can restrict the speed of the program using a pygame.time.Clock object. Before the start of your loop, probably right after your screen definition, you can construct a clock.
clock = pygame.time.Clock()
Then in your main loop, as the very first step each iteration (right after while running:) you can put clock.tick(60) to restrict the loop to running 60 times per second (60fps). This makes the program run smoothly, and you get the nice bouncing rectangle!
The tick method works well, but on my system it seems to have small hitches every so often. If you also experience this or if you want to be more accurate, you can use clock.tick_busy_loop(60) instead.
Both of these tick methods work the same way: By measuring the amount of time that passed since the last call to clock.tick() and delaying a certain additional amount based on that so that the target fps will be met.
More info on Clock at Pygame docs.
I also needed to import pygame.rect instead of just import rect, but if you're not getting an import error you should be fine.
I've figured it out. Stupidly I had another file in my test directory named "rect.py", I changed the file name and my code to from pygame.rect import * and it's working fine now. Thank you Baked Potato for the help and for making me wonder where x=50, y=60, w=200, h=80 left=50, top=60, right=250, bottom=140 center=(150, 100) was coming from!

How to rotate a surface in pygame, without changing its shape

I'm writing a class in pygame to create a sprite object, and I'd like to be able to rotate it. It works fine with an image, and rotates without issue. But when rotating a surface with a plain colour, the box appears to grow and shrink. I know that this is a result of the surface changing size to fit the vertices of the rectangle inside, but how do I stop it? I'd like to see a visual rotation.
I've created some sample code to show the problem that I'm facing, running it causes the box to simply change in size.
import sys, pygame
from pygame.locals import *
pygame.init()
SCREEN = pygame.display.set_mode((200, 200))
CLOCK = pygame.time.Clock()
surface = pygame.Surface((50 , 50))
surface.fill((0, 0, 0))
rotated_surface = surface
rect = surface.get_rect()
angle = 0
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
SCREEN.fill((255, 255, 255))
angle += 5
rotated_surface = pygame.transform.rotate(surface, angle)
rect = rotated_surface.get_rect(center = (100, 100))
SCREEN.blit(rotated_surface, (rect.x, rect.y))
pygame.display.update()
CLOCK.tick(30)
How do I fix this issue, to make the surface rotate how I want?
Any help would be appreciated!
You have to create the Surface objects you create to stamp on the display surface in a way they use transparency information (That is - they have to have an alpha channel).
To do that, is just a question of passing the appropriate flag when creating your surface objects - simply replace this:
surface = pygame.Surface((50 , 50))
with:
surface = pygame.Surface((50 , 50), pygame.SRCALPHA)
and it should work.

Additive blending in pygame doesn't work with alpha

I recently discovered the different blending modes you can apply to blitted surfaces in pygame and I wanted to see how flexible the system was. Unless I'm doing something wrong, it's apparently pretty limited (just like the rest of pygame OOOOOOOH shots fired). I wrote a simple program that draws a bunch of gradient circles using alpha and blits them all around the screen. This is the code:
import pygame
import pygame.gfxdraw
pygame.init()
import random
SCREEN = pygame.display.set_mode((800, 600))
SCREEN.fill((0, 0, 0))
def draw_square(surface, colour, x, y):
"""
Yeah it's called draw square but it actually draws a circle thing I was just too lazy
to change the name so you gotta deal with it.
"""
square = pygame.Surface((100, 100))
square.fill((0, 0, 0))
colour += (int(15/255*100), )
for i in range(25):
pygame.gfxdraw.filled_circle(square, 50, 50, i*2, colour)
# Comment the previous two lines out and uncomment the next line to see different results.
# pygame.draw.circle(square, colour[:3], (50, 50), 50)
surface.blit(square, (x - 50, y - 50), special_flags=pygame.BLEND_RGB_ADD)
running = True
while running:
for evt in pygame.event.get():
if evt.type == pygame.QUIT:
running = False
draw_square(SCREEN, (25, 255, 25), random.randint(0, 800), random.randint(0, 600))
pygame.display.update()
pygame.quit()
It seems to work when drawing a normal circle, but when drawing the circles with pygame.gfxdraw.filled_circle additive blending doesn't work. Any ideas?
EDIT: I'm using Python 3, so 15/255 evaluates properly to a float.
The issue is still with this line:
colour += (int(15/255*100), )
It should go to white initially, but the alpha is so low it will take a long time (well, it should in theory...).
Doing:
colour += (int(125/255*100), )
Makes the effect more obvious.
Result:

pygame - trying to move a rectangle

I am codding a drum game (based on dtx-Mania).
I got an issue on how to draw a rectangle and move it. I can make a static one, but not a moving one. I currently used a line but it seams pygame draw it as a rectangle so far if it as an impact i will change back to rectangle.
My goal is to draw the rectangle and move it at a slow enough pace so that it takes about 1 second to get to a line.
I know I still have a lot to learn, this is what I got to test so far.
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
#small exemple of a moving rectangle
import pygame, sys
pygame.init()
fpsClock = pygame.time.Clock()
windowsSurfaceObj = pygame.display.set_mode((640, 480))
pygame.display.set_caption('moving rectangle test')
white = pygame.Color(255, 255, 255)
black = pygame.Color(0, 0, 0)
step = pygame.draw.line(windowsSurfaceObj, white, (233, 0), (269, 0), 6)
step
while True:
windowsSurfaceObj.fill(black)
#the coordonate are moved but the rectangle is now drew
step.move(0, -1)
#this is the target line (where the moving object must go to)
pygame.draw.line(windowsSurfaceObj, white, (90, 420), (390, 420), 6)
pygame.display.update()
fpsClock.tick(30)
Thank you for your help.
It is always good to read the docs. From the pygame docs:
line(Surface, color, start_pos, end_pos, width=1) -> Rect
Draw a straight line segment on a Surface. There are no endcaps, the
ends are squared off for thick lines.
In the description text of the whole module:
The functions return a rectangle representing the bounding area of changed pixels.
So you draw your first line, and move a rectangle representing a bounding are of changed pixels. Since you do not redraw your line, your first line disappears.
To solve this, you need to draw the line in the while loop after moving.
step.move(0, -1)
pygame.draw.rect(windowsSurfaceObj, white,step, 6)

Categories