"""
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.6.0
>>> mat = Material(Color(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", "Color", "Clock",
"Material", "MeshRenderer", "Tag", "Transform"]
import os
import glm
import sys
import time
from .vector3 import Vector3
from .quaternion import Quaternion
from .errors import *
from . import Logger
if os.environ["PYUNITY_INTERACTIVE"] == "1":
from OpenGL import GL as gl
[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(
"Argument 1: 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.AddComponent(Transform)
if parent:
self.transform.ReparentTo(parent.transform)
self.tag = Tag(0)
@staticmethod
def BareObject(name="GameObject"):
obj = GameObject.__new__(GameObject)
obj.name = name
obj.components = []
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 ``Component``
"""
if not issubclass(componentClass, Component):
raise ComponentException(
"Cannot add " + repr(componentClass.__name__) +
" to the GameObject; it is not a component"
)
if not (
issubclass(componentClass, SingleComponent) and
any(isinstance(component, componentClass) for component in self.components)):
component = componentClass()
self.components.append(component)
if componentClass is Transform:
self.transform = component
component.gameObject = self
component.transform = self.transform
return component
else:
raise ComponentException(
"Cannot add " + repr(componentClass.__name__) +
" to the GameObject; 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 ``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(
"Cannot remove " + componentClass.__name__ + "from the GameObject, 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 ``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 "<GameObject name=" + repr(self.name) + " components=" + \
str(list(map(lambda x: type(x).__name__, self.components))) + ">"
__str__ = __repr__
[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.
"""
attrs = []
def __init__(self):
self.gameObject = None
self.transform = None
self.enabled = True
[docs] def AddComponent(self, component):
"""
Calls `AddComponent` on the component's GameObject.
Parameters
----------
component : Component
Component to add. Must inherit from ``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 ``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 ``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 ``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 ``Component``
"""
return self.gameObject.RemoveComponents(component)
[docs]class SingleComponent(Component):
"""
Represents a component that can be added only once.
"""
pass
[docs]class Light(SingleComponent):
"""
Component to hold data about the light in a scene.
Attributes
----------
intensity : int
Intensity of light
"""
attrs = ["intensity"]
def __init__(self):
super(Light, self).__init__()
self.intensity = 100
[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
"""
attrs = ["mesh", "mat"]
def __init__(self):
super(MeshRenderer, self).__init__()
self.mesh = None
self.mat = None
[docs] def Render(self):
"""Render the mesh that the MeshRenderer has."""
if self.mesh is None:
return
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.mesh.vbo)
gl.glBindVertexArray(self.mesh.vao)
gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self.mesh.ibo)
gl.glBindVertexArray(self.mesh.vao)
gl.glDrawElements(gl.GL_TRIANGLES, len(
self.mesh.triangles) * 3, gl.GL_UNSIGNED_BYTE, None)
[docs]class Material:
"""
Class to hold data on a material.
Attributes
----------
color : Color
An albedo tint.
texture : Texture2D
A texture to map onto the mesh provided by a MeshRenderer
"""
def __init__(self, color, texture=None):
self.color = color
self.texture = texture
[docs]class Color:
"""
A class to represent a color.
Parameters
----------
r : int
Red value (0-255)
g : int
Green value (0-255)
b : int
Blue value (0-255)
Atrributes
----------
r : int
Red value (0-255)
g : int
Green value (0-255)
b : int
Blue value (0-255)
"""
def __init__(self, r, g, b):
self.r = r
self.g = g
self.b = b
def __truediv__(self, other):
return self.r / other, self.g / other, self.b / other
def __repr__(self):
return "Color(" + self.to_string + ")"
__str__ = __repr__
def to_string(self):
return "{}, {}, {}".format(self.r, self.g, self.b)
@staticmethod
def from_string(string):
return Color(*list(map(int, string.split(", "))))
class Clock:
def __init__(self):
self._fps = 60
self._frameDuration = 1 / self._fps
@property
def fps(self):
return self._fps
@fps.setter
def fps(self, value):
self._fps = value
self._frameDuration = 1 / self._fps
def Start(self, fps=None):
if fps is not None:
self.fps = fps
self._start = time.time()
def Maintain(self):
self._end = time.time()
elapsedMS = self._end - self._start
sleep = self._frameDuration - elapsedMS - 0.001
if sleep < 0:
self._start = time.time()
return sys.float_info.epsilon
time.sleep(sleep)
self._start = time.time()
return sleep