Many people on the forum use Python; It’s lightweight, versatile and reasonably easy to pick up as programming languages go. As users of the Game Engine learn more about its inner workings, they are often tempted (and rightly so) to pick up the power that the API can provide.
However, this has its pitfalls.
After progamming for a couple of years, from the basics to the complex objects, I have learnt many lessons. It became apparent to me a short while back that the biggest problems I faced within the engine were mostly derived from poorly constructed code.
In order to highlight my own issues, please allow me to list and correct the most common mistakes.
- Hard coded variables, objects names and parameters
- Linear processing (avoiding objects or methods)
- Inconsistent naming conventions
- Inefficient function calls
In the beginning, most users learn Python because there is a certain task, such as a complex Inventory, that would be far easier to programme than to hard code in Logic Bricks.
As they are introduced into the Python workflow, they often omit the core documentation about the Game Engine Workflow, which they are unlikely to have experienced following the Logic Brick route.
The most typical example is when sample code is provided to access objects in the scene - they will see
scene = bge.logic.getCurrentScene()
without understanding how that actually comes to be. They derive objects from a list of objects in the scene - showing how objects belong to a scene, but they often do not learn this until later on. Therefore, we often see hard-coded object references in scripts of beginners, because that is presented as the only way of accessing objects. The user does not often understand the concept of objects represented as Python objects, and references stored on Children, Parents and so forth.
The same sits for parameters. Hard coded parameters often crop up because they are seen as the only solution, maybe because they are the first solution presented to a problem.
It is important that we correct this. In some cases, hard coding object names will be inevitable as a Python script will at some point need to know about its own scene. However, there are ways of doing this. Always think about your script in a global scope - what is vitally important to stay constant in the script, and what can change depending on usage scenarios?
For the former, hard coding object names may be necessary, but one can use object variables for that, accessed from the script controller’s owner. For the latter, the term “dynamic” applies - enabling a script to traverse object relations and perform calculations using functions which you can pass arguments, removing all elements of defining everything.
We can access variables from an objects using dictionary access. However, often a variable may not exist if the user is sharing code with another user, who hasn’t set up correctly. At this point, you need to consider its value. If the variable is vital to the running of the code, like the name of an object in the scene, then you want the script to fail and display an error. However, if the variable was just a parameter, like the speed of an object, you can often assume constants. Here are the two usage cases in code:
import bge cont = bge.logic.getCurrentController() own = cont.owner # 1) Variable is crucial to script's operation: try: variable = own['variable'] except KeyError: print('The variable did not exist') bge.logic.endGame() # 2) Variable can be set to a default value variable = own.get('variable', 100)
1) The first example expects the variable to exist. If it does not, it fails, and calls an Exception, and thus the interpreter prints the error message to the console, and the game ends “gracefully”, with one error frame.
2) The second example doesn’t mind if the variable doesn’t exist; it just carries on with a default variable, silently to the user.
Many beginners in python are shown a limited scope. By this, I mean that is unlikely that they will have been shown complex examples of any code, because they wouldn’t have been able to understand it, and so everyone’s time would have been wasted. This typically means that they do not learn about Functions or Objects.
This isn’t always an issue. In many cases, they may not need to perform a complex task. However, one does see the odd example when a user has copied the same code block three times, changing one line or parameter.
In such a case as this, one would want to use a Function, for clarity and efficiency.
Here is an example of a user mistake:
import bge scene = bge.logic.getCurrentScene() object_1 = scene.objects['Cube'] object_2 = scene.objects['Cube.001'] object_3 = scene.objects['Cube.002'] x, y, z = object_1.worldPosition x += 1.0 object_1.worldPosition = [x, y, z] x, y, z = object_2.worldPosition x += 1.0 object_2.worldPosition = [x, y, z] x, y, z = object_3.worldPosition x += 1.0 object_3.worldPosition = [x, y, z]
Here the same code block has been repeated 3 times. The user could greatly simplify this with a function and a for loop:
import bge scene = bge.logic.getCurrentScene() object_1 = scene.objects['Cube'] object_2 = scene.objects['Cube.001'] object_3 = scene.objects['Cube.002'] def move_object(object): x, y, z = object.worldPosition x += 1.0 object.worldPosition = [x, y, z] for object in [object_1, object_2, object_3]: move_object(object)
Learning complex (or more complex) attributes to the language will greatly develop the users comprehension of the language, and open far more doors.
Inconsistent Naming Convention
This has to be one of the most irritating and destructive aspect of coding, in any language. Naming conventions enable different programmers to look at someone’s work, and work with it, understand it, and offer improvements.
Naming conventions also enable the programmer to look back at their code at a later date, and understand how it works, and feel pride in the work and time they’ve spent.
Many surveys have shown that the majority of time a programmer spends (for which i can vouch) is spent on debugging code, and not writing it. This means that one should take as much care as one can when programming.
Let me first show you an example of poorly refactored code:
import bge.logic as gl ow = gl.getCurrentController().owner ow['X'] = 1 sc = gl.getCurrentScene() sc.objects['Cube'].worldPosition.z += 0.1 sc = sc.objects['Cube'] sc.worldPosition.z-=0.1
The above code demonstrates two issues. Firstly, the variable names are too short. They don’t describe themselves aptly enough, especially to non-english speakers. Secondly. the variables are sometimes overwritten with completely different functions (sc becomes an object instead of a scene).
A third issue is that the variables are often inline rather than branched - multiple attributes on one line.
Here is some refactored code to a naming convention:
import bge.logic as logic cont = logic.getCurrentController() scene = logic.getCurrentScene() own = cont.owner cube = scene.objects['Cube'] cube.worldPosition.z += 0.1 cube.worldPosition.z -= 0.1
Now, some people may argue that even cont is too short to be an accurate description of its function, especially to non-native english speakers, however I have found that anything longer than cont (such as “controller”) is just a pain to write, and therefore “cont” is a compromise.
The first thing you ought to observe is that the second paragraph is “cleaner” or nicer looking. I would like to make an abstract opinion and argue that one’s opinion of one’s code shapes the quality and direction the code takes. If you don’t like how your code looks, you use it less effectively and give in more easily.
Naming conventions serve to solve this problem. Most of the user base for Python now use PEP#8, which is just one naming convention, though you don’t have to, you just need to make sure you pick one and stick to it.
For example, PEP#8 uses CamelCase for Classes, and lowercase_with_underscores for Functions.
Ever since I chose a naming convention, I enjoyed my code a lot more.
Inefficient Function Calls
Most of your time spent in Python will be writing the structure for code. In most cases, you shouldn’t need to worry about optimisation because the code is so light weight that it doesn’t cost that much more, and is not noticed if it is wasteful of resources. However, there will be times when you need to optimise code that you’ve written. Therefore, you need to know what is efficient.
In Python, the less the interpreter has to do, the more efficient.
Therefore, the below tips are of great relevance:
- If you use an attribute (or a part of a module) regularly, set a variable to point towards it, rather than continually call the attribute.
- When allowing for conditions, be sure to use the most efficient polling to save resources.
- Avoid heavy calculations that are unnessecary.