psychopy.org | Reference | Downloads | Github

Setting new stimuli positions upon collision

python3

#1

Hello PsychoPy Discourse community!

Forgive my spaghetti code. Hopefully not too annoying.

I’m coding a game where I am a character trying to dodge obstacles. I want to remove all of the randomly placed obstacles upon my collision with any one of them and replace these with newly positioned obstacles.

Right now I have a messy for loop embedded within a while loop that, upon registering an overlap/collision, draws new obstacles without discarding the old ones. How did I fix this?

Snip-it of my existing code (only most relevant parts included):

me_y = -600 # my initial vertical position 
    
    while True:
        for i in range(len(obs_pos)): # indexes all x,y coordinates of obstacle positions
            obs_circle.pos = [obs_pos[i][0], obs_pos[i][1]] # updates position of obstacle
            obs_circle.draw() 
            if me_circle.overlaps(obs_circle): # if I interact with any of the obstacles
                me_y = -600 # put my character back to start
                new_positions() # call the function that creates new positions for obstacles
         
      # ... code in between that does other stuff... #
        
        me_circle.draw()
        win.flip()

So as you can see, I don’t have a way to remove the old obstacles from the screen. I’m not fully there to understand how I can fix this, which is why I am reaching out to you lovely folks. Let me know if you have the answer! Much appreciates


#2

Hi Hanna,

At the moment, you are unconditionally drawing your obstacle obs_circle. Don’t draw it until the end of the loop (at the same point where you draw me_circle), so that whatever happens as a result of the earlier code, it gets drawn at the correct location on very frame.

The stimulus doesn’t need to be drawn in order to check whether it overlaps another stimulus: the .overlaps() function will just be calculating things geometrically, based upon the stimulus’ current position and vertex attributes. These exist regardless of whether the stimulus has been drawn.


#3

Hi Michael,

Thanks for responding. So I tried this modification, and now only one obstacle gets drawn rather than the whole obstacle set. My character has begun to collide with “invisible” obstacles.

The good news is that the new obstacle positions are being created with the old being discarded.


#4

Sorry, I misunderstood your code. Would suggest something like this, with two separate loops to detect overlaps and then to draw the obstacles:

# first check for overlaps:
for pos in obs_pos: # all x,y coordinates of obstacle positions
    obs_circle.pos = pos
    if me_circle.overlaps(obs_circle): # if I interact with any of the obstacles
        me_y = -600 # put my character back to start
        obs_pos = new_positions() # would be better if this function explicitly returned the 
        # positions: at the moment its result is unclear.
        break # terminate this loop on first overlap

# now draw the stimuli, whether at previous or updated positions:
for pos in obs_pos:
    obs_circle.pos = pos
    obstacle_circle.draw

me_circle.pos[1] = me_y
me_circle.draw()

win.flip()

I’ve also suggested using more “Pythonic” style loops, directly iterating over objects in lists rather than indirectly accessing them by iterating over a list of indices. I suspect that shows some pre-existing experience with another programming language(s)? It takes a while to break those habits…


#5

It worked! Thanks for your patience and advice, Michael.

I don’t have any experience with other object-oriented programming, so my gross habits are a result of ignorance and lack of exposure (at least, I hope!).


#6

@Michael

I’m also having an issue with a counter I added. Every time I collide with an obstacle, I only want the counter to add 1 to its original count (I’ve set counter = 300 earlier in the function). It’s interesting because every time my character hits the obstacle, the counter adds 10 instead of 1 (due to the radius of the obstacle and character both being 10… I tested this by changing the radii). Is there a simple solution to made the counter only add 1, instead of 10?

while True:
    for pos in obs_pos: # all x,y coordinates of obstacle positions
        obs_circle.pos = pos 
        if condition == 'A' and me_circle.overlaps(obs_circle): 
            counter += 1
            break

    # now draw the stimuli, whether at previous or updated positions:
    for pos in obs_pos:
       obs_circle.pos = pos
       obs_circle.draw()

    if me_y == 350:
        obs_pos = new_positions() # only get new positions when character /
                                  # reaches top of screen
        me_y = -350
        

#7

In your newly-posted code posted above, no action is taken upon detecting an overlap (except incrementing the counter). So on the next iteration of the loop, assuming the character stimulus continues moving in a straight line, then there will continue to be overlaps detected within the same overall encounter. e.g. if the character and obstacle are each circles of diameter 10 pixels, and the character moves on a straight line connecting the centres of each stimulus at 1 pixel per screen refresh, then an overlap would be detected on I think 20 (or 19?) consecutive frames until the two circles no longer have an overlap.

This didn’t cause a problem before, as you changed the stimulus locations immediately upon detecting an overlap. If you want to avoid multiple overlap detections, then you need to have a variable for each obstacle that maintains state across iterations of the drawing loop. i.e. to only increment the counter when the character first overlaps the stimulus, you need to know that this is the first overlap in this current encounter.

I’d suggest you increase the information you hold about each obstacle. At the moment, it is just a pair of [x, y] coordinates. You could bind that together with a variable to flag whether the obstacle is currently overlapping the character, by putting both pieces on information together into a dictionary, with default values like this:

obs_info = dict(pos = [0,0], overlapping = False)

Then you would work through the list of dictionaries just like the list of coordinates before, but now you can associate an obstacle at a specific location with whether it has already been overlapped:

while True:
    for obs in obs_info: # all info dictionaries for each obstacle
        obs_circle.pos = obs['pos'] 
        if me_circle.overlaps(obs_circle):
            if condition == 'A':
                # only increment when overlap first detected:
                if not obs['overlapping']: 
                    obs['overlapping'] = True # record that an overlap has begun
                    counter += 1
        else:
            obs['overlapping'] = False

Note that we don’t use break anymore, as this loop needs to iterate over every obstacle now, and not just stop on the first overlap detected, as it is now necessary to reset the 'overlapping' property to False as required.


#9

So to reiterate, I create a copy of the existing dictionary and add it to a list. The number of dictionaries within the list should reflect the number of coordinate pairs made


#10

Hi Hanna,

I’m not sure how you generate your coordinates initially, but let’s say you have them already in the list called obs_pos, then you could generate a list of dictionaries from them like this:

obs_info = []
for position in obs_pos:
    obs_info.append(dict(pos = position, overlapping = False))

A more concise way of doing the above operation in Python is by using a “list comprehension”, to create and populate the new list in one expression:

obs_info = [dict(pos = position, overlapping = False) for position in obs_pos]

Dictionaries can also be created directly using curly bracket notation rather than the dict() function:

obs_info = [{'pos': position, 'overlapping':False} for position in obs_pos]

#11

Hi Michael!

This is very helpful. The list comprehension is way more concise than what I wrote up in the meantime… the learning curve is flattening :rofl:

Thank you!