Python script crit

Hi all,

I have been blundering along making a ship select using LaHs cool Flexicomp system. I think I have a decent setup now (i.e. it works (!), no error messages and potential errors are handled…well I think they are!- this is also my first proper go at module scripting too).

I have a niggling feeling that there could be a better way of doing things. Can someone take a look and give me some feedback?

Cheers

Paul

Attachments

Selection_New.blend (659 KB)

what are the controls…on mine out of the keys ASDW and UP,LEFT,RIGHT,DOWN,SPACE…“A” is the only key that does anything. And it makes a ship apprear in the center. A run through the keys again does nothing. “A” only recenters the ship. As if re adding it. B-2.61. I was just wondering if i dont know the controls.

Whoops! Sorry, the controls are simple- A B and C spawn a ship that has one of three shapes attached to it. Although nothing should happen, pressing down will delete the link and release the ‘payload’- but only if you edit the script and change the camera ‘on’ value to ‘false’ in the release payload section.

Just a quick look.

I agree with superflip.
What are the controls and what should happen?
I see you are checking A,B,C and <down>. What should they do?
[edit] already answered [/edit]

Should this logic deal with exactly one ship or a ship (design question)?

The first thing is there are 3 modules: MOD.py, MOD2.py MOD3.py
Why three of them?
What means MOD?

You set up sce (Sony Computer Entertainment ;)), cn (CN-tower of Toronto? :D) and ow (owls in space? :P) at the module level.
That is in general not a good idea. It means you can use this module for exactly one object.

Better update these references as soon as the code enters an entry point:


def Make(cont): #make what?
    owner = cont.owner
    scene = bge.logic.getCurrentScene()
    allObjects = scene.objects


It seems you have a very strong dependency on object names in your code. That makes it very inflexible.
To make your code re-usable you should avoid hard-coded object names.

There are better ways:

  • object name in a string property (n:1 relations)
  • objects with a specific property (1:n relations)
  • object parent (1:1)
  • object children (1:n)
  • logic brick owner (1:1)
  • object in logic brick parameter e.g. the object of an TrackTo actuator (1:1)
  • objects in logic brick arguments e.g. objectList of a Near sensor (1:n)

1:1 -> one object relates to exactly one other (e.g. one ship has one FPV-camera)
1:n -> one object relates to multiple others (e.g. one ship has multiple guns)
n:1 -> multiple objects relate to one object (e.g. multiple guns belong to one ship)

If you use hard-coded names you get n:1 but it is exactly one (singleton). e.g. You can’t have multiple ships each with multiple guns. it would make sense for a single player ship (if it never changes).

In you situation I think the best option is to use a property to define the player’s ship and the payload.

Hint: I define property names at the top of the module. This makes it much easier to change it later (if needed) at on central place. And it shows which properties are used within this module. It is not the best place as each entry point uses different properties.

This is my convention:

  • Properties start with Upper case ‘P’ + camel case name starting with lower case letter. example: PpropertyName
  • Variable names to be camel case name starting with lower case letter .Example: propertyName

PplayerShipName = "playerShip"
PpayloadType = "payload"

...
playerShipName = owner.get(PplayerShipName )
playerShip = scene.eddObjet(playerShipName, emitter)
...
payloadType = owner.get(PpayloadType )
payload = scene.addobject(payloadType, mountPount)
...

Your mount point is a singleton too. You can have only one in the whole scene. I do not know if that is what you want.
If you could describe the structure of your/a ship I think it would be easier to verify the design (of the game, the ship and the logic).

NEVER use except without a specific error!
Except should check for expected errors only (e.g. KeyError, AttributeError).
Otherwise you hide unexpected errors. This makes it nearly impossible to identify problems at the caught code.

The try block should be as small as possible. If possible with just one statement.
With multiple statements you can’t determine which one got the error. And the preceding statements are executed while the succeeding statements are skipped. This can easily lead to unwanted side effects.

In Release_payload() the code checks for the keyboard input.
This is not necessary as the keyboard sensor does this already for you. You just need to check if all sensors are positive (AND) or at least one (OR).

In Release_payload() the code checks for the keyboard input.

Thanks Monster, thats what I was after! Here is a little context for the script:

The script is for a select screen. Each emitter (there will be eight of them eventually) will create a ship (made up of several parts, each detaches according to damage/ impact force) plus a payload (which can be ejected during a race). Each ship will have three variations of payload (thus for now having three scripts- MOD(ule)) that create each version. This does mean that there will be 24 scripts (one for each iteration of ship/ payload combination) which is wasteful and thus this thread.

The scripts work by creating an object but checking if it first exists, if it does it is destroyed so repeated key presses do not generate extra ships. It is a direct copy of a blend I made using logic blocks in function. It is a little hardwired, but I have to be sure I kill the simulated 6dof joints cleanly otherwise this causes a Blender crash (which was in a previous thread).

Regarding try/except, would it be better to use an if/else plus a [if ob in ob] check?

And I swear from now on I will name my objects and variables properly!

No, that is not needed.
You can use:


try:
  myObject = objects["myObject"]
except KeyError:
  print ("oups no myObject")

if you want to check if an object has a specific attribute:


try:
  hitObject = sensor.hitObject
except AttributeError:
  print ("oups, this is a sensor without hitObject")

In the except statement you can enter the error you get when you see an error message in Python.
The important point is not to catch all errors. You catch only the errors you expect. For example if an expected object does not exist.
Sometime it is better to let the code fail rather than silently continue. Because caught errors can create followup errors. They are usually harder to identify as they are the result of earlier (hidden) problems.
For example an object was not found = variable not defined.
The next access to the variable results in another error and you wonder why.

The point with the names is, there is really no reason not to write full names. They work even with cryptic names, because the compiler does not care. But we have copy&paste and all kind of tools. Why not using them and make our code human readable :D.

If you really want to go into Python development I suggest to get a mature IDE (e.g. PyDev) this allows code completion including variable completion. So you do not need to (mis-)type that much. They offer some other benefits I do not want to discuss here ;).

I will have a look into your design. It think there is a great potential for improvement. But first I need to draw a sketch.

You can check the sketch if you want

Thanks Monster. I just realised, back (nearly half a year ago) you helped me with a similar problem (using messages). Here it is below (and not to waste your time- sorry for being so thick!:o)


import GameLogic

#--- internal Properties
Pship = "_ship"

#--- BGE callable (Module mode!)
def switchShip(cont):
    if not onePositive(cont):
        return
    
    own = cont.owner
    skinNames = getMsgBodies(cont)

    deleteShip(own)

    if not skinNames:
        return
    
    skinName = skinNames[0] # only one skin needed
    addShip(own, skinName)

#--- internal    
def onePositive(cont):
    for sensor in cont.sensors:
        if sensor.positive:
            return True
    return False

def getMsgBodies(cont):
    bodies = []
    for sensor in cont.sensors:
        try:
            bodies += sensor.bodies
        except AttributeError:
            pass
        
    return bodies

def deleteShip(own):
    ship = own.get(Pship)
    if ship is None:
        return
        
    ship.endObject()
    own[Pship] = None  
    
def addShip(emitter, skinName):
    currentScene = GameLogic.getCurrentScene()
    emitter[Pship] = currentScene.addObject(skinName, emitter)

Now that I am familiar with a few more Python commands I can visualise how part of this works (the try/ except is clear now), but how I graft the 6dof joint code into this is still beyond me. From looking at my old file (using traditional parenting commands) I did it the 1:1 way and just used object names.

And thanks for your patience, I am trying my best to learn!

Cheers

Paul

Concept

You have multiple slots in your ship yard. Slots are identified by the property slot.

The player can select one of the slots (I have chosen left/right for cycling). The camera does the selection and tracks the selected camera. The selected slot gets the property “slot” set to True. The previous slot gets the property “slot” set to False.

Slot
You just need one slot as they all behave the same.
The easiest way is the define a group and instantiate the group.

The slot gets 3 states:
Init

  • switches to Add on <A>
    Add
  • adds a Ship type “Ship_1” (registers the new ship at the slot)
  • switches to Remove on <A>
    Remove
  • signals the current ship to end itself
  • switches to Add on <A>

Currently this seems to be the best setup but you might want to detail the behavior.

Ship Type “Ship_1”
Regardless of the ship type they should follow the same behavior.

first: levitation can be done with a motion actuator. This is much easier than a Python controller. I guess it would be easier to assemble a ship when the ship has no physics at all. For future releases I suggest to parent the ship to the slot to do that.

second:

  • when the ship’s property “end” gets True the ship ends itself. Currently a EndObject Actuator is enough as there is nothing more to do (currently)

Demo:
You can test this in the attached Demo.
Let me know if that matches what you want (at least to this part).

Future:

  1. you might want to cycle through the ship types. See throwables for a “munition selection” demo.
    How do you want select different ship types?

  2. The ship brings mount points. How should the user select a mount point?

  3. The mount point should equip itself (not the slot, not the ship)

  4. The mount points needs to be deactivated (remove constraints) before ending the ship.

  5. what should happen with the final ship?

Attachments

Shipyard.blend (421 KB)

Wow, thats amazing, and with so little Python!

As an idea of what I was building so long ago, here is an example: Now I do warn you, this will make you cry with pain its so disorganised :eek:but it illustrates what I was aiming for back then:

You have two large purple blocks. If you click the left one it spawns two grey blocks. The right purple block cancels all choices. Each spawn a different ship on left clicking. When these blocks are clicked, it spawns more cubes- by clicking one of the three you select a weapon (or no weapon). The last cube at the bottom switches to the race view, and there is a simple forward motion (W) with a primitive speed readout. To jettison the payload press the UP arrow. There is also a chase cam and in ship view (keys 1 and 2).

Now this would be duplicated seven times so the player can select what racers they are going to compete against. What I also intend to do is in fact have two emitters: one for the select screen (so the player sees what they are selecting), and another (for position on a track, ready for when the camera switches to the race).

Also, a little on needing a python script to make antigravity: a side effect of using 6dof rather than parent/child is that all objects now have mass. Before, with child objects, the (child) would lose its mass when parented (along with its collision bounds if compound is not set). Now with this system, the mass is taken into account (even in dynamic movement, I believe) and you need a corresponding upward force to counteract gravity. So for example, a ship of five parts weighs 5 units , plus its payload of 10 units = 15 x 9.81 upward force. Now, I could work this out and set each part up using a motion actuator, but I thought it might be easier would be a script that dynamically checks how many parts are still attached to the ship, so that if any bits fall off, they get affected by gravity and fall, but the ship remains in a state of neutral buoyancy.

Attachments

AttachQ2.blend (882 KB)