Simple Python syntax question:

Hi all!

I’ve started to seriously play with Python, and I’m actually liking it (whoa!;))! My current task is to make a charge shot for my game (which is going well).

At the minute I am writing everything out long hand and not defining things. I want to define things at the start but I cant get it to work, no error message is generated and I’m a little puzzled. Here is an example:



import bge

cont = bge.logic.getCurrentController()

scene = bge.logic.getCurrentScene()

own = cont.owner

prop = own["prop"]

if prop >= 2:

    prop = 0.0



The prop property should set to 0 when it reaches 2, but it does not.

If I write:



import bge

cont = bge.logic.getCurrentController()

scene = bge.logic.getCurrentScene()

own = cont.owner

if own["prop"] >= 2:

    own["prop"] = 0.0


the property this is linked to resets fine. What is wrong?

Cheers

Paul

It’s because you’re redefining prop as 0, because variables are not permanent, unless you use code to check whether they exist before modifying them.

It’s a bit complicated:

If You do:

mylist = somelist

and change some items in mylist, somelist is changed to because mylist is just a reference to somelist. However if You assign a new list to mylist then somelist is NOT changed - the reference in mylist is changed to the new list.

For ‘atomic’ values the assignment is by value and changing the variable do not change the original value, only the value of the new variable. That whats happens in Your case. Changing prop do NOT change own[‘prop’].

You have to explicitly say somewhere that own[‘prop’] = prop

If You do:

a = b
a = c

It’s safe to assume the later assignment do not change b, only a.

If You do:

a = b
a[‘x’] = c

Then the value of b probably change to.

So in the example, what would you change to make the (in my test case) timer reset to zero when it reaches 2?

we would use your second code block…or use a property sensor and a property actuator :wink:

Ah, cool! Thanks for the advice guys.

You would use the code that you posted afterwards.
Essentially, variables found on objects, such as object[“my_prop”] exist outside of your ‘session’
This is different to variables which you create in a python script, as each time the script is run, all the variables are created, and then destroyed when the script ends.
^^Just some info

In terms of your question, you’re using a variable: ‘prop’ to reference another variable, on the owner object. By using ‘prop = 0.0’ you’re then replacing the reference to the variable with a number, not accessing the reference directly.

To be exact: prop is newer a reference to own[‘prop’] - as own[‘prop’] is a float (atomic value) prop is assigned that value directly. For strings and other constant types python always use references but that we need not care about :slight_smile:

Local variables is a tad faster so if You use prop allot the first version is better and also slightly easier to read. If You going to use prop later in the script set both prop and own[‘prop’] in the if body - else just own[‘prop’].

But if You not using prop allot Your second code example is probably better.

to fix your first example simply add:


    own["prop"] = prop

after changing the variable prop - to update the property “prop” with the new value of variable prop.

Thanks for the insight to that- this is the code I am working on that inspired this question:


import bge

from bge import events

cont = bge.logic.getCurrentController()

scene = bge.logic.getCurrentScene()

own = cont.owner

key = bge.logic.keyboard.events

kbleft = key[events.LEFTARROWKEY]

own["charge_rate"] = 1

own["shot_power"] = own["shot_charge_time"]

####################################################

if kbleft == 2 and own["shot_charge_time"] < 100:
    
    #print ("CHARGING LASER")
    
    own["shot_charge_time"] = own["shot_charge_time"] + 1
    
#if own["shot_charge_time"] == 100:
    
    #print ("MAXIMUM POWER") 
    
if kbleft == 3 and own["shot_charge_time"] >= 0:

    print (own["shot_power"])
    
    own["battery_reserve"] = own["battery_reserve"] - own["shot_charge_time"]
    
if own["shot_charge_time"] > own["battery_reserve"]:
    
    own["shot_charge_time"] = own["battery_reserve"] 

if own["battery_reserve"]  <= 0:
    
    own["battery_reserve"] = 0 # stops this going below 0
   
    #print ("OUT OF ENERGY")
    
if kbleft == 0: 
    
    own["shot_charge_time"] = (0)


I wondered if there was a better way to write own[“xxxxxx”] statements. This is a bit off the topic, but how does this code look? This is my first go at doing something and although it works well, how could I improve it, or make it more concise?

The single most important thing You can do to speed it up is to define the logic that need to run every time as a function and call it as a module instead of a script. It’s probably everything belove ####… and possibly the line before to.

That way the first lines is run only once the first time and most important - python do only compile the code once.

If You don’t use some of the properties elsewhere in the game You may remove them and only have them as python variables in the module (using a module makes the module variables persistent for the game duration). But if You are to change there value in the function You need to declare them global in the function.

The tutorial part of the python documentation at python.org is a good read.

the code looks … confusing. I’m pretty sure it works, but would you know what it does next month?

As the code runs at every frame you really need to remove any unnecessary code


own["charge_rate"] = 1

can go away. It is not used and makes no sense at all. If you need it simply setup the value in the GUI.


own["shot_power"] = own["shot_charge_time"]

can go away. You use that for printing only. A simple variable would be enough but you do not need it in the code.
Edit: I see you want to use “shot_power” as input for shooting. Do not set it here. No one knows what this line of code means and why it is important. Set it at a later stage after you changed “shot_charge_time”.


kbleft = key[events.LEFTARROWKEY]

very inflexible name better call it chargeKey or fireKey or kbCharge or whatever. Think about what happens when you decide to use another key. The meaning should be the same - it is just another key.


scene = bge.logic.getCurrentScene()
key = bge.logic.keyboard.events

is not used anywhere. Remove it.


if kbleft == 2...

what is 2 ? … better use the constants from GameLogic KX_INPUT_JUST_ACTIVATED etc. With that the reader knows whats going on.
Beside that, I’m pretty sure you need to take care of KX_INPUT_ACTIVE and KX_INPUT_JUST_ACTIVATED. Both mean that a key is pressed. But only one can be active at the time.

My suggestion:


from bge import events
from bge.logic import KX_INPUT_NONE, KX_INPUT_JUST_ACTIVATED, KX_INPUT_ACTIVE, KX_INPUT_JUST_RELEASED
cont = bge.logic.getCurrentController()
own = cont.owner
kbCharge = key[events.LEFTARROWKEY] # a keyboard sensor (with true pulse) would be much better here
 
####################################################
if isHold(kbCharge):
  countTime(own)
  return
 
if isJustReleased(kbCharge):
  loadPowerFromBattery(own)
  return
 
 
#--- internal
def countTime(own):
  chargedTime = own["shot_charge_time"]
  if chargedTime >= 100:
    return 
  own["shot_charge_time"] = chargedTime + 1
 
def loadPowerFromBattery(own):
  chargedTime = own["shot_charge_time"]
  if chargedTime <= 0:
    return
 
  chargedPower = convertTimeToPower(chargeTime)
  own["shot_charge_time"] = 0 
 
  reserve = own["battery_reserve"]
  if reserve < chargedPower:
     chargedPower = reserve
 
  own["shot_power"] = chargedPower  
  own["battery_reserve"] = reserve - chargedPower
 
def convertTimeToPower(chargeTime):
  return chargeTime
 
def isJustReleased(event):
  kbCharge == KX_INPUT_JUST_RELEASED
 
def isHold(event):
 return ( 
    event == KX_INPUT_JUST_ACTIVATED or 
    event == KX_INPUT_ACTIVE )

You might want to deside that time is not equal to power. You can do that by changing the method convertTimeToPower(time).

Improvements:
For more efficiency I suggest to use a keyboard sensor with True pulse (no False pulse). There is really no reason to run this code all the time.

For much more efficiency you can give the time counting away to a timer property. Simply reset it to zero when the charge key is pressed (no pulses). When the charge key is released the timer property contains the time in seconds since pressing. You can convert this time to power again with convertTimeToPower(). Run the code just once!.

Thanks Lah and Monster. Obviously there is writing a script that works and writing a script that works efficently!

In my own defence Monster, I should have posted the script after I had commented it- from the little I know the best advice I was ever given was to always annotate (so you never forget!)- but for pretty much everything you were correct. This test was on a simple cube and I used a random key (which would have been changed later when the code was intergrated into my project).

And I realise now that trying to do everything in Python is not always the best way- that tip for using keyboard sensors is an obvious thing that I missed.

I dont know what to think now, for a minute I thought I could almost write Python- back to the drawing board!

You can write Python - you wrote something that worked. That’s nothing to sneeze at. People code for days sometimes trying to get something like that to work, and you did it. Good job.

I agree with SolarLune. You produced a working version. That is very good. The performance differences to other solutions are mostlikely not noticable at this stage.

It is not that easy to know the how the BGE logic works. A good way to get that is what you did, you asked. I think your questions are really good for that. You showed us what you already know and where you need some improvements. That enabled us to help you at the right point (rather than wild guessing :wink: ).

Use the right tools at the right time. Sometimes it is better to use Python, sometimes it is better to use logic bricks. Most of the time, it is better to use a mix. I think there is very good reason, why the Python controller is part of the BGE logic and not something complete separate. Ther eis really not need to replicate the BGE logic with Python. The difficulty is to judge what to use when. And to judge when a solution is sufficiant enough.

Anyway, your working version enables you to decide it is good enough and you can continue on other things. You might decide to come back later and replace it with another (maybe better) version. To decide which version is better is -again- your decision ;).

Monster

Thanks for the words of encouragement!

Hi again!

I am changing the script above as per Monsters suggestion to use a keyboard input rather than a wasteful always sensor. At the minute I am trying to go the simplest route that I can understand but I am still in a muddle. Take this code:



import bge

from bge import events

cont = bge.logic.getCurrentController()

own = cont.owner

sensor = cont.sensors["Keyboard"]

sensor.key = bge.events.LEFTARROWKEY

kbcharge = sensor.key

####################################################

for sensor.key,status in sensor.events:
    
    if kbcharge == 2:  #if the key is held down 
        print ("CHARGING LASER") 

    if kbcharge == 0:  #if the key is not pressed 
        print ("INACTIVE")


I know this code is wrong, but I cannot visualize how to get an input from an outside sensor. I assume



sensor = cont.sensors["Keyboard"]


tells Python to look at the keyboard sensor attached, but am I mixing something up? Are keyboard variables like ==0,==2 etc the same as KX_INPUT_NONE etc? From what I can tell, things are going wrong here:



kbcharge = sensor.key  

 for sensor.key,status in sensor.events:


How would this code be changed to get it to use sensor input properly? Also (related to this)- is it possible for keyboard sensors to register keys held down?

Thanks for your patience!

Paul

You can still use monster’s code, just use a keyboard sensor as a trigger.

Look at the API: http://www.blender.org/documentation/249PythonDoc/GE/GameTypes.SCA_KeyboardSensor-class.html

you can simply use sensor.positive


kbCharge = cont.sensors["Keyboard"] 
# A better name of the sensor would be "chargeKey" or "kbCharge" 
# The real key to listen to is configured in the GUI. 
# Here you do not need to worry the key.
# You could even setup a mouse or joystick sensor without code change.
 
...
if kbCharge.positive:
  # the key is pressed
  countTime(own)
  return
 
# the key is not pressed 
loadPowerFromBattery(own)
...

Excellent! That API reference is gold! In the end I managed to get the keyboard sensor working with KX_INPUT_ACTIVE etc.

Thanks for all the help!