"""
Core classes for the PyUnity library.
This module has some key classes used throughout PyUnity, and
have to be in the same file due to references both ways. Usually
when you create a scene, you should never create Components
directly, instead add them with AddComponent.
Example
-------
To create a GameObject with 2 children, one of which has its own child,
and all have MeshRenderers:
>>> from pyunity import * # Import
Loaded config
Trying GLFW as a window provider
GLFW doesn't work, trying PySDL2
Trying PySDL2 as a window provider
Using window provider PySDL2
Loaded PyUnity version 0.8.4
>>> mat = Material(RGB(255, 0, 0)) # Create a default material
>>> root = GameObject("Root") # Create a root GameObjects
>>> child1 = GameObject("Child1", root) # Create a child
>>> child1.transform.localPosition = Vector3(-2, 0, 0) # Move the child
>>> renderer = child1.AddComponent(MeshRenderer) # Add a renderer
>>> renderer.mat = mat # Add a material
>>> renderer.mesh = Mesh.cube(2) # Add a mesh
>>> child2 = GameObject("Child2", root) # Create another child
>>> renderer = child2.AddComponent(MeshRenderer) # Add a renderer
>>> renderer.mat = mat # Add a material
>>> renderer.mesh = Mesh.quad(1) # Add a mesh
>>> grandchild = GameObject("Grandchild", child2) # Add a grandchild
>>> grandchild.transform.localPosition = Vector3(0, 5, 0) # Move the grandchild
>>> renderer = grandchild.AddComponent(MeshRenderer) # Add a renderer
>>> renderer.mat = mat # Add a material
>>> renderer.mesh = Mesh.cube(3) # Add a mesh
>>> root.transform.List() # List all GameObjects
/Root
/Root/Child1
/Root/Child2
/Root/Child2/Grandchild
>>> child1.components # List child1's components
[<Transform position=Vector3(-2, 0, 0) rotation=Quaternion(1, 0, 0, 0) scale=Vector3(1, 1, 1) path="/Root/Child1">, <pyunity.core.MeshRenderer object at 0x0A929460>]
>>> child2.transform.children # List child2's children
[<Transform position=Vector3(0, 5, 0) rotation=Quaternion(1, 0, 0, 0) scale=Vector3(1, 1, 1) path="/Root/Child2/Grandchild">]
"""
__all__ = ["Component", "GameObject", "Light", "SingleComponent",
"MeshRenderer", "Tag", "Transform", "ShowInInspector",
"HideInInspector", "LightType"]
import inspect
import enum
from .errors import *
from .meshes import Mesh
from .values import *
from . import Logger
[docs]class Tag:
"""
Class to group GameObjects together without referencing the tags.
Parameters
----------
tagNumOrName : str or int
Name or index of the tag
Raises
------
ValueError
If there is no tag name
IndexError
If there is no tag at the provided index
TypeError
If the argument is not a str or int
Attributes
----------
tagName : str
Tag name
tag : int
Tag index of the list of tags
"""
tags = ["Default"]
"""List of current tags"""
[docs] @classmethod
def AddTag(cls, name):
"""
Add a new tag to the tag list.
Parameters
----------
name : str
Name of the tag
Returns
-------
int
The tag index
"""
cls.tags.append(name)
return len(cls.tags) - 1
def __init__(self, tagNumOrName):
if type(tagNumOrName) is str:
self.tagName = tagNumOrName
self.tag = Tag.tags.index(tagNumOrName)
elif type(tagNumOrName) is int:
self.tag = tagNumOrName
self.tagName = Tag.tags[tagNumOrName]
else:
raise TypeError(f"Argument 1:"
f"expected str or int, got {type(tagNumOrName).__name__}")
[docs]class GameObject:
"""
Class to create a GameObject, which is an object with components.
Parameters
----------
name : str, optional
Name of GameObject
parent : GameObject or None
Parent of GameObject
Attributes
----------
name : str
Name of the GameObject
components : list
List of components
tag : Tag
Tag that the GameObject has (defaults to tag 0 or Default)
transform : Transform
Transform that belongs to the GameObject
"""
def __init__(self, name="GameObject", parent=None):
self.name = name
self.components = []
self.transform = None
self.AddComponent(Transform)
if parent:
self.transform.ReparentTo(parent.transform)
self.tag = Tag(0)
self.enabled = True
self.scene = None
[docs] @staticmethod
def BareObject(name="GameObject"):
"""
Create a bare GameObject with no components or attributes.
Parameters
==========
name : str
Name of the GameObject
"""
obj = GameObject.__new__(GameObject)
obj.name = name
obj.components = []
obj.transform = None
obj.scene = None
return obj
[docs] def AddComponent(self, componentClass):
"""
Adds a component to the GameObject.
If it is a transform, set
GameObject's transform to it.
Parameters
----------
componentClass : Component
Component to add. Must inherit from :class:`Component`
"""
if not issubclass(componentClass, Component):
raise ComponentException(
f"Cannot add {componentClass.__name__} to the GameObject; "
f"it is not a component"
)
if not (
issubclass(componentClass, SingleComponent) and
any(isinstance(component, componentClass) for component in self.components)):
component = componentClass(self.transform)
self.components.append(component)
if componentClass is Transform:
self.transform = component
elif issubclass(componentClass, Light):
if self.scene is not None:
self.scene.RegisterLight(component)
component.gameObject = self
component.transform = self.transform
return component
else:
raise ComponentException(
f"Cannot add {componentClass.__name__} to the GameObject; "
f"it already has one")
[docs] def GetComponent(self, componentClass):
"""
Gets a component from the GameObject.
Will return first match.
For all matches, use `GetComponents`.
Parameters
----------
componentClass : Component
Component to get. Must inherit from :class:`Component`
Returns
-------
Component or None
The specified component, or `None` if the component is not found
"""
for component in self.components:
if isinstance(component, componentClass):
return component
return None
[docs] def RemoveComponent(self, componentClass):
"""
Removes the first matching component from a GameObject.
Parameters
----------
componentClass : type
Component to remove
Raises
------
ComponentException
If the GameObject doesn't have the specified component
ComponentException
If the specified component is a Transform
"""
component = self.GetComponent(componentClass)
if component is None:
raise ComponentException(
f"Cannot remove {componentClass.__name__} from the GameObject; "
f"it doesn't have one")
if componentClass is Transform:
raise ComponentException(
"Cannot remove a Transform from a GameObject")
self.components.remove(component)
[docs] def GetComponents(self, componentClass):
"""
Gets all matching components from the GameObject.
Parameters
----------
componentClass : Component
Component to get. Must inherit from :class:`Component`
Returns
-------
list
A list of all matching components
"""
return [component for component in self.components if isinstance(component, componentClass)]
[docs] def RemoveComponents(self, componentClass):
"""
Removes all matching component from a GameObject.
Parameters
----------
componentClass : type
Component to remove
Raises
------
ComponentException
If the specified component is a Transform
"""
components = self.GetComponents(componentClass)
if componentClass is Transform:
raise ComponentException(
"Cannot remove a Transform from a GameObject")
for component in components:
self.components.remove(component)
def __repr__(self):
return (f"<GameObject name={self.name!r} components="
f"{list(map(lambda x: type(x).__name__, self.components))}>")
def __str__(self):
return (f"<GameObject name={self.name!r} components="
f"{list(map(lambda x: type(x).__name__, self.components))}>")
[docs]class HideInInspector:
"""
An attribute that should be saved when saving a project,
but not shown in the Inspector of the PyUnityEditor.
Attributes
==========
type : type
Type of the variable
default : Any
Default value (will be set to the Behaviour)
name : NoneType
None
"""
def __init__(self, type=None, default=None):
self.type = type
self.default = default
self.name = None
[docs]class ShowInInspector(HideInInspector):
"""
An attribute that should be saved when saving a project,
and shown in the Inspector of the PyUnityEditor.
Attributes
==========
type : type
Type of the variable
default : Any
Default value (will be set to the Behaviour)
name : str
Alternate name shown in the Inspector
"""
def __init__(self, type=None, default=None, name=None):
super(ShowInInspector, self).__init__(type, default)
self.name = name
[docs]class Component:
"""
Base class for built-in components.
Attributes
----------
gameObject : GameObject
GameObject that the component belongs to.
transform : Transform
Transform that the component belongs to.
"""
shown = {}
saved = {}
def __init__(self, transform, is_dummy=False):
if is_dummy:
self.gameObject = None
else:
self.gameObject = transform.gameObject
self.transform = transform
self.enabled = True
def __init_subclass__(cls):
members = inspect.getmembers(cls, lambda a: not inspect.isroutine(a))
variables = list(
filter(lambda a: not (a[0].startswith("__")), members))
shown = {a[0]: a[1]
for a in variables if isinstance(a[1], ShowInInspector)}
saved = {a[0]: a[1]
for a in variables if isinstance(a[1], HideInInspector)}
cls.shown = shown
cls.saved = saved
for name, val in saved.items():
if val.type is None:
val.type = cls
if val.name is None:
val.name = name
setattr(cls, name, val.default)
[docs] def AddComponent(self, component):
"""
Calls `AddComponent` on the component's GameObject.
Parameters
----------
component : Component
Component to add. Must inherit from :class:`Component`
"""
return self.gameObject.AddComponent(component)
[docs] def GetComponent(self, component):
"""
Calls `GetComponent` on the component's GameObject.
Parameters
----------
componentClass : Component
Component to get. Must inherit from :class:`Component`
"""
return self.gameObject.GetComponent(component)
[docs] def RemoveComponent(self, component):
"""
Calls `RemoveComponent` on the component's GameObject.
Parameters
----------
component : Component
Component to remove. Must inherit from :class:`Component`
"""
return self.gameObject.RemoveComponent(component)
[docs] def GetComponents(self, component):
"""
Calls `GetComponents` on the component's GameObject.
Parameters
----------
componentClass : Component
Component to get. Must inherit from :class:`Component`
"""
return self.gameObject.GetComponents(component)
[docs] def RemoveComponents(self, component):
"""
Calls `RemoveComponents` on the component's GameObject.
Parameters
----------
component : Component
Component to remove. Must inherit from :class:`Component`
"""
return self.gameObject.RemoveComponents(component)
@property
def scene(self):
"""Get either the scene of the GameObject or the current running scene."""
from .scenes import SceneManager
if self.gameObject.scene is None:
return SceneManager.CurrentScene()
else:
return self.gameObject.scene
[docs]class SingleComponent(Component):
"""
Represents a component that can be added only once.
"""
pass
[docs]class LightType(enum.IntEnum):
Point = 0
Directional = 1
Spot = 2
[docs]class Light(SingleComponent):
"""
Component to hold data about the light in a scene.
Attributes
----------
intensity : int
Intensity of light
color : Color
Light color (will mix with material color)
type : LightType
Type of light (currently only Point and
Directional are supported)
"""
intensity = ShowInInspector(int, 20)
color = ShowInInspector(Color, RGB(255, 255, 255))
type = ShowInInspector(LightType, LightType.Directional)
[docs]class MeshRenderer(SingleComponent):
"""
Component to render a mesh at the position of a transform.
Attributes
----------
mesh : Mesh
Mesh that the MeshRenderer will render.
mat : Material
Material to use for the mesh
"""
DefaultMaterial = Material(RGB(200, 200, 200))
DefaultMaterial.default = True
mesh = ShowInInspector(Mesh)
mat = ShowInInspector(Material, DefaultMaterial, "material")
[docs] def Render(self):
"""Render the mesh that the MeshRenderer has."""
if self.mesh is None:
return
self.mesh.compile()
self.mesh.draw()