Source code for pyunity.scenes.scene

"""
Class to load, render and manage GameObjects
and their various components.

You should never use the :class:`Scene`
class directly, instead, only use
the :class:`SceneManager` class.

"""

__all__ = ["Scene"]

from ..audio import *
from ..core import *
from ..files import Behaviour
from ..values import Vector3, Quaternion
from .. import config, physics, logger as Logger
from ..errors import *
from ..values import Clock
from time import time
import os
import math
import uuid

if os.environ["PYUNITY_INTERACTIVE"] == "1":
    import OpenGL.GL as gl

disallowed_chars = set(":*/\"\\?<>|")

[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): from .. import render self.name = name self.mainCamera = GameObject("Main Camera").AddComponent(render.Camera) self.mainCamera.AddComponent(AudioListener) light = GameObject("Light") light.transform.localPosition = Vector3(10, 10, -10) light.transform.localRotation = Quaternion.Euler(Vector3(-45, 45, 0)) self.gameObjects = [self.mainCamera.gameObject, light] component = light.AddComponent(Light) self.lights = [component] self.ids = {} self.id = str(uuid.uuid4())
[docs] @staticmethod def Bare(name): """ Create a bare scene. Parameters ---------- name : str Name of the scene Returns ------- Scene A bare scene with no GameObjects """ cls = Scene.__new__(Scene) cls.name = name cls.gameObjects = [] cls.mainCamera = None cls.ids = {} cls.lights = [] return cls
@property def rootGameObjects(self): """All GameObjects which have no parent""" return [x for x in self.gameObjects if x.transform.parent is None]
[docs] def Add(self, gameObject): """ Add a GameObject to the scene. Parameters ---------- gameObject : GameObject The GameObject to add. """ if gameObject.scene is not None: raise PyUnityException("GameObject \"%s\" is already in Scene \"%s\"" % (gameObject.name, gameObject.scene.name)) gameObject.scene = self self.gameObjects.append(gameObject) component = gameObject.GetComponent(Light) if component is not None: self.lights.append(component)
[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 not in self.gameObjects: raise PyUnityException( "The provided GameObject is not part of the Scene") pending = [a.gameObject for a in gameObject.transform.GetDescendants()] for gameObject in pending: if gameObject in self.gameObjects: gameObject.scene = None component = gameObject.GetComponent(Light) if component is not None and component in self.lights: self.lights.remove(component) self.gameObjects.remove(gameObject) if self.mainCamera is not None and gameObject is self.mainCamera.gameObject: Logger.LogLine(Logger.WARN, f"Removing Main Camera from scene {self.name!r}") self.mainCamera = None for gameObject in self.gameObjects: for component in gameObject.components: for saved in component.saved: attr = getattr(component, saved) if isinstance(attr, GameObject): if attr in pending: setattr(component, saved, None) elif isinstance(attr, Component): if attr.gameObject in pending: setattr(component, saved, None)
[docs] def Has(self, gameObject): """ Check if a GameObject is in the scene. Parameters ---------- gameObject : GameObject Query GameObject Returns ------- bool If the GameObject exists in the scene """ return gameObject in self.gameObjects
[docs] def RegisterLight(self, light): """ Register a light for the scene. Parameters ---------- light : Light Light component to register """ if isinstance(light, Light): self.lights.append(light)
[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( f"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( f"No tag at index {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( f"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) if renderer.transform.parent is not None: parent = renderer.transform.parent.position else: parent = 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.""" from .. import render 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.mainCamera.setup_buffers() self.physics = any( isinstance( component, physics.Rigidbody ) for gameObject in self.gameObjects for component in gameObject.components ) if self.physics: self.collManager = physics.CollManager() self.collManager.AddPhysicsInfo(self) self.lastFrame = time()
[docs] def Start(self): """ Start the internal parts of the Scene. """ 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) gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) self.start_scripts() Logger.LogLine(Logger.DEBUG, "Physics is", "on" if self.physics else "off") Logger.LogLine(Logger.DEBUG, "Scene " + repr(self.name) + " has started")
[docs] def update_scripts(self): """Updates all scripts in the scene.""" from ..input import Input from ..gui import Canvas dt = max(time() - self.lastFrame, 0.001) Input.UpdateAxes(dt) canvasUpdated = [] for gameObject in self.gameObjects: for component in gameObject.components: if isinstance(component, Behaviour): component.Update(dt) elif isinstance(component, AudioSource): if component.loop and component.playOnStart: if component.channel and not component.channel.get_busy(): component.Play() elif isinstance(component, Canvas): component.Update(canvasUpdated) if self.physics: for i in range(self.collManager.steps): self.collManager.Step(dt / self.collManager.steps) for gameObject in self.gameObjects: for component in gameObject.GetComponents(Behaviour): component.FixedUpdate(dt / self.collManager.steps) for gameObject in self.gameObjects: for component in gameObject.GetComponents(Behaviour): component.LateUpdate(dt) self.lastFrame = time()
[docs] def no_interactive(self): """ Run scene without rendering. """ 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": self.Render()
[docs] def Render(self): """ Call the appropriate rendering functions of the Main Camera. """ from ..gui import Canvas gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT) if self.mainCamera is None: return self.mainCamera.renderPass = True self.mainCamera.Render( self.FindComponentsByType(MeshRenderer), self.lights) self.mainCamera.Render2D(self.FindComponentsByType(Canvas))
[docs] def clean_up(self): """ Called when the scene finishes running, or stops running. """ if self.audioListener is not None: self.audioListener.DeInit()