So lately I’ve been experimenting with my Game Engine called PyUnity, and I’ve been porting some things from the Unity GameEngine tutorials. As PyUnity is still WIP, there are a lot of unusable features (such as the half-finished physics engine). However, I have made the basics of the Roll A Ball tutorial. Here is the complete code:
This is an extremely long piece of code, isn’t it? Let’s break it down.
GameObjects and Transforms
A GameObject is an individual object in your Scene. It has a name, a tag and some components. All components will affect the GameObject and any other GameObjects. For example, a Transform component affects the GameObject’s size, position and rotation, while maintaining a hierarchical structure. To instantiate a GameObject, we call its constructor:
gameObject = GameObject("Object 1")
Logger.Log(gameObject.transform.position)
This creates a GameObject with the name “Object 1”. We then print its position, from gameObject.transform
. Note that we use Logger.Log
instead of print
, because it will show up in the PyUnity logs. gameObject.transform
is the Transform component and all GameObjects have one. It defines 8 properties, 4 local
properties and 4 non- local
properties. Local properties are relative to their parent, and non-local properties are relative to the world space. Transforms also define the hierarchical structure, with the parent
and children
properties.
Components
Components are what make GameObjects interactive. The Transform
component is just one of many others. For example, MeshRenderer
renders a mesh every frame at the location of the Transform
. A Light
component casts light on other objects. Here is an example of the MeshRenderer
:
cube = GameObject("Cube")ess
renderer = cube.AddComponent(MeshRenderer)
renderer.mesh = Mesh.cube(2)
renderer.mat = Material(RGB(255, 0, 0))
To add a component, call AddComponent
from the GameObject or any of its components. If I were to create a new renderer, I could use the old renderer to call AddComponent
:
renderer2 = renderer.AddComponent(MeshRenderer)
All components define the gameObject
property, which is the GameObject the component belongs to. Each Component will define some writable attributes, like MeshRenderer.mesh
. Above we created a cube mesh from the Mesh class, and also set its material to a bright red colour. However, sometimes we don’t always want to waste so much processing power on creating a mesh, so we can use one of 6 presets defined in Loader.Primitives
.
Behaviours
To create custom Components, subclass from Behaviour
. It has 2 main overridable methods, named Start
and Update
. Start
is called when the scene is started, so it is guaranteed to be able to find all GameObjects and access their properties. Update
takes one parameter, dt
, which is the time since the last frame. Behaviours also have another function which takes the same parameter, LateUpdate
. As a Behaviour is just a Component, you can access the same attributes as a Component:
class Debugger(Behaviour):
def Update(self, dt):
Logger.Log(self.transform.position, self.gameObject.name)
To add a Behaviour, add it just like a Component:
cube.AddComponent(Debugger)
Input
To take user input, we can use the Input
class. There are 2 preset axes, which are values from -1 to 1 depending on what keys we press. For example, the “Horizontal” axis will increase when either the right or D key is pressed and will decrease when either the left or A key is pressed. However, sometimes we don’t want a smooth interpolation between -1 and 1, and we would like to just get the raw input of an axis. This is when we can use GetRawAxis
:
class KeyLogger(Behaviour):
def Update(self, dt):
Logger.Log(
Input.GetAxis("Horizontal"),
Input.GetRawAxis("Horizontal")
)
To get the state of a single key, we can use GetKey
, GetKeyDown
and GetKeyUp
. GetKey
is triggered whenever the key is held down, GetKeyDown
is only triggered when the key was pressed down this frame and GetKeyUp
is triggered only when the key was released this frame.
Scenes
By default, a scene has two GameObjects: the Main Camera and a Light. The Main Camera is accessible from the mainCamera
property of the scene:
scene = SceneManager.AddScene("Scene")
scene.mainCamera.position = Vector3(0, 5, -10)
scene.mainCamera.eulerAngles = Vector3(20, 0, 0)
To get the Light, use scene.gameObjects[1]
. The Light
component has 3 attributes: intensity
, color
and type
. In our script, we make the intensity of our light 100
so that it can light up the entire box.
Main Code
Finally, the part that you are all waiting for! We first define two Behaviours. To help integrate Behaviours with the PyUnity editor, the way we define attributes that can be edited in the Editor is using ShowInInspector
. This class will be detected when we subclass Behaviour
and will be replaced by the default value that we want to give it. The Start
function is called as soon as the scene starts, so in the Behaviour CameraController
, we can access the other
component and its transform.position
to get the offset.
A Rigidbody makes an object move. For a Rigidbody to work, it must have Colliders that define its collision box, which is what SphereCollider
and AABBoxCollider
do. A PhysicMaterial
has two properties, restitution
and friction
. For now, we make friction
0 so that our ball will keep rolling along.
For an example of using ShowInInspector
and HideInInspector
, here is the Python code:
class CustomScript(Behaviour):
editorProperty = ShowInInspector(GameObject)
hiddenProperty = HideInInspector(int, 5)
privateProperty = True
And the C# code that would be used in Unity:
public class CustomScript : MonoBehaviour {
public GameObject editorProperty;
[HideInInspector] private int hiddenProperty = 5;
private bool privateProperty = true;
}
Summary
If you ran the code, you may notice that the ball doesn’t rotate. As of now (version 0.8.2), the PyUnity physics engine has no rotation physics, but it won’t matter much for now because a rotating ball and a non-rotating ball look virtually the same. Here is a picture of what it looks like:
If you have any improvements, feel free to share! Here is the main repository for PyUnity: https://github.com/pyunity/pyunity