"""
Class to load, render and manage GameObjects
and their various components.
You should never use the ``Scene``
class directly, instead, only use
the SceneManager class.
"""
from ..audio import *
from ..core import *
from ..files import Behaviour
from ..vector3 import Vector3
from ..quaternion import Quaternion
from .. import config, physics, logger as Logger, render
from ..errors import *
from time import time
import os
import math
if os.environ["PYUNITY_INTERACTIVE"] == "1":
import OpenGL.GL as gl
[docs]class Scene:
"""
Class to hold all of the GameObjects, and to run the whole
scene.
Parameters
----------
name : str
Name of the scene
Notes
-----
Create a scene using the SceneManager, and don't create a scene
directly using this class.
"""
def __init__(self, name):
self.name = name
self.mainCamera = GameObject("Main Camera").AddComponent(render.Camera)
self.mainCamera.AddComponent(AudioListener)
light = GameObject("Light")
light.AddComponent(Light)
light.transform.localPosition = Vector3(10, 10, -10)
self.gameObjects = [self.mainCamera.gameObject, light]
self.rootGameObjects = [self.mainCamera.gameObject, light]
@staticmethod
def Bare(name):
cls = Scene.__new__(Scene)
cls.name = name
cls.gameObjects = []
cls.rootGameObjects = []
cls.mainCamera = None
return cls
[docs] def Add(self, gameObject):
"""
Add a GameObject to the scene.
Parameters
----------
gameObject : GameObject
The GameObject to add.
"""
self.gameObjects.append(gameObject)
if gameObject.transform.parent is None:
self.rootGameObjects.append(gameObject)
[docs] def Remove(self, gameObject):
"""
Remove a GameObject from the scene.
Parameters
----------
gameObject : GameObject
GameObject to remove.
Raises
------
PyUnityException
If the specified GameObject is not part
of the Scene.
"""
if gameObject in self.gameObjects:
self.gameObjects.remove(gameObject)
else:
raise PyUnityException(
"The provided GameObject is not part of the Scene")
if gameObject is self.mainCamera:
Logger.LogLine(
Logger.WARN, "Removing Main Camera from scene \"" + self.name + "\"")
[docs] def List(self):
"""Lists all the GameObjects currently in the scene."""
for gameObject in sorted(self.rootGameObjects, key=lambda x: x.name):
gameObject.transform.List()
[docs] def FindGameObjectsByName(self, name):
"""
Finds all GameObjects matching the specified name.
Parameters
----------
name : str
Name of the GameObject
Returns
-------
list
List of the matching GameObjects
"""
return [gameObject for gameObject in self.gameObjects if gameObject.name == name]
[docs] def FindGameObjectsByTagName(self, name):
"""
Finds all GameObjects with the specified tag name.
Parameters
----------
name : str
Name of the tag
Returns
-------
list
List of matching GameObjects
Raises
------
GameObjectException
When there is no tag named `name`
"""
if name in Tag.tags:
return [gameObject for gameObject in self.gameObjects if gameObject.tag.tagName == name]
else:
raise GameObjectException(
"No tag named " + name + "; create a new tag with Tag.AddTag")
[docs] def FindGameObjectsByTagNumber(self, num):
"""
Gets all GameObjects with a tag of tag `num`.
Parameters
----------
num : int
Index of the tag
Returns
-------
list
List of matching GameObjects
Raises
------
GameObjectException
If there is no tag with specified index.
"""
if len(Tag.tags) > num:
return [gameObject for gameObject in self.gameObjects if gameObject.tag.tag == num]
else:
raise GameObjectException(
"No tag at index " + str(num) + "; create a new tag with Tag.AddTag")
[docs] def FindComponentByType(self, component):
"""
Finds the first matching Component that is in the Scene.
Parameters
----------
component : type
Component type
Returns
-------
Component
The matching Component
Raises
------
ComponentException
If the component is not found
"""
for gameObject in self.gameObjects:
query = gameObject.GetComponent(component)
if query is not None:
break
if query is None:
raise ComponentException(
"Cannot find component " + component.__name__ + " in scene")
return query
[docs] def FindComponentsByType(self, component):
"""
Finds all matching Components that are in the Scene.
Parameters
----------
component : type
Component type
Returns
-------
list
List of the matching Components
"""
components = []
for gameObject in self.gameObjects:
query = gameObject.GetComponents(component)
components.extend(query)
return components
[docs] def inside_frustrum(self, renderer):
"""
Check if the renderer's mesh can be
seen by the main camera.
Parameters
----------
renderer : MeshRenderer
Renderer to test
Returns
-------
bool
If the mesh can be seen
"""
mesh = renderer.mesh
pos = self.mainCamera.transform.position * Vector3(1, 1, -1)
directionX = self.mainCamera.transform.rotation.RotateVector(
Vector3.right()) * Vector3(1, 1, -1)
directionY = self.mainCamera.transform.rotation.RotateVector(
Vector3.up()) * Vector3(1, 1, -1)
directionZ = self.mainCamera.transform.rotation.RotateVector(
Vector3.forward()) * Vector3(1, 1, -1)
parent = renderer.transform.parent.position if renderer.transform.parent else Vector3.zero()
rpmin = renderer.transform.rotation.RotateVector(
mesh.min - renderer.transform.localPosition)
rpmax = renderer.transform.rotation.RotateVector(
mesh.max - renderer.transform.localPosition)
rpmin += parent - pos
rpmax += parent - pos
minZ = rpmin.dot(directionZ)
maxZ = rpmax.dot(directionZ)
if minZ > self.mainCamera.near or maxZ < self.mainCamera.far:
return True
minY = rpmin.dot(directionY)
maxY = rpmax.dot(directionY)
hmin = minZ * 2 * \
math.tan(math.radians(self.mainCamera.fov /
config.size[0] * config.size[1] / 2))
hmax = maxZ * 2 * \
math.tan(math.radians(self.mainCamera.fov /
config.size[0] * config.size[1] / 2))
if minY > -hmin / 2 or maxY < hmax / 2:
return True
minX = rpmin.dot(directionX)
maxX = rpmax.dot(directionX)
wmin, wmax = hmin * \
config.size[0] / config.size[1], hmax * \
config.size[0] / config.size[1]
return minX > -wmin / 2 or maxX < wmax / 2
[docs] def start_scripts(self):
"""Start the scripts in the Scene."""
self.lastFrame = time()
audioListeners = self.FindComponentsByType(AudioListener)
if len(audioListeners) == 0:
Logger.LogLine(
Logger.WARN, "No AudioListeners found, audio is disabled")
self.audioListener = None
elif len(audioListeners) > 1:
Logger.LogLine(Logger.WARN, "Ambiguity in AudioListeners, " +
str(len(audioListeners)) + " found")
self.audioListener = None
else:
self.audioListener = audioListeners[0]
self.audioListener.Init()
for gameObject in self.gameObjects:
for component in gameObject.components:
if isinstance(component, Behaviour):
component.Start()
elif isinstance(component, AudioSource):
if component.PlayOnStart:
component.Play()
elif isinstance(component, MeshRenderer) and component.mesh is not None:
mesh = component.mesh
mesh.vbo, mesh.ibo = render.gen_buffers(mesh)
mesh.vao = render.gen_array()
self.physics = any(
isinstance(
component, physics.Collider
) for gameObject in self.gameObjects for component in gameObject.components
)
if self.physics:
self.collManager = physics.CollManager()
self.collManager.AddPhysicsInfo(self)
[docs] def Start(self):
"""
Start the internal parts of the
Scene.
"""
self.mainCamera.lastPos = Vector3.zero()
self.mainCamera.lastRot = Quaternion.identity()
if os.environ["PYUNITY_INTERACTIVE"] == "1":
self.mainCamera.Resize(*config.size)
gl.glEnable(gl.GL_DEPTH_TEST)
if config.faceCulling:
gl.glEnable(gl.GL_CULL_FACE)
self.start_scripts()
Logger.LogLine(Logger.DEBUG, "Physics is",
"on" if self.physics else "off")
Logger.LogLine(Logger.DEBUG, "Scene \"" + self.name + "\" has started")
[docs] def update_scripts(self):
"""Updates all scripts in the scene."""
dt = max(time() - self.lastFrame, 0.001)
for gameObject in self.gameObjects:
for component in gameObject.components:
if isinstance(component, Behaviour):
component.Update(dt)
elif isinstance(component, AudioSource):
if component.Loop:
if component.PlayOnStart:
if component.channel and not component.channel.get_busy():
component.Play()
if self.physics:
self.collManager.Step(dt)
self.lastFrame = time()
def no_interactive(self):
done = False
clock = Clock()
clock.fps = config.fps
while not done:
try:
self.update_scripts()
clock.Maintain()
except KeyboardInterrupt:
Logger.LogLine(Logger.DEBUG, "Exiting")
done = True
[docs] def update(self):
"""Updating function to pass to the window provider."""
self.update_scripts()
if os.environ["PYUNITY_INTERACTIVE"] == "1":
gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
if (self.mainCamera.lastPos != self.mainCamera.transform.position or
self.mainCamera.lastRot != self.mainCamera.transform.rotation):
self.mainCamera.lastPos = self.mainCamera.transform.position
self.mainCamera.lastRot = self.mainCamera.transform.rotation
self.mainCamera.Render(self.gameObjects)
def clean_up(self):
if self.audioListener is not None:
self.audioListener.DeInit()