Another Assertion Error

I have an issue while coding a game. I am setting up classes so it is easier and more organized to acces certain variables. I put instances of those classes in arrays, because I want to have multiple different Instancess of those classes. The game runs fine with one instance, but as soon as I add another (which in my case looked almost identical), an error occurs:
AssertionError: !is_empty() at line 1045 of panda/src/pgraph/nodePath.cxx
And now I dont know what to do. Ive searched for a few solutions, but I didnt really understand what was meant (Something with nodepath?). Although I am not a complete beginner in coding, this error has set a major roadblock in my path.

In short, this indicates I believe that the code attempted to do something with a NodePath that, for whatever reason, had no associated node. It may be that the node was removed, for example.

As to this problem specifically, are you perhaps calling “removeNode” on any of the NodePaths your array, but without then removing those NodePaths from the array?

If not, it might perhaps help to see more of your code–specifically, anything that interacts with your array, whether it be adding, removing, or operating on things within said array.

Ok now that I look at it the error is in another array, not the original i mentioned, but i think i see what you mean. I am removing stuff from that array, maybe thats the problem? if so i dont know how to code it diffrently. I am trying to generate objects, save them in an array as well as saving a few other variables in other arrays with the same index as the generated object, then removing them noce they hit something. Currently i am moving each generated object in the ‘objects array’ one by one (in a for loop) and checking, wether they hit something. If so, i add the index of that object to another array (to later remove them) and once each object was moved (and checked), I check if there are any indexes in the ‘remove later array’ and if so, i go through each index and remove the object (and its elements form the other arrays) with that index from the ‘object list’.

Here is the code (it is in the update() method)

  #for as many elements that are in the bullets list
  for i in range(len(bullets)):
            #move the bullets (move is a custom function)
	move(bullets[i], bulletDirs[i], BULLETSPEED, 1)
	origin = bullets[i].position
            #checking from who the bullet is
	if(bulletOrigins[i] == 2):
                    checking for any collision
		ray = raycast(origin, (0, 0, 90), ignore=[bullets[i],player2,], distance=0.5, debug=False)
		if(ray.hit and ray.entity == player1):
			damageP(1, bulletDamage[i])
                            #add index to removeBullets (bullets that have to removed)
			cacheMisc.removeBullets.append(i)
	else:
		ray = raycast(origin, (0, 0, 90), ignore=[bullets[i],player1,], distance=0.5, debug=False)
		if(ray.hit and ray.entity == player2):
			damageP(2, bulletDamage[i])
			cacheMisc.removeBullets.append(i)
    
    #after moving bullets remove collided ones
if(len(cacheMisc.removeBullets) > 0):
         #for as many elements in the removeBullets (array with the indexes)
	for i in range(len(cacheMisc.removeBullets)):
		#remove bullets (and other variables) from arrays
		destroy(bullets[i])
		bullets.remove(bullets[cacheMisc.removeBullets[i]])
		bulletDirs.remove(bulletDirs[cacheMisc.removeBullets[i]])
		bulletOrigins.remove(bulletOrigins[cacheMisc.removeBullets[i]])
		bulletDamage.remove(bulletDamage[cacheMisc.removeBullets[i]])
		cacheMisc.removeBullets.remove(cacheMisc.removeBullets[i])

The error pointed towards the move() function specificly when i modify the position. I think that, due to there being no more object to move it threw that error

So I did a few tests. If I only shoot at the player, it works. But if I shoot around (not at the player/ outside of the map since I have nothing stopping them from going “out of bounds”), then it happens

This may not be related, but let me add a note of caution here:

If you have more than one index to remove in a given frame, the removal of the first index will presumably invalidate all of the subsequent indices, meaning that you may well remove the wrong objects. (Or, indeed, attempt to remove an object beyond the end of the array.)

To illustrate, let me construct an example:

Let’s say that we start with this array:

array---->["cat", "mew", "purr", "meow"]
indices-->  0       1      2        3

We then find that “mew” and “purr” are both to be removed, so we store their indices: 1 and 2.

Now, we iterate over those stored indices. Removing the first index, 1, results in this:

array---->["cat", "mew", "purr", "meow"]
indices-->  0     **1**    2        3

Remove item at index 1:

array---->["cat", "purr", "meow"]
indices-->  0       1        2

However, since the object at index 1 was removed, the contents of the array have changed, and so the object at the next stored index, 2, is “meow”, not “purr”. As a result, the code ends up removing the former rather than the latter.

And indeed, looking at your code, this may be the source of your problem: You may be destroying bullets that are still valid, and leaving in your arrays bullets that are no longer valid.

Hmm… What I’d suggest is what Python calls “list comprehensions”. This allows one to construct a list (or other such thing) in a single line, based on an existing list or group of lists.

Before I get to that, let me note an alternative way of constructing a “for-loop” in Python. For one thing, the list comprehension stuff might be easier to follow if you know about it, and for another, it can be useful.

~

For-Loops

So, from what I see you know how to construct an index-based “for-loop”, like so:

for i in range(someRange):
    # Do something

However, if you’re drawing from a list or other iterable thing (a tuple, a dictionary, etc.), you can also do this:

# An arbitrary list for purposes of the example:
myList = ["cat", "mew", "purr"]

# And now, iterate over it:
for thing in myList:
    # "thing" here--and that name is arbitrary--ends up holding
    # each item in "myList" in turn--first "cat", then "mew",
    # then "purr"--as the loop iterates.
    print (thing)

Which should print the following:

cat
mew
purr

So, with that said, on to list comprehensions!

~

List Comprehensions

The basic form looks like this:

newList = [thing for thing in oldList]

Note the section starting with “for”–that amounts to much the same as the “for-loop” that I showed above, I do believe.

The result, then, is a new list containing the contents of the old list.

Now, you can also add conditions to the comprehension, to determine which items are added. These are placed after the “for-loop” section, and reference it. Like so, for example:

newList = [thing for thing in oldList if len(thing) > 0]

This, then, will only place a given “thing” into the new list if that “thing” has a length greater than zero.

Now, there are more complexities–you can have multiple “for-loops”, or nested “things”. And I recommend that you look into some what can be done with list comprehensions–they’re useful, I do find! But for our current purposes, the above should do.

But one more thing: This doesn’t assign the new list to the variable on the left of the line of code until the new list has been completed. That means that, if that variable is the same as the one being referenced in the comprehension, the content of the variable–the list–isn’t changed until the work is completed. This is useful for our current purposes, I feel!

So, on to using this in your case:

~

Using a List Comprehension

In your case, you have a list of indices to remove. So, what we can perhaps do is create a comprehension that includes only the objects that don’t match those indices. That is, create a comprehension that makes a new list that contains only the objects that we want to keep. Something like this:
(Noting that I haven’t tested the following code.)

bullets = [bullets[i] for i in range(len(bullets)) if i not in cacheMisc.removeBullets]

What this is intended to do is as follows:

  • It iterates over the indices of the bullets
  • For each one, it checks whether that index is in the list named “cacheMisc.removeBullets”
    • If so, it ignores it
    • If not, it includes it in the comprehension
  • Once all is done, it replaces the old “bullets”-list with the new one created by the comprehension
1 Like

Thank you a lot.
This will definetly be useful to know. Although I havent tried any of it yet, I am thankful you took your time explaining it.

1 Like

It’s my pleasure! I hope that it proves helpful! :slight_smile:

Hmm. So Ive typed in your solution and python sees the reiteration as a declaration of a local variable, and with that previos lines of code throw an error telling me that i define a given variable later (locally), although I defined them at the very start of the entire script

Ah–where then does the “bullets” list come from? I don’t see its initial declaration in the code that you posted.

It is at the top outside the Update() function, where you would normally define them (so like after the import and class methods)

Do I have to add something to the first definition (up top) or is “bullets = [ ]” already enaugh?

Ah, but they’re not instance-variables, however? I note that they’re not prepended with “self.”…

As a result, indeed, declaring them as I described above might result in their being “shadowed” by a new local variable in that name.

I’m not really familiar with declaring variables in the way that you describe, if I’m following you correctly–I usually declare them as instance (or occasionally class) variables. I’m not sure of how to get around the issue there aside from, well, making them instance or class variables, allowing identification via the “self”-reference.

That makes it a global variable.

There are two ways you can deal with this:

  1. let Python know it’s a global variable by using the global keyword; at the top inside the Update function, before the line where you want to alter its value, write this line:
        global bullets
    
  2. (my preferred solution) just use slice assignment:
        bullets[:] = [b for i, b in enumerate(bullets) if i not in cacheMisc.removeBullets]
    
    (note that I use enumeration here as an alternative to Thaumaturge’s code).

Hope this helps!

1 Like

Ok ill try that

1 Like

so I tried the “global bullets” thing in the update method and it works!!

1 Like