wxPython FloatCanvas event Binding - performance

I am using Python 3.6 with wxPython 4.1.0 gtk3 (phoenix) wxWidgets 3.1.4. There is a delay in the Bind function on every 4th line that i try to plot and after about 12 lines the program crashs. Normally my code uses a grid to input the points for the lines but the attached code generates the data once the Draw button is clicked with exactly the same effect. The delay is about 2.5 seconds on the 4th line normally it takes about .1 secs to plot a line. I have tried two functions for the Bind call one uses the event the other calls the object.
If anyone knows a work around can you please let me know.
import time
import string
import wx
from wx.lib.floatcanvas import NavCanvas, FloatCanvas
import wx.lib.colourdb
class InputForm(wx.Frame):
'''set up the form and draw axis'''
def __init__(self):
super(InputForm, self).__init__(None, wx.ID_ANY, title='Plot Lines', size=(1300, 830))
# set dictionary of points; key node letter, value tuple of point,
self.pts = {}
# create the form level sizer
Main_Sizer = wx.BoxSizer(wx.HORIZONTAL)
# add the sizer for the left side widgets
sizerL = wx.BoxSizer(wx.VERTICAL)
# add the grid and then set it ot he left panel
btnsizer = wx.BoxSizer(wx.HORIZONTAL)
drw = wx.Button(self, -1, "Draw\nLines")
btnsizer.Add(drw, 0, wx.ALL|wx.ALIGN_CENTER, 5)
# bind the button events to handlers
self.Bind(wx.EVT_BUTTON, self.OnDraw, drw)
sizerL.Add((10, 20))
sizerL.Add(btnsizer, 1, wx.ALIGN_CENTER)
# add the draw panel
rght = NavCanvas.NavCanvas(self,
ProjectionFun=None,
Debug=0,
BackgroundColor="LIGHT GREY",
)
self.Canvas = rght.Canvas
self.InitCanvas()
Main_Sizer.Add(sizerL, 0, wx.EXPAND)
Main_Sizer.Add((10, 10))
Main_Sizer.Add(rght, 1, wx.EXPAND)
self.SetSizer(Main_Sizer)
def InitCanvas(self):
# add the x & y axis
self.Canvas.AddLine([(0, 0), (0, 5)], LineWidth=2, LineColor='Yellow')
self.Canvas.AddLine([(0, 0), (5, 0)], LineWidth=2, LineColor='Green')
origin = self.Canvas.AddScaledTextBox('origin', (0, 0),
Color='blue',
Size=.5,
PadSize=0,
Width=None,
LineColor=None,
Family=wx.MODERN,
Position='tr',
Alignment='bottom',
InForeground=True)
# first Bind of node to EvtLeftDown
origin.Bind(FloatCanvas.EVT_FC_LEFT_DOWN,
lambda evt, selctEnd='Origin':
self.EvtLeftDown(evt, 'Origin'))
wx.CallAfter(self.Canvas.ZoomToBB)
def OnDraw(self, evt):
pts1 = (0, 0)
x = [i for i in range(5, 30, 2)]
y = x[::-1]
pts2 = [(x[i], y[i]) for i in range(0, len(x))]
alph = string.ascii_uppercase
LnLbls = [alph[i] for i in range(0, len(x))]
New_EndPt = True
n = 0
for pt in pts2:
points = []
points.append(pts1)
points.append(pt)
LnLbl = LnLbls[n]
New_EndPt = True
n += 1
self.DrawLine(points, LnLbl, New_EndPt)
def DrawLine(self, points, LnLbl, New_EndPt):
'''Draws the line object as specified in the VarifyData() function'''
# label the end point of the line in lower case
if New_EndPt is True:
new_end = self.Canvas.AddScaledTextBox(LnLbl.lower(), tuple(points[1]),
Color='black',
Size=.5,
PadSize=.2,
Width=None,
LineColor=None,
Family=wx.MODERN,
Position='cc',
Alignment='bottom',
InForeground=True)
new_end.Bind(FloatCanvas.EVT_FC_LEFT_DOWN,
lambda evt, selctEnd=LnLbl.lower():
self.EvtLeftDown(evt, selctEnd))
# define the new line
self.Canvas.AddLine(points, LineWidth=2, LineColor='red')
# add the new line to the list of lines
self.Canvas.AddPoint(tuple(points[1]), 'black', 8)
# locate the center of the new line for the label location
lncntr = ((int(points[0][0])+int(points[1][0]))//2,
(int(points[0][1])+int(points[1][1]))//2)
# place the new line lable
new_line = self.Canvas.AddScaledTextBox(LnLbl, lncntr,
Color='red',
Size=.5,
PadSize=None,
Width=None,
LineColor=None,
Family=wx.MODERN,
Position='tc',
Alignment='bottom',
InForeground=True)
new_line.Name = LnLbl
tic = time.perf_counter()
new_line.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.ObjLeftDown)
toc = time.perf_counter()
print(f'time to execute BIND function for DrawLine line 136 = {toc-tic:0.2f}')
wx.CallAfter(self.Canvas.ZoomToBB)
def ObjLeftDown(self, object):
lbl = object.Name
if lbl == 'Origin':
self.Node(lbl)
elif 65 <= ord(lbl) <= 90:
print('you have selected line ', lbl)
elif 97 <= ord(lbl) <= 122:
print('you have selected node ', lbl)
def EvtLeftDown(self, evt, lbl):
if lbl == 'Origin':
print('you have selected the origin')
elif 97 <= ord(lbl) <= 122:
print('you have selected node ', lbl)
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = InputForm()
frame.Center()
frame.Show()
app.MainLoop()

With minor adjustments to your code, I am unable to replicate the delays that you refer to.
No matter how many times I re-draw the lines, the timings, always fall within roughly the same time frame.
The bindings are to different objects, and it's just binding an event to an object, so I doubt that that is the issue.
import time
import string
import wx
from wx.lib.floatcanvas import NavCanvas, FloatCanvas
import wx.lib.colourdb
class InputForm(wx.Frame):
'''set up the form and draw axis'''
def __init__(self):
super(InputForm, self).__init__(None, wx.ID_ANY, title='Plot Lines', size=(1300, 830))
# set dictionary of points; key node letter, value tuple of point,
self.pts = {}
self.draw_repetitions = 0
# create the form level sizer
Main_Sizer = wx.BoxSizer(wx.HORIZONTAL)
# add the sizer for the left side widgets
sizerL = wx.BoxSizer(wx.VERTICAL)
# add the grid and then set it ot he left panel
btnsizer = wx.BoxSizer(wx.HORIZONTAL)
drw = wx.Button(self, -1, "Draw\nLines")
btnsizer.Add(drw, 0, wx.ALL|wx.ALIGN_CENTER, 5)
# bind the button events to handlers
self.Bind(wx.EVT_BUTTON, self.OnDraw, drw)
sizerL.Add((10, 20))
sizerL.Add(btnsizer, 1, wx.ALIGN_CENTER)
# add the draw panel
self.rght = NavCanvas.NavCanvas(self,
ProjectionFun=None,
Debug=0,
BackgroundColor="LIGHT GREY",
)
#self.Canvas = self.rght.Canvas
self.InitCanvas()
Main_Sizer.Add(sizerL, 0, wx.EXPAND)
Main_Sizer.Add((10, 10))
Main_Sizer.Add(self.rght, 1, wx.EXPAND)
self.SetSizer(Main_Sizer)
def InitCanvas(self):
# add the x & y axis
self.Canvas = self.rght.Canvas
self.Canvas.ClearAll()
self.Canvas.AddLine([(0, 0), (0, 5)], LineWidth=2, LineColor='Yellow')
self.Canvas.AddLine([(0, 0), (5, 0)], LineWidth=2, LineColor='Green')
origin = self.Canvas.AddScaledTextBox('origin', (0, 0),
Color='blue',
Size=.5,
PadSize=0,
Width=None,
LineColor=None,
Family=wx.MODERN,
Position='tr',
Alignment='bottom',
InForeground=True)
# first Bind of node to EvtLeftDown
origin.Bind(FloatCanvas.EVT_FC_LEFT_DOWN,
lambda evt, selctEnd='Origin':
self.EvtLeftDown(evt, 'Origin'))
wx.CallAfter(self.Canvas.ZoomToBB)
def OnDraw(self, evt):
self.InitCanvas()
pts1 = (0, 0)
x = [i for i in range(5, 30, 2)]
y = x[::-1]
pts2 = [(x[i], y[i]) for i in range(0, len(x))]
alph = string.ascii_uppercase
LnLbls = [alph[i] for i in range(0, len(x))]
New_EndPt = True
n = 0
for pt in pts2:
points = []
points.append(pts1)
points.append(pt)
LnLbl = LnLbls[n]
New_EndPt = True
n += 1
self.DrawLine(points, LnLbl, New_EndPt)
def DrawLine(self, points, LnLbl, New_EndPt):
'''Draws the line object as specified in the VarifyData() function'''
self.draw_repetitions += 1
# label the end point of the line in lower case
if New_EndPt is True:
new_end = self.Canvas.AddScaledTextBox(LnLbl.lower(), tuple(points[1]),
Color='black',
Size=.5,
PadSize=.2,
Width=None,
LineColor=None,
Family=wx.MODERN,
Position='cc',
Alignment='bottom',
InForeground=True)
new_end.Bind(FloatCanvas.EVT_FC_LEFT_DOWN,
lambda evt, selctEnd=LnLbl.lower():
self.EvtLeftDown(evt, selctEnd))
# define the new line
self.Canvas.AddLine(points, LineWidth=2, LineColor='red')
# add the new line to the list of lines
self.Canvas.AddPoint(tuple(points[1]), 'black', 8)
# locate the center of the new line for the label location
lncntr = ((int(points[0][0])+int(points[1][0]))//2,
(int(points[0][1])+int(points[1][1]))//2)
# place the new line lable
new_line = self.Canvas.AddScaledTextBox(LnLbl, lncntr,
Color='red',
Size=.5,
PadSize=None,
Width=None,
LineColor=None,
Family=wx.MODERN,
Position='tc',
Alignment='bottom',
InForeground=True)
new_line.Name = LnLbl
tic = time.perf_counter()
new_line.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.ObjLeftDown)
toc = time.perf_counter()
print(f'time to execute BIND function for DrawLine line ',LnLbl, toc-tic)
print(f'Draw repetitions ',self.draw_repetitions)
# wx.CallAfter(self.Canvas.ZoomToBB)
self.Canvas.ZoomToBB()
def ObjLeftDown(self, object):
lbl = object.Name
if lbl == 'Origin':
self.Node(lbl)
print(dir(self.Node))
elif 65 <= ord(lbl) <= 90:
print('you have selected line ', lbl)
elif 97 <= ord(lbl) <= 122:
print('you have selected node ', lbl)
def EvtLeftDown(self, evt, lbl):
if lbl == 'Origin':
print('you have selected the origin')
elif 97 <= ord(lbl) <= 122:
print('you have selected node ', lbl)
# try:
# evt.Skip()
# except:
# pass
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = InputForm()
frame.Center()
frame.Show()
app.MainLoop()

TL;DR: As a workaround don't install wxpython via pip inside a venv, install the ubuntu package python3-wxgtk4.0.
I was facing the same issue you ran into - at least I think it is the same issue - and was, in a way, able to solve it. Running the code provided in the answer by Rolf of Saxony, the program would freeze for minutes when trying to draw the 12th or so line. After some minutes however, I at least got an exception out of it:
Traceback (most recent call last):
File "/bug_test.py", line 92, in OnDraw
self.DrawLine(points, LnLbl, New_EndPt)
File "/bug_test.py", line 138, in DrawLine
new_line.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.ObjLeftDown)
File "/env/lib/python3.8/site-packages/wx/lib/floatcanvas/FCObjects.py", line 236, in Bind
self.HitColor = next(self._Canvas.HitColorGenerator)
StopIteration
Which to me looks like some problem with the generation of the hit color. I guess the iterator generating it is for some reason not able to come up with an adequate hit color and thus runs for almost ever.
Although interesting, this did not help in solving the problem. What helped was leaving my virtual environment. Initially I was running everything inside an venv and installed wxpython via pip. When I left the venv and installed wxpython via the ubuntu package python3-wxgtk4.0, everything worked as expected. So this is the workaround I found. Nevertheless the problem inside the venv persists. I suspect it might have something to do with the installation via pip which builds wxpython from source.

Related

Pygame sprite hitboxes don't follow as screen scrolls/zooms

First, the CameraGroup class is attributed to the wonderful youtube channel Clear Code.
Second, the hitboxes will not zoom (mousewheel) or scroll (WASD keys) with the planet icons. Try clicking the planets before zooming or scrolling and you will see the hitbox in action.
Third, as the screen is zoomed out the planets get smaller and the camera window shrinks towards the middle of the screen. Ideally, the camera window should stay the full size of the parent window to allow displaying more planets in the zoomed out state.
Finally, I am eager to learn, so please be brutal. Just drop the two planet icons in the folder with the .py file.
import os
import pygame
from pygame import *
from sys import exit
class CameraGroup(pygame.sprite.Group):
def __init__(self):
super().__init__()
self.display_surf_foreground = pygame.display.get_surface()
# camera offset
self.offset = pygame.math.Vector2()
self.half_w = self.display_surf_foreground.get_size()[0] // 2
self.half_h = self.display_surf_foreground.get_size()[1] // 2
self.scroll_offset = (0, 0)
# box setup
self.camera_borders = {'left': 200, 'right': 200, 'top': 100, 'bottom': 100}
l = self.camera_borders['left']
t = self.camera_borders['top']
w = self.display_surf_foreground.get_size()[0] - (self.camera_borders['left'] + self.camera_borders['right'])
h = self.display_surf_foreground.get_size()[1] - (self.camera_borders['top'] + self.camera_borders['bottom'])
self.camera_rect = pygame.Rect(l, t, w, h)
# camera speed
self.keyboard_speed = 10
# zoom
self.zoom_scale = 1
self.foreground_surf_size = (400, 400)
self.foreground_surf = pygame.Surface(self.foreground_surf_size, pygame.SRCALPHA)
self.foreground_rect = self.foreground_surf.get_rect(center=(self.half_w, self.half_h))
self.foreground_surf_size_vector = pygame.math.Vector2(self.foreground_surf_size)
self.foreground_offset = pygame.math.Vector2()
self.foreground_offset.x = self.foreground_surf_size[0] // 2 - self.half_w
self.foreground_offset.y = self.foreground_surf_size[1] // 2 - self.half_h
# planets and labels
self.planet_surf = pygame.Surface
self.planet_rect = pygame.Rect
def keyboard_control(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_a]:
self.camera_rect.x -= self.keyboard_speed
if keys[pygame.K_d]:
self.camera_rect.x += self.keyboard_speed
if keys[pygame.K_w]:
self.camera_rect.y -= self.keyboard_speed
if keys[pygame.K_s]:
self.camera_rect.y += self.keyboard_speed
self.offset.x = self.camera_rect.left - self.camera_borders['left']
self.offset.y = self.camera_rect.top - self.camera_borders['top']
def zoom_keyboard_control(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_q]:
self.zoom_scale += 0.1
if self.zoom_scale > 2:
self.zoom_scale = 2
if keys[pygame.K_e]:
self.zoom_scale -= 0.1
if self.zoom_scale < .5:
self.zoom_scale = .5
def custom_draw(self, planets):
self.keyboard_control()
self.zoom_keyboard_control()
self.foreground_surf.fill((0, 0, 0, 255))
# active elements
for planet in planets:
self.planet_surf = pygame.image.load(planet.icon).convert_alpha()
offset_coord = planet.coord - self.offset + self.foreground_offset
self.planet_rect = self.planet_surf.get_rect(center=offset_coord)
self.foreground_surf.blit(self.planet_surf, self.planet_rect)
scaled_surf = pygame.transform.scale(self.foreground_surf, self.foreground_surf_size_vector * self.zoom_scale)
scaled_rect = scaled_surf.get_rect(center=(self.half_w, self.half_h))
self.display_surf_foreground.blit(scaled_surf, scaled_rect)
class Game:
def __init__(self):
self.game_over = False
self.game_year = 0
self.game_state = 'splash'
#staticmethod
def activate_planet(screen, planets):
active_planet_coord = None
for planet in planets:
if planet.rect.collidepoint(pygame.mouse.get_pos()):
active_planet_coord = planet.rect.center
return active_planet_coord
return active_planet_coord
#staticmethod
def heartbeat(screen, active_planet_coord):
# global heartbeat_mod
global heartbeat
ticks = pygame.time.get_ticks()
heartbeat_thump = round(ticks / 1000) % 2
if heartbeat_thump == 0:
heartbeat_mod = .1
else:
heartbeat_mod = -.1
heartbeat += heartbeat_mod
if heartbeat < 1:
heartbeat = 1
elif heartbeat > 6:
heartbeat = 6
heartbeat_color = (0, 255, 0)
pygame.draw.circle(screen, heartbeat_color, active_planet_coord, 25 + round(heartbeat), round(heartbeat) + 2)
class Planet(pygame.sprite.Sprite):
def __init__(self, icon, group):
super().__init__(group)
self.icon = icon
self.coord = (0, 0)
self.group = group
self.image = pygame.image.load(self.icon).convert_alpha()
self.rect = self.image.get_rect(center=self.coord)
self.planet_icons = []
self.planet_names = []
#staticmethod
def update_coords(planets, star_coords):
i = 0
for planet in planets:
planet.coord = tuple(star_coords[i])
planet.rect = planet.image.get_rect(center=planet.coord)
i += 1
if i == len(star_coords):
del planets[len(star_coords):]
return planets
def main():
global heartbeat
global heartbeat_mod
pygame.init()
width, height = 400, 400
screen = pygame.display.set_mode((width, height))
pygame.display.init()
pygame.display.update()
active_planet_coord = (-100, -100)
heartbeat = 0
heartbeat_mod = .1
clock = pygame.time.Clock()
game = Game()
camera_group = CameraGroup()
planet_array = os.listdir('./')
planet_array.pop(0) # remove 'camera scroll example.py' file
planet_icons = planet_array
planets = []
for new_planet in range(2):
icon = planet_icons.pop()
planet = Planet(icon, camera_group)
planets.append(planet)
star_coords = [[100, 100], [200, 200]]
planets = planet.update_coords(planets, star_coords)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
pygame.quit()
exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
active_planet_coord = game.activate_planet(screen, camera_group)
if active_planet_coord is None:
active_planet_coord = (-100, -100)
pygame.display.update()
elif event.type == pygame.MOUSEWHEEL:
camera_group.zoom_scale += event.y * 0.1
if camera_group.zoom_scale > 2:
camera_group.zoom_scale = 2
elif camera_group.zoom_scale < .5:
camera_group.zoom_scale = .5
camera_group.update()
camera_group.custom_draw(planets)
game.heartbeat(screen, active_planet_coord)
pygame.display.update()
clock.tick(60)
if __name__ == '__main__':
main()
An alternative way to solve this problem is to "inverse zoom" the mouse position when you use it for click detection:
zoom:
p_zoom = (p - zoom_center) * zoom_scale + zoom_center
inverse zoom:
p = (p_zoom - zoom_center) / zoom_scale + zoom_center
Apply this to your code:
class Game:
# [...]
#staticmethod
def activate_planet(screen, planets):
zoom_scale = planets.zoom_scale
zoom_center = pygame.math.Vector2(screen.get_rect().center)
pos = pygame.math.Vector2(pygame.mouse.get_pos())
pos = (pos - zoom_center) / zoom_scale + zoom_center
active_planet_coord = None
for planet in planets:
if planet.rect.collidepoint(pos):
planet_center = (planet.rect.center - zoom_center) * zoom_scale + zoom_center
active_planet_coord = round(planet_center.x), round(planet_center.y)
return active_planet_coord
return active_planet_coord

How Do I Make Enemies In My Game Shoot Missles randomnly?

So I'm trying to create space Invaders currently and i'm trying to make the aliens shoot at the player randomnly. I've made it so that one projectile or enemy missle is shot at the player, but I can't figure out how I can make this happen from random places where the aliens are located, and how I can make the enemy repeatedly shoot missles,as opposed to just once as it's doing right now. My code that i'm using is down below (variables related to the shooting of the enemy are enemMissleFire and enemymissles)....
import pygame
import sys
import time
#initialize pygame
pygame.init()
width = 800
height = 600
# set the size for the surface (screen)
screen = pygame.display.set_mode((width, height),pygame.FULLSCREEN)
width = screen.get_width()
height = screen.get_height()
print(width)
print(height)
# set the caption for the screen
pygame.display.set_caption("Space Invaders")
def checkCollision(missles, type, score):
for missle in missles:
collision = missle.collidelist((type))
if collision > -1:
type.pop(collision)
missles.remove(missle)
missle.move_ip(0,missleSpeed)
pygame.draw.rect(screen, WHITE, missle,0)
# define colours you will be using
WHITE = (255,255,255)
GREEN = (0,255,0)
RED = (255,0,0)
BLUE = (0,0,255)
BLACK = (0,0,0)
YELLOW = (255,255,0)
clock = pygame.time.Clock()
FPS = 60
s = 25
#load and scale images
smallInvaderImg = pygame.image.load("images/smallinvader.png")
smallInvaderImg = pygame.transform.scale(smallInvaderImg,(s,s))
medInvaderImg = pygame.image.load("images/crabinvader.png")
medInvaderImg = pygame.transform.scale(medInvaderImg, (s,s))
bigInvaderImg = pygame.image.load("images/biginvader.png")
bigInvaderImg = pygame.transform.scale(bigInvaderImg, (s,s))
shipImg = pygame.image.load("images/ship.png")
shipImg = pygame.transform.scale(shipImg, (60,60))
smallInvaders = []
medInvaders = []
bigInvaders = []
enemiesMap = ["sssssssssss",
"mmmmmmmmmmm",
"mmmmmmmmmmm",
"bbbbbbbbbbb"]
invadertype = [smallInvaders,medInvaders,bigInvaders]
dx = 1
dy = 0
x = 240
y = 0
gap = 10
for element in enemiesMap:
for char in element:
if char == "s":
smallInvaders.append(pygame.Rect(x,y,s,s))
elif char == "m":
medInvaders.append(pygame.Rect(x,y,s,s))
elif char == "b":
bigInvaders.append(pygame.Rect(x,y,s,s))
x += s + gap
y += s + gap
x = 240
score = 2
ship = pygame.Rect(width/2,525,60,60)
if ship.right == width:
ship.right = width
#missles
maxMissles = 3
missleSpeed = -6
missleWidth = 5
missleHeight = 30
enemmissleWidth = 5
enemmissleHeight = 25
missles = []
missleFired = False
lives = 3
playbutton = pygame.Rect(width/2,height/2,155,90)
playbutton.center = (width/2,height/2)
quitbutton = pygame.Rect(width/2,height/2,155,90)
quitbutton.center = (width/2,height/2+110)
playagn = pygame.Rect(width/2,height/2,155,90)
playagn.center = (width/2,height/2)
playword = pygame.font.SysFont("comicsanms", 35)
title = pygame.font.SysFont("comicsanms", 90)
quitword = pygame.font.SysFont("comicsanms",35)
endscreen = pygame.font.SysFont("comicsanms", 90)
playagaintext = pygame.font.SysFont("comicsanms", 35)
enemMissleFire = False
enemmislist = []
enemymissles = (pygame.Rect(ship.centerx,y,enemmissleWidth,enemmissleHeight))
invaderDirSwapped = False
screenControl = 0
main = True
while main:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
main = False
for element in smallInvaders:
element.move_ip(dx,dy)
for element in smallInvaders:
if element.right >= width or element.left <= 0:
dx *= -1
invaderDirSwapped = True
for element in medInvaders:
element.move_ip(dx,dy)
if not invaderDirSwapped:
for element in medInvaders:
if element.right >= width or element.left <= 0:
dx *= -1
invaderDirSwapped = True
for element in bigInvaders:
element.move_ip(dx,0)
if not invaderDirSwapped:
for element in bigInvaders:
if element.right >= width or element.left <=0:
dx *= -1
invaderDirSwapped = True
key_input = pygame.key.get_pressed()
if key_input[pygame.K_RIGHT] and ship.right < width:
ship.move_ip(4,0)
if key_input[pygame.K_LEFT] and ship.left > 0:
ship.move_ip(-4,0)
if key_input[pygame.K_SPACE] and not missleFired:
missleFired = True
missles.append(pygame.Rect(ship.centerx,ship.top,missleWidth,missleHeight))
if screenControl == 0:
screen.fill(BLACK)
texttitle = title.render("SPACE INVADERS", True, WHITE)
textrect = texttitle.get_rect()
textrect.center = (width/2, 100)
screen.blit(texttitle,textrect)
if playbutton.collidepoint(pygame.mouse.get_pos()) and pygame.mouse.get_pressed()[0]:
screenControl = 1
pygame.draw.rect(screen,WHITE,(playbutton), 0)
if playbutton.collidepoint(pygame.mouse.get_pos()):
pygame.draw.rect(screen,BLUE, (playbutton), 4)
if quitbutton.collidepoint(pygame.mouse.get_pos()) and pygame.mouse.get_pressed()[0]:
main = False
pygame.draw.rect(screen,WHITE,(quitbutton), 0)
if quitbutton.collidepoint(pygame.mouse.get_pos()):
pygame.draw.rect(screen,BLUE,quitbutton,4)
textplay = playword.render("PLAY", True, BLUE)
textrect2 = textplay.get_rect()
textrect2.center = (width/2,height/2)
screen.blit(textplay,textrect2)
textquit = quitword.render("QUIT",True,BLUE)
textrect3 = textquit.get_rect()
textrect3.center = (width/2,height/2+110)
screen.blit(textquit,textrect3)
if screenControl == 1:
screen.fill(BLACK)
if len(missles) > 0:
if missleFired and missles[-1].bottom < (ship.top - 120) and not key_input[pygame.K_SPACE]:
missleFired = False
if len(missles) == 0:
missleFired = False
if enemMissleFire:
enemymissles.colliderect(ship)
enemymissles.move_ip(0,-missleSpeed)
pygame.draw.rect(screen, WHITE, enemymissles,0)
for invader in smallInvaders:
screen.blit(smallInvaderImg, invader)
for invader in medInvaders:
screen.blit(medInvaderImg, invader)
for invader in bigInvaders:
screen.blit(bigInvaderImg, invader)
screen.blit(shipImg,ship)
#move and draw missles
checkCollision(missles,smallInvaders,score)
checkCollision(missles,medInvaders,score)
checkCollision(missles,bigInvaders,score)
if smallInvaders == [] and medInvaders == [] and bigInvaders == []:
screenControl = 2
if screenControl == 2:
screen.fill(BLACK)
if playagn.collidepoint(pygame.mouse.get_pos()) and pygame.mouse.get_pressed()[0]:
screenControl = 0
pygame.draw.rect(screen,WHITE,(playagn),0)
if quitbutton.collidepoint(pygame.mouse.get_pos()) and pygame.mouse.get_pressed()[0]:
main = False
pygame.draw.rect(screen,WHITE, (quitbutton),0)
if playagn.collidepoint(pygame.mouse.get_pos()):
pygame.draw.rect(screen,BLUE,(playagn), 4)
if quitbutton.collidepoint(pygame.mouse.get_pos()):
pygame.draw.rect(screen,BLUE,(quitbutton),4)
textend = endscreen.render("YOU WON!", True, WHITE)
textrect4 = textend.get_rect()
textrect4.center = (width/2, 150)
screen.blit(textend,textrect4)
textplayagn = playagaintext.render("PLAY AGAIN", True, BLUE)
textrect5 = textplayagn.get_rect()
textrect5.center = (width/2,height/2)
screen.blit(textplayagn,textrect5)
textquit = quitword.render("QUIT",True,BLUE)
textrect3 = textquit.get_rect()
textrect3.center = (width/2,height/2+110)
screen.blit(textquit,textrect3)
pygame.display.update()
Regardless, I can't figure out how I can make it so that projectiles can be shot from random aliens, over the course of the player shooting, and so any help would be appreicated.
Import Random and Time
this will get the random and time functions built in
from random import randint as ran
import time
Get the Number
this will get the time between the previous shot and the next
t = ran(Min_Time,Max_Time)
Get The Countdown
this will count a second between numbers
time.sleep(1)
t -= 1
print(t)
Shot!
shoot
if t == 0:
print('shot!')
FINAL CODE (trumpet fanfare)
from random import randint as ran
import time
def randshot(Min_Time,Max_Time):
t = ran(Min_Time,Max_Time)
print(t)
while t:
time.sleep(1)
t -= 1
print(t)
if t == 0:
print('shot!')
while True:
randshot(5,10)
Hope this Helped!

pygame random points function

im trying to create a function that fill the screen with dots(that will be the points in the game)
the problem is the points created by a for loop and when i run the program the points just move around and wont stay in place.
the code:
def random_points():
point_x = random.randint(0, 900)
point_y = random.randint(0, 500)
rand_color = (random.random(), random.random(), (random.random()))
R = random.randint(1, 4)
for _ in range(1, 5):
pygame.draw.circle(WIN, rand_color, (point_x, point_y), R)
You need to create a list of points before the application loop:
point_list = []
for _ in range(1, 5):
point_x = random.randint(0, 900)
point_y = random.randint(0, 500)
rand_color = (random.random(), random.random(), (random.random()))
R = random.randint(1, 4)
point = (rand_color, (point_x, point_y), R)
point_list.append(point)
Draw the points from the list in the application loop:
run = True
while run:
for event = pygame.event.get():
if event.type == pygame.QUIT:
run = False
WIN.fill(0)
for color, center, rad in point_list:
pygame.draw.circle(WIN, color, center, rad)
pygame.disaply.flip()

Multiple function calls for parallel processing inside a loop

I wish to detect an image from my webcam and write the result whether it is moving left or right to a file. To increase the frame rate(because this project involves a lot more processing and might be run on a raspberry pi) I decided to do the file writing part through multiprocessing (which I am new to):
code:
function for multi-processing
def send_cmd(cv):
# ####EXECUTE LOGIC AND CREATE COMMAND###
# writing to file
# just a sample command// change as will
dictionary = {'left': 0, 'right': 0, 'stop': 0}
if cv[0] == 'left':
dictionary['right'] = 1
else:
dictionary['left'] = 1
cmd = '{"left":' + str(dictionary['left']) + ',"right":' + str(dictionary['left'
]) + ',"stop":' + str(dictionary['left']) + '}'
print("command written: " + cmd)
f = open('command.txt', 'w')
f.write(cmd)
f.close()
Main code:
while True:
try:
frame = cv.VideoCapture(0)
frame = imutils.resize(frame, width=400)
if W is None or H is None:
(H, W) = frame.shape[:2]
blob = cv.dnn.blobFromImage(cv.resize(frame, (300, 300)),
0.007843, (300, 300), 127.5)
net.setInput(blob)
detections = net.forward()
rects = []
for i in range(0, detections.shape[2]):
if detections[0, 0, i, 2] > args['confidence']:
box = detections[0, 0, i, 3:7] * np.array([W, H, W, H])
rects.append(box.astype('int'))
(startX, startY, endX, endY) = box.astype('int')
cv.rectangle(frame, (startX, startY), (endX, endY), (0,
0xFF, 0), 2)
objects = ct.update(rects)
for (objectID, centroid) in objects.items():
text = 'ID {}'.format(objectID)
cv.putText(
frame,
text,
(centroid[0] - 10, centroid[1] - 10),
cv.FONT_HERSHEY_SIMPLEX,
0.5,
(0, 0xFF, 0),
2,
)
cv.circle(frame, (centroid[0], centroid[1]), 4, (0, 0xFF,
0), -1)
center = (centroid[0], centroid[1])
pts.appendleft(center)
for i in np.arange(1, len(pts)):
if pts[i - 1] is None or pts[i] is None:
continue
if counter >= 10 and i == 1 and pts[-1] is not None:
dX = pts[-1][0] - pts[i][0]
dY = pts[-1][1] - pts[i][1]
global dirX
global dirY
(dirX, dirY) = ('', '')
if np.abs(dX) > 20:
dirX = ('left' if np.sign(dX) == 1 else 'right')
if np.abs(dY) > 20:
dirY = ('up' if np.sign(dY) == 1 else 'down')
#tried multiprocessing with process method but to many process calls at the same time
order = multiprocessing.Process(target = send_cmd,args = ([dirX, dirY]))
order.start()
order.join()
# send_cmd(cv=[dirX, dirY], us=ultra_sonic)
if dirX != '' and dirY != '':
direction = '{}-{}'.format(dirY, dirX)
else:
direction = (dirX if dirX != '' else dirY)
thickness = int(np.sqrt(args['buffer'] / float(i + 1))
* 2.5)
cv.putText(
frame,
direction,
(10, 30),
cv.FONT_HERSHEY_SIMPLEX,
0.65,
(0, 0, 0xFF),
3,
)
cv.putText(
frame,
'dx: {}, dy: {}'.format(dX, dY),
(10, frame.shape[0] - 10),
cv.FONT_HERSHEY_SIMPLEX,
0.35,
(0, 0, 0xFF),
1,
)
cv.imshow('Frame', frame)
key = cv.waitKey(1) & 0xFF
counter += 1
Error:
RuntimeError:
An attempt has been made to start a new process before the
current process has finished its bootstrapping phase.
This probably means that you are not using fork to start your
child processes and you have forgotten to use the proper idiom
in the main module:
if __name__ == '__main__':
freeze_support()
...
The "freeze_support()" line can be omitted if the program
is not going to be frozen to produce an executable.
Can someone guide me?

Image viewer - standard gui controls, bottom-up or what?

I need to make a basic image viewer.
The main concern is how to implement (in terms of gui components and image manipulation) such features as: zoom in-out, scroll and 'hand tool'.
There seems to be several options for achieving this goal, differing mainly in degree of putting responsibilities on the graphical interface framework as opposed to manually implementing things.
Two solutions that are obvious to me are:
1) Resizing and cropping the visible part of the image is crafted on its own using functionality of some image manipulation library. The image (or it's part) is then drawn on some window/control in an overridden onPaint() method. Scrollbars updating (when 'hand tool' used) and operation (when used directly) code needs to be written.
2) An oversized control (StaticBitmap or whatever) containing the image is put inside a window with automatic scrolling. Then one needs to figure out how to convert image coordinates to scrolling coordinates.
Both ways look awkward. Any ideas how to do it in a neat way? Or is what I feel as being ugly just the only way to go?
I'm using Python with wxPython/wxWidgets and PIL, but the question is to a large extent language- and platform- independent.
Sample code and links to sources (of something that is not too bloated) are welcome.
Here's a tutorial that might help. Build a wxPython Image Viewer
I didn't actually watch all the videos, so I can't speak to how well it address's your specific problems.
Also, here's a blog post by Jeff Atwood on Coding Horror that could apply. Programming Is Hard, Let's Go Shopping! It speaks to when you should take the time to write your own code and when to just use a third party solution.
I'm new here, and after searching for a while I still can't find a way to upload files. Oh well, here's the code in a post. Sorry for the non-descriptive variable names and lack of comments. I guess the main functions you'll want to look at are processPicture and showPicture.
Edit: and just to reiterate, I started this with the example in this tutorial.
import wx, os, string, sys
from PIL import Image
# Scroll wheel and +/- do zoom in/out. f toggles full screen. r rotates.
# m changes PIL mode from low quality (fast) to high quality (slow).
# Images under 1000x1000 are automatically on high quality.
# Middle button down while dragging moves image around, as do arrow
# keys (if image is bigger than window).
# Left and right mouse buttons are next and previous image.
# There is no functionality to load an image. When an executeable is made, the
# viewer is started by opening an image with it.
# To run this file from command line, comment out line 55 and uncomment
# line 54, then do "viewer.py sampleImage"
# There are several lines that are Windows specific. They (probably) all have
# to do with paths, i.e, "/" vs "\".
class ImageFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,None,title = "viewer")
self.Centre()
self.Size = (450,450)
self.imageBox = wx.Window(self)
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.CreateStatusBar(5)
self.SetStatusWidths([-1, 70, 50, 50, 30])
self.cursor = wx.StockCursor(wx.CURSOR_ARROW)
self.moveCursor = wx.StockCursor(wx.CURSOR_SIZING)
self.vbox.Add(self.imageBox,proportion=1,flag = wx.EXPAND)
self.SetSizer(self.vbox)
self.Show()
self.sbm = 0
self.sbmList = []
self.name = ''
self.url = ''
self.dir = ''
self.factor = 1.0
self.rotation = 0
self.width = 0
self.height = 0
self.count = 0
self.size = 0
self.numOfPics = 0
self.mc = False
self.fs = False
self.mode = 0
self.SetStatusText(str(self.mode), 4)
if len(sys.argv) == 2:
#self.url = os.getcwd() + '\\' + sys.argv[1]
self.url = sys.argv[1]
self.name = self.url.split('\\')[len(self.url.split('\\'))-1]
self.dir = self.url.replace('\\' + self.name,'')
self.loadDirectory(self.dir)
self.processPicture()
self.imageBox.Bind(wx.EVT_SIZE, lambda evt: self.rescale(evt,1))
self.imageBox.Bind(wx.EVT_MOUSEWHEEL,self.zoom)
self.imageBox.Bind(wx.EVT_KEY_DOWN, self.keyEvent)
self.imageBox.Bind(wx.EVT_MIDDLE_UP, self.endDrag)
self.imageBox.SetBackgroundColour((0,0,0,0))
self.imageBox.Bind(wx.EVT_LEFT_DOWN, self.next)
self.imageBox.Bind(wx.EVT_RIGHT_DOWN, self.prev)
def nameFromUrl(self,url):
name = url.split('\\')
name = name[len(name)-1]
return name
def processPicture(self, factor = 0):
img = Image.open(self.url)
self.width = img.size[0]
self.height = img.size[1]
ogHeight = self.height
ogWidth = self.width
xWin = self.imageBox.Size[0]
yWin = self.imageBox.Size[1]
winRatio = 1.0*xWin/yWin
imgRatio = 1.0*self.width/self.height
self.factor = factor*self.factor
if factor == 0:
self.factor = 1
mode = 0
if (ogWidth <=1000 and ogHeight <= 1000) or self.mode == 1:
mode = 1
if imgRatio >= winRatio: #match widths
self.width = self.factor*xWin
self.height = self.factor*xWin/imgRatio
img = img.resize((int(self.width),int(self.height)),mode)
else: #match heights
self.height = self.factor*yWin
self.width = self.factor*yWin*imgRatio
img = img.resize((int(self.width),int(self.height)),mode)
label = str(int(100*self.width/ogWidth))
name = self.nameFromUrl(self.url)
index = self.sbmList.index(name)
self.SetStatusText(name, 0)
self.SetStatusText(str(ogWidth) + 'x' + str(ogHeight), 1)
self.SetStatusText(label + '%', 2)
self.SetStatusText(str(index+1) + '/' + str(self.numOfPics), 3)
if self.rotation % 360 != 0:
img = img.rotate(self.rotation)
self.width = img.size[0]
self.height = img.size[1]
wximg = wx.EmptyImage(img.size[0],img.size[1])
wximg.SetData(img.convert("RGB").tostring())
wximg.SetAlphaData(img.convert("RGBA").tostring()[3::4])
self.showPicture(wximg)
def showPicture(self,img):
bmp = wx.BitmapFromImage(img)
x = (self.imageBox.Size[0] - self.width)/2.0
y = (self.imageBox.Size[1] - self.height)/2.0
tmp = wx.StaticBitmap(self.imageBox,wx.ID_ANY,bmp,(x,y))
tmp.Bind(wx.EVT_LEFT_DOWN, self.next)
tmp.Bind(wx.EVT_RIGHT_DOWN, self.prev)
tmp.Bind(wx.EVT_MOTION, self.drag)
tmp.Bind(wx.EVT_MIDDLE_UP, self.endDrag)
tmp.SetBackgroundColour((180,180,180,180))
if self.sbm:
self.sbm.Destroy()
self.sbm = tmp
self.imageBox.Refresh()
def loadDirectory(self,dir):
self.sbmList = []
for image in os.listdir(dir):
if image.lower().endswith('jpg') or image.lower().endswith('png') or image.lower().endswith('jpeg') or image.lower().endswith('gif') or image.lower().endswith('bmp'):
self.sbmList.append(image)
self.numOfPics = len(self.sbmList)
def next(self,event):
if self.name in self.sbmList:
n = self.sbmList.index(self.name)
if n == len(self.sbmList) - 1:
n = -1
self.name = self.sbmList[n + 1]
self.url = self.dir + '\\' + self.name
self.rotation = 0
self.processPicture()
def prev(self,event):
if self.name in self.sbmList:
n = self.sbmList.index(self.name)
if n == 0:
n = len(self.sbmList)
self.name = self.sbmList[n - 1]
self.url = self.dir + '\\' + self.name
self.rotation = 0
self.processPicture()
def rescale(self,event,factor):
if self.url and self.GetStatusBar(): #close is seen as a size event.
self.processPicture(factor)
def zoom(self,event):
factor = 1.25
if event.GetWheelRotation() < 0:
factor = 0.8
self.rescale(event,factor)
def keyEvent(self,event):
code = event.GetKeyCode()
if code == 43: #plus
self.rescale(event,1.25)
elif code == 45: #minus
self.rescale(event,0.8)
elif code == 82 and self.url: #r
self.rotation = self.rotation + 90
self.processPicture(1)
elif code == 70: #f
self.toggleFS()
elif (code == 314 or code == 315 or code == 316 or code == 317) and self.sbm:
#left, up, right, down
self.scroll(code)
elif code == 77: #m
if self.mode == 0:
self.mode = 1
else:
self.mode = 0
self.SetStatusText(str(self.mode), 4)
self.processPicture(1)
def scroll(self,code):
boxPos = self.imageBox.GetScreenPositionTuple()
imgPos = self.sbm.GetScreenPositionTuple()
delta = 20
if code == 314 and self.width > self.imageBox.Size[0]:
compare = boxPos[0] - imgPos[0]
if compare <= delta:
delta = max(compare,0)
self.imageBox.ScrollWindow(delta,0)
if code == 315 and self.height > self.imageBox.Size[1]:
compare = boxPos[1] - imgPos[1]
if compare <= delta:
delta = max(compare,0)
self.imageBox.ScrollWindow(0,delta)
if code == 316 and self.width > self.imageBox.Size[0]:
compare = imgPos[0] + self.sbm.Size[0] - boxPos[0] - self.imageBox.Size[0]
if compare <= delta:
delta = max(compare,0)
self.imageBox.ScrollWindow(-delta,0)
if code == 317 and self.height > self.imageBox.Size[1]:
compare = imgPos[1] + self.sbm.Size[1] - boxPos[1] - self.imageBox.Size[1]
if compare <= delta:
delta = max(compare,0)
self.imageBox.ScrollWindow(0,-delta)
def drag(self,event):
if event.MiddleIsDown():
if not self.mc:
self.SetCursor(self.moveCursor)
self.mc = True
boxPos = self.imageBox.GetScreenPositionTuple()
imgPos = self.sbm.GetScreenPositionTuple()
if self.count == 0:
self.x = event.GetX()
self.y = event.GetY()
self.count = self.count + 1
if self.count > 1:
deltaX = event.GetX() - self.x
deltaY = event.GetY() - self.y
if imgPos[0] >= boxPos[0] and deltaX > 0:
deltaX = 0
if imgPos[0] + self.width <= boxPos[0] + self.imageBox.Size[0] and deltaX < 0:
deltaX = 0
if imgPos[1] >= boxPos[1] and deltaY > 0:
deltaY = 0
if imgPos[1] + self.height <= boxPos[1] + self.imageBox.Size[1] and deltaY < 0:
deltaY = 0
self.imageBox.ScrollWindow(2*deltaX,2*deltaY)
self.count = 0
def endDrag(self,event):
self.count = 0
self.SetCursor(self.cursor)
self.mc = False
def toggleFS(self):
if self.fs:
self.ShowFullScreen(False)
self.fs = False
else:
self.ShowFullScreen(True)
self.fs = True
app = wx.App(redirect = False)
frame = ImageFrame()
app.MainLoop()
And there is Cornice an open source image viewer written in wxPython which might help you
You can try Document/View architecture.
I think it should be available in Python. You can take a look at this tutorial, it is for C++ but approach should be similar. It also shows how to implement selection rectangle.
I actually just made a simple image viewer with wxPython and PIL. I didn't want to, but I was having a hard time finding a viewer as simple as I wanted. Anyway, I started from this page and worked myself up to an app that zooms, rotates, and browses all images in the folder it was started from. When I get home I can post the full code if you like.

Resources