Strange python problem in BGE

Hi all!

So I’m making an RTS unit system and I’m working on code for a building to produce units.

There are 3 objects. On layer 1 is “building” and on layer 2 is “unit” and “navunit”

all 3 have the property(int) “number” which starts at zero

unit has the property(bool) “selected” which starts at false but is set to true if the unit is clicked

unit always steers to navunit.

Here’s the code for the building when the ‘A’ key is pressed:

from bge import logic as g

#Defining variables
scene = g.getCurrentScene()
object = g.getCurrentScene().objects
cont = g.getCurrentController()
owner = cont.owner

#Declaring more variables
number = owner["number"]

number = number + 1

#Adding the objects
unitname = scene.addObject("unit", owner)
navname = scene.addObject("navunit", owner)

#Setting the navigation
unitname.actuators["Steering"].target = navname

#Setting properties
unitname["number"] = number
navname["number"] = number

this is the code for the navunit.
Mouse is “mouse over any” and Click is left button. Both are connected to the script.

from bge import logic as g

#Defining variables
objects = g.getCurrentScene().objects
cont = g.getCurrentController()
owner = cont.owner

#Getting sensors
Mouse = cont.sensors["Mouse"]
Click = cont.sensors["Click"]

number = owner["number"]
hitPos = Mouse.hitPosition

#find associated unit
for object in objects:
    if "selected" in object:
        if object["number"] == number:
            unit = object
#Move when user clicks
if Click.positive == True:
    if unit["selected"] == True:
        owner.position = (hitPos[0],hitPos[1],0.5)

The code doesn’t come up with any errors in the console. But when I run the game engine I get a weird problem.
When I press “A”, instead of making one unit it makes 2, with only one selectable. It makes one navunit though.

If I press “A” more than once, it still produces 2 units every time. But if I try to select a unit made later, then it removes the first navunit.

I’m attaching the blend so you can take a look if you want:

problematicCode.blend (478 KB)

Any help would be much appreciated,

Thanks guys! :slight_smile:

Scripts actually run twice. Once when the sensor is positive and again when it is negative. Create a reference to the sensor and then check if it is positive.

a_key = own.sensors["A"]

if a_key.positive:
    "Your code here"

Or if you don’t like the indentation you can write:

if not cont.sensors['Click'].positive:
# write your code here

Thanks guys! That code worked perfectly to solve the problem of making 2 units instead of 1

However I still seem to have the problem that all units will follow the same navunit, and only the first unit created seems to be selectable :S

Is this because the addObject command doesn’t give the created objects unique identities? That might explain it, but I can’t see how that would work… I gotta keep experimenting I guess! :stuck_out_tongue:

You’ve got it. Names are not unique. But they are still identifiable. You simply have to identify them by things like: Properties, if it’s the object under the mouse etc.

The double execution of a python controller is explained already and solved already.

Your “navigation” problem is that you forgot to implement a deselection method.
This means all navunit (maybe better called target) are selected (after clicking the according unit) and follow at the same hit position of the mouse.

Solution: implement a way to deselect unwanted targets

I just see you have a deselection.

I had a look at your “navigation”. (It is an irritating name because it does not navigate. I suggest setTarget or similar as that is what it should do. )

The way this code identifies selected objects is a bit strange. It loops other all objects but does not deal with all of the identified "unit"s - just with one of them.

This code runs at each “navunit” but it deals with a complete different object.

Option A) Run the code at “unit”. “unit” knows the related “navunit” as it is setup in the actuator’s target. The “unit” can tell the “navunit” when it should react on mouse input or not. E.g. if “unit” is selected - “navunit” reacts on mouse otherwise it does not.

Option B) Have a single object that deals with the mouse input and distributes the hitPosition to all “navunit” when the according “unit” is selected.

The current code is a mix of both.

In general, the combination “unit”/“navunit” can’t live on it’s own, as it needs to be setup by the builder.
It might be better to redesign it in a way that the builder is only needed to assign the mentioned “number” to the “unit”. The objects itself should not need them to be fully functional.

Just an idea

There are some confusing names in your code. Here is a suggestion to solve that:

addObject returns the object reference not the object name. Naming the variable “…name” is irritating.

KX_Scene.objects returns multiple objects. As you store it in a variable called “object” it is irritating too:
A) this name indicates it is a single object
B) “object” is a build-in class (but this is not that important in this situation
C) it is not used anywhere so you can skip it

I do not know what you try to do with number. The current code will distribute the same number to all added objects. This is because the property “number” never changes at the builder. You might need something like this:

def retrieveNextNumber():
    builder = getOwner()
    nextNumber = builder.get("number",0) + 1
    builder["number"] = nextNumber
    return nextNumber

Thanks again all.

It sounds like the problem is that the steering actuator is not being pointed in the right place from what I can gather.


Sorry about some of the names! I don’t generally code with other people so I can be a bit sloppy on what I call things. Apologies for the confusion there.

What you said about each created object having the same number,
Actually the code increments the builder’s number before copying that value to the number of the unit and the navunit.
The reason for this was so that the navunit can find it’s associated unit by searching for the object that has the same number as it.
Also hence the loop to find the selected unit.

RE the fact that the created objects don’t have unique identifiers,
is it possible to change the identifier of the objects? The steering actuator for the unit needs to point to it’s own associated navunit, but it can’t do that if the navunits don’t have unique callers.
Any suggestions on that front? :S

The navigation is fine already. The problem is that the incorrect “navunits” are moved around.

You do not need to change the identifier of an object. You already have a reference … you used it to configure target of the steering actuator (by the builder). The steering actuator belongs to “unit”. So “unit” can simply look into the actuator to find the assigned “navunit”. (Be aware the “navunit” does not know the unit).

If you need more confidence you can store cross references in properties. But I think this is not really necessary.

You can create unique numbers. Currently the builder does not store the last assigned number. So he dos not know what was assigned earlier. You can easily fix that by writing the last assigned number in the property “number” (see above snippet).

Do not worry to much about the naming. I mention that because incorrect naming can easily make the reader misunderstanding code. I suggest to write proper names even when you code on your own. First it prevents to develop a “dirty” style (which is very easy [look at me ;)]). and second, you will forget what you meant. E.g. if you are looking at your code after one or two months doing something else. Hey we have computers, they help us e.g. with copy and paste, code completion and such things.

okay, so here’s what I’ve come up with.

Instead of having code all over the place, it’s now only in the builder. The builder has 3 sensors:
-Keyboard, for the “A” key
-Mouse, which is mouse over any
-Click, which is mouse left button

They all connect to this python script:

from bge import logic as g

#Defining variables
scene = g.getCurrentScene()
objects = g.getCurrentScene().objects
cont = g.getCurrentController()
owner = cont.owner

#When user presses "A"
if owner.sensors["Keyboard"].positive == True:

    #Adding the unit
    newunit = scene.addObject("unit", owner)
#When user clicks anywhere
if owner.sensors["Click"].positive == True:
    #Creating the navpoint
    newnav = scene.addObject("navunit", owner)
    hitPos = owner.sensors["Mouse"].hitPosition
    newnav.position = (hitPos[0],hitPos[1],0.5)

    #find selected units and set their targets
    for object in objects:
        if "selected" in object:
            if object["selected"] == True:
                object.actuators["Steering"].target = newnav

Unfortunately it still doesn’t work.
Selected units do not steer to where I click :frowning:

Any ideas where I’ve gone wrong with the code?

You must learn to debug your code.

First see what it prints to the game console window (Window -> Toggle system console), are there errors?

You can also mark some of those properties for debugging so you see them at runtime plus include print(variable) lines to see what the script is doing and with what values.

Ah, thanks for that!
So i placed tactical print commands around the place.
The conclusion:
All the navunits are called the same thing. This means the units only ever track to the first one :S
Basically addObject doesn’t give created objects unique identifiers, hence they are all the same.

Don’t know how I’m going to get around that… :S

Well, do you actually need to keep the old navunit after you place the new one?

If you don’t, you can just endObject() it before creating a new one.

If you do, you need to replace the steering actuator with python that is able to distinguish between objects in other means besides the name. Usually addObject() objects that need to be distinguished have a property called for example “ID” which you set just after you’ve created it. Then you can use the ID to distinguish.

Yes, unfortunately ending the old one wouldn’t work.

is there a way to make the unit’s steering target be a property rather than the object ID? How is that done in python?
Actually, even better, is there a way to make the steering target be coordinates?

So your steering actuator handles turning and moving at the moment? You can write a python script to do those 2 things for you:

import bge

cont = bge.logic.getCurrentController()
own = cont.owner
scene = bge.logic.getCurrentScene()

if not "target" in own:
    own["target"] = None 
    #creates a "target" property for the object
    #you can assign this to give the unit a target

def MoveTo(loc): #takes a point [x, y, z] or KX_GameObject such that you get from addObject()
    delta = own.getVectTo(loc)  #gets a vector from current position to target position
    own.alignAxisToVect(delta[1], 1, 0.8)  #turns itself to face the target position on local Y axis

    own.applyMovement([0,0.2,0.0], True) #moves itself forward on local Y axis

if own["target"] != None:

oops, my bad. The code actually works. It was just collision with the building that was preventing it moving.
Sorted it now, thanks! :slight_smile: