Error with pymunk space.remove method - python

I have a ball generator, that "generates" and adds balls(circles) to the simulation.
The ball is to be removed when it hits a static poly in list s_boxes.
This is done by a collision handler ball_wall_collision.
The Error:
The following pop-up window does what it's name says, it pops-up
My code:
Ball Generator
class BallGenerator:
def __init__(self, min_y, max_y, x):
self.min = min_y
self.max = max_y
self.x = x
self.counter = 0
def bowl(self, balls):
global ball_bowled
y = random.randint(self.min, self.max)
pos = to_pymunk((self.x,y))
r = 10
m = 15
i = pm.moment_for_circle(m, 0, r)
b = pm.Body(m,i)
b.position = pos
f_x = random.randint(-600000,-400000)
b.apply_force( (f_x,0.0),(0,0) )
ball = pm.Circle(b, r)
ball.elasticity = 0.75
ball.friction = 0.95
balls.append(ball)
space.add(ball,b)
print 'bowled'
ball_bowled += 1
def handle(self, balls):
if self.counter == FPS:
self.bowl(balls)
self.counter = 0
self.counter += 1
Collision handler
def ball_wall_collision(space, arb, balls, s_boxes):
shapes = arb.shapes
boxes = [box[0] for box in s_boxes] # Get walls
ball = None
wall = None
for ba in balls:
if ba in shapes:
ball = ba
break
for box in boxes:
if box in shapes:
wall = box
break
if wall and ball:
print 'removing'
space.remove(ball, ball.body) # Where the runtime problem happens
balls.remove(ball)
print 'removed'
return False
else:
return True
space.add_collision_handler(0,0,begin=ball_wall_collision,
balls=balls,s_boxes=s_boxes) # Other args to function
What am I doing wrong in the collision handling??
Am I missing something in the call to space.remove?
Is the function not working as I want it to??
Or is the error elsewhere (which I don't think it is)...

It looks like the problem is that you try to remove objects from the space in a collision handler during the simulation step.
Instead you can try with either manually collect all the balls into a list and then call remove after the step, or queue up removes with the post step callback like this:
space.add_post_step_callback(space.remove, ball)
space.add_post_step_callback(space.remove, ball.body)
(untested code)
I should probably try and make this more obvious in the API docs.. I wonder if it would be a good idea to automatically schedule the remove until end of step, or the less intrusive option, trigger a assert in python so you dont get the c++ error.

Related

Pygame make sprite invisible/flashing while invincible

I have already implemented the invincibility / immunity part for when bullet collides with tank, now I want the sprite to "flash"/less opacity/invisible while immune. Here is the relevant code and TODOs where I want to implement a change in the tank image.
class Tank(GamePhysicsObject):
# Handle respawn
self.time_since_death = pygame.time.get_ticks()
self.immune = False
self.immune_time = 3000
def collision_bullet_tank(arb, space, data):
# Set immunity, save time since death, start flashing
tank.parent.immune = True
tank.parent.time_since_death = pygame.time.get_ticks()
tank.parent.start_flashing()
def post_update(self):
# If tank has been immune for some time, make tank vulnerable again
current_time = pygame.time.get_ticks()
if current_time - self.time_since_death > self.immune_time:
self.immune = False
# TODO: Stop flashing
Basically, my question is how can I make a visual sprite change?
Edit: More specifically, what do I type here?
def start_flashing(self):
""" Call this function to make the tank flash. """
#TODO: White layer opacity 50% ?
1
The simplest solution is to draw the object Tank only in every 2nd frame as long as self.immune is set:
class Tank(GamePhysicsObject):
def __init__(self):
self.immune = False
self.frame_count = 0
def draw(self)
draw_tank = True
if self.immune:
self.frame_count += 1
draw_tank = self.frameCount % 2 == 0
if draw_tank:
# draw the tank

Python CodeSkulptor Pause Drawing from Inside For Loop

I want to build some visualizations for searching algorithms (BFS, A* etc.) within a grid.
My solution should show each step of the algorithm using CodeSkulptor simplegui (or the offline version using SimpleGUICS2Pygame.)
I've made a version which highlights all the cells visited by changing their color, but I've run into trouble trying to make the path display step-by-step with a time delay between each step.
I've extracted the essence of the problem and created a minimal example representing it in the code below, also run-able online here: http://www.codeskulptor.org/#user47_jB2CYfNrH2_2.py
What I want is during the change_colors() function, for there to be a delay between each iteration.
CodeSkulptor doesn't have time.sleep() available, and I don't think it would help anyway.
CodeSkulptor does have timers available, which might be one solution, although I can't see how to use one in this instance.
Code below:
import time
try:
import simplegui
except ImportError:
import SimpleGUICS2Pygame.simpleguics2pygame as simplegui
simplegui.Frame._hide_status = True
TITLE = "TEST"
FRAME_WIDTH = 400
FRAME_HEIGHT = 400
DELAY = 10
class Square:
"""This class represents a simple Square object."""
def __init__(self, size, pos, pen_size=2, pen_color="red", fill_color="blue"):
"""Constructor - create an instance of Square."""
self._size = size
self._pos = pos
self._pen_size = pen_size
self._pen_color = pen_color
self._fill_color = fill_color
def set_color(self, color):
self._fill_color = color
def get_color(self):
return self._fill_color
def is_in(self, pos):
"""
Determine whether coordinates are within the area of this Square.
"""
return self._pos[0] < pos[0] < self._pos[0] + self._size and self._pos[1] < pos[1] < self._pos[1] + self._size
def draw(self, canvas):
"""
calls canvas.draw_image() to display self on canvas.
"""
points = [(self._pos[0], self._pos[1]), (self._pos[0] + self._size, self._pos[1]),
(self._pos[0] + self._size, self._pos[1] + self._size), (self._pos[0], self._pos[1] + self._size)]
canvas.draw_polygon(points, self._pen_size, self._pen_color, self._fill_color)
def __str__(self):
return "Square: {}".format(self._pos)
def draw(canvas):
for square in squares:
square.draw(canvas)
def change_colors():
for square in squares:
# time.sleep(1) # Not implemented in CodeSkulptor and would'nt work anyway
square.set_color("green")
frame = simplegui.create_frame(TITLE, FRAME_WIDTH, FRAME_HEIGHT)
frame.set_draw_handler(draw)
width = 20
squares = []
for i in range(10):
squares.append(Square(width, (i * width, 0)))
change_colors()
frame.start()
Any help appreciated.
Yes, you need to use a timer. Something like this:
I = 0
def change_next_color():
if I < len(squares):
squares[I].set_color("green")
global I
I += 1
timer = simplegui.create_timer(1000, change_next_color)
timer.start()
http://www.codeskulptor.org/#user47_udyXzppCdw2OqdI.py
I also replaced
simplegui.Frame._hide_status = True
by simplegui.Frame._hide_controlpanel = True
https://simpleguics2pygame.readthedocs.io/en/latest/simpleguics2pygame/frame.html#SimpleGUICS2Pygame.simpleguics2pygame.frame.Frame._hide_controlpanel
See also _keep_timers option of SimpleGUICS2Pygame to help you:
https://simpleguics2pygame.readthedocs.io/en/latest/simpleguics2pygame/frame.html#SimpleGUICS2Pygame.simpleguics2pygame.frame.Frame._keep_timers
Possible improvements:
Find a better solution that don't use a global counter.
Stop timer when all work is finished.

Using VPython how to call Class sphere positions?

Through using VPython, I am able to get the program I am working on to generate multiple balls by calling the same class. I am also able to have the balls appear within a selected random range when they are generated (across x, y and z).
However, I am currently stumped on how I get to call the pos / position function from within my loop - as I would like to have the balls move.
Please see my code so far below.
If I call Ball.pos it states as undefined, but if I place my positioning via self.position, only one ball is generated, as they are not being referred to from under the sphere details?
from visual import *
from random import *
scene.title = "Multi Balls"
wallBm = box(pos=(0,-6,0), size=(12.2,0.1,12.1), color=color.blue, opacity=0.4)
vscale = 0.1
deltat = 0.005
t = 0
scene.autoscale = False
i = 0
totalBalls = 10
class Ball:
def __init__(self):
self.velocity = vector(0,5,0)
#vel sample ([5,10,15,20,25],3)
sphere (pos=(randrange (-6,6),randrange (-6,6),randrange (-6,6)), radius=0.5, color=color.cyan)
while True:
rate(100)
if i < totalBalls:
Ball()
i = i + 1
t = 5 + deltat
Try Inheritance from frame:
class Ball(frame):
def __init__(self, pos=(randrange (-6,6),randrange (-6,6),randrange (-6,6))):
frame.__init__(self,pos=pos)
self.velocity = vector(0,5,0)
sphere(frame=self,pos=pos,radius=0.5, color=color.cyan)
listOfBalls=[]
while True:
rate(100)
for i in range(totalBalls):
listOfBalls.append(Ball())
now try again!
You can call each Ball's position by calling listOfBalls[3].pos. I hope this helps!

Fade Between Two Music Tracks in-progress in Pygame

My intention is to have two music tracks, which are similar in nature, fade between each other at various times. When such a fade occurs, one music track should fade from full volume to muted in a short period of time, and, simultaneously, the other track should fade from 0 to 100 and continue playing from the same time index. They must be able to do this dynamically at any time - when a certain action occurs, the fade will occur and the new track will start playing at the same position that the other one left off at.
This might be plausible by either using volume manipulation or by starting and stopping the music (however, it appears that only a "fadeout" option exists, and there is a lack of a "fadein" option). How can I do this? What is the best method, if any, that exists? If it is impossible using Pygame, alternatives to Pygame are acceptable.
This isn't exactly an answer to the question, but for future-googlers I wrote a script to fade-in my music from volume 0 in the morning and this is what I used:
max_volume = 40
current_volume = 0
# set the volume to the given percent using amixer
def set_volume_to(percent):
subprocess.call(["amixer", "-D", "pulse", "sset", "Master",
str(percent) + "%", "stdout=devnull"])
# play the song and fade in the song to the max_volume
def play_song(song_file):
global current_volume
print("Song starting: " + song_file)
pygame.mixer.music.load(song_file)
pygame.mixer.music.play()
# gradually increase volume to max
while pygame.mixer.music.get_busy():
if current_volume < max_volume:
set_volume_to(current_volume)
current_volume += 1
pygame.time.Clock().tick(1)
play_song("foo.mp3")
Try this, it's pretty straight forward..
import pygame
pygame.mixer.init()
pygame.init()
# Maybe you can subclass the pygame.mixer.Sound and
# add the methods below to it..
class Fader(object):
instances = []
def __init__(self, fname):
super(Fader, self).__init__()
assert isinstance(fname, basestring)
self.sound = pygame.mixer.Sound(fname)
self.increment = 0.01 # tweak for speed of effect!!
self.next_vol = 1 # fade to 100 on start
Fader.instances.append(self)
def fade_to(self, new_vol):
# you could change the increment here based on something..
self.next_vol = new_vol
#classmethod
def update(cls):
for inst in cls.instances:
curr_volume = inst.sound.get_volume()
# print inst, curr_volume, inst.next_vol
if inst.next_vol > curr_volume:
inst.sound.set_volume(curr_volume + inst.increment)
elif inst.next_vol < curr_volume:
inst.sound.set_volume(curr_volume - inst.increment)
sound1 = Fader("1.wav")
sound2 = Fader("2.wav")
sound1.sound.play()
sound2.sound.play()
sound2.sound.set_volume(0)
# fading..
sound1.fade_to(0)
sound2.fade_to(1)
while True:
Fader.update() # a call that will update all the faders..
Pseudocode:
track1 = ...
track2 = ...
track1.play_forever()
track1.volume = 100
track2.play_forever()
track2.volume = 0
playing = track1
tracks = [track1, track2]
def volume_switcher():
while True:
playing.volume = min(playing.volume + 1, 100)
for track in tracks:
if track != playing:
track.volume = max(track.volume - 1, 100)
time.sleep(0.1)
Thread(target=volume_switcher).start()
So it looks like what you want to do in pygame is create two 'Sound' objects, and create a linear interpolation on the volume between the two of them.
I would create two vectors, each from [0,100], and relate them inversely with some constant.
So when sound A is at 100, sound b is at 0. Then when an action occurs, you modify the constant.
t=0
A: [0 ... 100]
B: [0 ... 100]
t=1
ACTION
t=1.1
A:[0 .. 50 .. 100]
B:[0 .. 50 .. 100]
t=2
A:[0 ... 100]
B:[0 ... 100]
Now some code. I'm not familiar with pygame, but this should put you on the right track.
class Song(object):
def __init__(self, songfilename):
self.song = pygame.mixer.Sound(songfilename)
def setVolume(self, somenumber):
#number validation
#possibly do some volume curve here if you wanted
self.song.set_volume(somenumber)
class SongFader(object):
def __init__(self, song1, song2):
self.song1 = song1
self.song2 = song2
self.__xAxisMax = 100
self.__xAxisMin = 0
def fade(self, xaxis):
assert(self.__xAxisMin <= xaxis <= self.__xAxisMax)
#could be any numbers you want.
#i chose 0-100 for convenience
self.song1.setVolume(xaxis)
self.song2.setVolume(self.__xAxisMax-xaxis)
song1 = Song('Song1.wav')
song2 = Song('Song2.wav')
fader = SongFader(song1, song2)
#Inside some event loop when you action is triggered
fader.fade(100) #Only song2 is playing
fader.fade(50) #Songs are evenly split
fader.fade(0) #Only left song is playing
edit
The linear interpolation is probably the more important concept here, so i have modified the fader class, with inspiration from Eric 's thread idea.
class SongFader(object):
def __init__(self, song1, song2):
self.song1 = song1
self.song2 = song2
self.lefttoright = False
self.starttime = 0
self.endtime = 0
def fade(self, starttime, fadeleft):
self.lefttoright = fadeleft == True #Being verbose here
self.starttime = starttime #assuming time is in millis
self.endtime = starttime + 1000
Thread(target = self.fadeHelper).start()
#this is where you define how the two songs are faded
def fadeHelper(self):
#if using thread, make sure you mutex the 'self.' variables
starttime = self.starttime
endtime = self.endtime
lefttoright = self.lefttoright
while starttime < endtime:
fadevalue = (starttime - endtime) / 1000 #a val between [0,1]
if lefttoright:
self.song1.setVolume(fadevalue)
self.song2.setVolume(1-fadevalue)
else:
self.song1.setVolume(1-fadevalue)
self.song2.setVolume(fadefalue)
starttime = getGameTimeFromSomewhere()

wxPython Rendering issues, very slow and crashes, not sure why

I have been creating a simple tile based game to help me learn python and wx python. For starters I wanted to create my own 'world' and to test the simple map generator I made, I bound the return key to generate a new map and display it.
That is when I ran into this problem. It slows down a lot every time you click return, renders each tile line by line (which is obviously slow and inefficient) and eventually just freezes.
I am quite a novice programmer and have never dealt with any form of GUI so this is all very new to me, please bear with me! I can guess that the way I have things set up is very taxing for the machine, and that perhaps I'm causing a lot of recursion. I'm simply unaware. Also I'm not too familiar with OOP so I just follow examples to create my classes (hence why I only have 1 massive class that handles everything, Im not too sure on all the '__ something__' functions.)
Here is all the code I have written so far, please ignore commented-out sections, they are for future functions etc:
import wx
import random
#main screen class, handles all events within the main screen
class MainScreen(wx.Frame):
hMap = []
tTotalX = 50
tTotalY = 50
def __init__(self, *args, **kwargs):
#This line is equivilant to wx.Frame.__init__('stuff')
super(MainScreen, self).__init__(None, -1, 'You shouldnt see this', style = wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX)
self.renderScreen()
def genMap(self,tTotalX,tTotalY):
count1 = 0
count2 = 0
self.hMap = []
while count1 < tTotalY:
count2 = 0
newrow = []
while count2 < tTotalX:
newrow.append(random.randint(1,120))
count2 += 1
self.hMap.append(newrow)
count1 += 1
self.smooth(tTotalX, tTotalY)
self.smooth(tTotalX, tTotalY)
def smooth(self, tTotalX, tTotalY):
countx = 0
county = 0
while county < tTotalY:
countx = 0
while countx < tTotalX:
above = county - 1
below = int(county + 1)
east = int(countx + 1)
west = int(countx - 1)
if east >= tTotalX:
east = 0
if west < 0:
west = tTotalX -1
teast = self.hMap[county][east]
twest = self.hMap[county][west]
if above < 0 or below >= tTotalY:
smooth = (self.hMap[county][countx] + teast + twest)/3
else:
tabove = self.hMap[above][countx]
tbelow = self.hMap[below][countx]
smooth = (self.hMap[county][countx] + tabove + tbelow + teast + twest)/5
self.hMap[countx][county] = int(smooth)
countx += 1
county += 1
def getTileType(self, coordX, coordY, totalX, totalY):
#this is the part of map creation, getting tile type based on tile attributes
tType = ''
height = self.hMap[coordX][coordY]
#the below values are all up to tweaking in order to produce the best maps
if height <= 55:
tType = 'ocean.png'
if height > 55:
tType = 'coast.png'
if height > 60:
tType = 'grassland.png'
if height > 75:
tType = 'hills.png'
if height > 80:
tType = 'mountain.png'
if tType == '':
tType = 'grassland.png'
return tType
#render the main screen so that it dislays all data
def renderScreen(self):
frameSize = 810 #Size of the game window
tTotalX = self.tTotalX #the dimensions of the tile display, setting for in-game coordinates
tTotalY = self.tTotalY
#tsTiny = 1 #ts = Tile Size
#tsSmall = 4
tsMed = 16
#tsLrg = 32
#tsXlrg = 64
tsCurrent = tsMed #the currently selected zoom level, for now fixed at tsMed
pposX = 0 #ppos = Pixel Position
pposY = 0
tposX = 0 #tpos = tile position, essentially the tile co-ordinates independent of pixel position
tposY = 0
#The below is just an example of how to map out the grid, it should be in its own function in due time
self.genMap(tTotalX, tTotalY)
while tposY < tTotalY: #loops through all y coordinates
tposX = 0
while tposX < tTotalX: #loops through all x coordinates
pposX = tposX*tsCurrent
pposY = tposY*tsCurrent
tiletype = self.getTileType(tposX,tposY,tTotalX,tTotalY)
img = wx.Image(('F:\First Pass\\' + str(tiletype)), wx.BITMAP_TYPE_ANY).ConvertToBitmap()
wx.StaticBitmap(self, -1, img, (pposX, pposY))#paints the image object (i think)
tposX += 1
tposY += 1
self.Bind(wx.EVT_KEY_DOWN, self.onclick)
self.SetSize((frameSize-4, frameSize+16))
self.SetBackgroundColour('CYAN')
self.Centre()
self.SetTitle('Nations First Pass')
#string = wx.StaticText(self, label = 'Welcome to Nations, First Pass', pos = (tTotalX*tsCurrent/2,tsCurrent*tTotalY/2))
#string.SetFont(wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT))
self.Show()
def onclick(self, e):
key = e.GetKeyCode()
if key == wx.WXK_RETURN:
self.renderScreen()
#game loop
def main():
app = wx.App()
MainScreen(None)
app.MainLoop()
if __name__ == '__main__':
main()
You will need to fabricate your own 'ocean.png' 'coast.png' 'grassland.png' 'hills.png' and 'mountain.png' (they need to be 16x16 pixels) or you can use mine from the Imgur link:
http://imgur.com/a/uFxfn
Also please change the file path in the img code as appropriate. I need to figure out how to set that to do it itself as well but thats another challenge for another day.
If you have any insight into this I'd be very appreciative.
You are creating a new set of wx.StaticBitmaps each time renderScreen is called, and you are not deleting them before creating a new set. After a while you'll have gobs of widgets stacked up with the old ones no longer visible, but still there consuming resources. At a minimum you should change things so your program makes only one set of wx.StaticBitmaps, keeps track of them, and then call their SetBitmap method when you want to change them.
For even better performance you should forget about the StaticBitmaps and draw the images yourself in an EVT_PAINT handler. StaticBitmaps are intended to be "static", IOW not changing very much. Instead you can implement an EVT_PAINT handler for the window and it will be called whenever the window needs to be redrawn, and you can trigger new redraws simply by calling the window's Refresh method.

Categories

Resources