So I have a gameobject which loads new scenes when necessary. The script attached to the game object has a variable which holds the reference to a canvas element(the canvas element has a progressbar). The problem is that after I load a new scene and I want to load a new scene in that loaded scene I lose the reference to the canvas object because I can't set him to DontDestroyOnLoad.
What I have done is I put the canvas element (see commented line in start function()) as a child of the gameobject which doesn't get destroyed.. After I have done that I don't get the warning that I have lost the object reference BUT it won't enable the canvas in the setNewNameAndLoadScene() function.
What I mean with enable is that the gui won't appear on the screen but it is in the hirarchy. I checked if it is null which it wasn't. The program runs trough the line of code without problem but the canvas doesn't appear on the gamescreen.
2nd EDIT: I just checked (during the run time) what if I put my canvas (which is a child of my gameobject ) in the canvas of the new loaded scene. what happened is that it showed my canvas. but what I ask me why does it show it when I put it into the scene canvas. before I loaded my first scene my canvas wasn't in the scene canvas but still has shown the progress bar in the gamescreen
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SceneLoaderGameObject : MonoBehaviour {
private SceneLoader sl;
public Canvas cav; //loses reference when loading new scene
void Start () {
sl = new SceneLoader ();
cav.GetComponent<Canvas> ().enabled = false;
DontDestroyOnLoad (this.gameObject);
DontDestroyOnLoad (this);
DontDestroyOnLoad (this.gameObject.transform.GetChild(0).GetComponent<Canvas>());
}
public void setNewNameAndLoadScene(string name){
if(name.Length ==0)
throw new UnityException("length 0");
if (cav != null) {
Slider slid = cav.transform.GetChild (1).GetComponent<Slider> ();
Text tx = cav.transform.GetChild (2).GetComponent<Text> ();
Button bttn = cav.transform.GetChild (3).GetComponent<Button> ();
sl.setNewScene (name);
sl.setSliderAndTextToChange (slid, tx);
bttn.onClick.AddListener (() => activateNewScene ());
cav.GetComponent<Canvas> ().enabled = true;
StartCoroutine (sl.LoadAsynchron (name, bttn));
}
}
public void activateNewScene(){
sl.AsgetOP().allowSceneActivation=true;
}
}
Simply use code below to make singletons and get public GameObject references of a sliders or whatever you want to activate-deactivate when scene loading. For example if you have game manager that controls components of canvas, make both singleton and reference objects one to another. Source of code is here.
public class SceneLoaderSingleton : MonoBehaviour
{
//Static instance of SceneLoaderSingleton which allows it to be accessed by any other script.
public static SceneLoaderGameObject instance = null;
//Awake is always called before any Start functions
void Awake()
{
//Check if instance already exists
if (instance == null)
//if not, set instance to this
instance = this;
//If instance already exists and it's not this:
else if (instance != this)
//Then destroy this. This enforces singleton pattern, meaning there can only ever be one instance of a SceneLoaderSingleton.
Destroy(gameObject);
//Sets this to not be destroyed when reloading scene
DontDestroyOnLoad(gameObject);
}
Related
I am new to unity, I created an AR app following a YouTube tutorial that makes use of the Google/Apple native AR libraries and ARKit. I successfully built the app and deployed it to my iPhone. problem is, even when the target image leaves the screen, the instantiated object doesn't disappear. the script I copied is below, I don't know if that is where the problem is as I haven't learned scripting yet.
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
[RequireComponent(typeof(ARTrackedImageManager))]
public class PlaceTrackedImages : MonoBehaviour {
// Reference to AR tracked image manager component
private ARTrackedImageManager _trackedImagesManager;
// List of prefabs to instantiate - these should be named the same
// as their corresponding 2D images in the reference image library
public GameObject[] ArPrefabs;
// Keep dictionary array of created prefabs
private readonly Dictionary<string, GameObject> _instantiatedPrefabs = new Dictionary<string, GameObject>();
void Awake() {
// Cache a reference to the Tracked Image Manager component
_trackedImagesManager = GetComponent<ARTrackedImageManager>();
}
void OnEnable() {
// Attach event handler when tracked images change
_trackedImagesManager.trackedImagesChanged += OnTrackedImagesChanged;
}
void OnDisable() {
// Remove event handler
_trackedImagesManager.trackedImagesChanged -= OnTrackedImagesChanged;
}
// Event Handler
private void OnTrackedImagesChanged(ARTrackedImagesChangedEventArgs eventArgs) {
// Loop through all new tracked images that have been detected
foreach (var trackedImage in eventArgs.added) {
// Get the name of the reference image
var imageName = trackedImage.referenceImage.name;
// Now loop over the array of prefabs
foreach (var curPrefab in ArPrefabs) {
// Check whether this prefab matches the tracked image name, and that
// the prefab hasn't already been created
if (string.Compare(curPrefab.name, imageName, StringComparison.OrdinalIgnoreCase) == 0
&& !_instantiatedPrefabs.ContainsKey(imageName)) {
// Instantiate the prefab, parenting it to the ARTrackedImage
var newPrefab = Instantiate(curPrefab, trackedImage.transform);
// Add the created prefab to our array
_instantiatedPrefabs[imageName] = newPrefab;
}
}
}
// For all prefabs that have been created so far, set them active or not depending
// on whether their corresponding image is currently being tracked
foreach (var trackedImage in eventArgs.updated) {
_instantiatedPrefabs[trackedImage.referenceImage.name]
.SetActive(trackedImage.trackingState == TrackingState.Tracking);
}
// If the AR subsystem has given up looking for a tracked image
foreach (var trackedImage in eventArgs.removed) {
// Destroy its prefab
Destroy(_instantiatedPrefabs[trackedImage.referenceImage.name]);
// Also remove the instance from our array
_instantiatedPrefabs.Remove(trackedImage.referenceImage.name);
// Or, simply set the prefab instance to inactive
//_instantiatedPrefabs[trackedImage.referenceImage.name].SetActive(false);
}
}
}
I used the script as is. I also created the exact same app using the vuforia plug in, found the setting in it to make the instantiated object dissappear. app worked perfect on my Mac, but when I deployed it to my iPhone all I get is a black screen. so out of 2 different builds I'm still stuck. would prefer an answer to the first scenario, but also curious about the second. I am using unity 2022.1.13 in both instances, can anyone please help?
When loading a new Scene I run into the trouble of having my mouse drag not carry over to the next scene and having to re-click when the new scene is loaded.
I would like the mouse click to carry over seamlessly to the next scene without the player noticing and more generally I would like to know how best to preserve certain game objects and make them carry over to the next scene.
In essence, what I'm trying to do is have the entire game act like one big scene that the player can play trough but still be broken down into smaller scenes that could be accessed or transformed into levels at a later stage.
Thanks in advance.
This is the code I'm currently using
using UnityEngine;
using System.Collections;
using UnityEngine.Profiling;
public class MoveBall : MonoBehaviour
{
public static Vector2 mousePos = new Vector2();
private void OnMouseDrag()
{
mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
transform.position = mousePos;
DontDestroyOnLoad(this.gameObject);
}
}
Bellow is the script that is responsible for the loading of the scene:
public class StarCollision : MonoBehaviour
{
private bool alreadyScored = false;
private void OnEnable()
{
alreadyScored = false;
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("White Ball"))
{
if (!alreadyScored)
{
ScoreScript.scoreValue += 1;
StartCoroutine(ChangeColor());
alreadyScored = true;
}
}
if (ScoreScript.scoreValue > 4)
{
SceneManager.LoadScene(1);
}
}
private IEnumerator ChangeColor()
{
ScoreScript.score.color = Color.yellow;
yield return new WaitForSeconds(0.1f);
ScoreScript.score.color = Color.white;
gameObject.SetActive(false);
}
}
I think the main reason why it doesn't work is that you probably also have another Camera in the new Scene.
The OnMouseDrag rely on the Physics system internally using the objects Collider and raycasts from the Camera. Now if you switch Scene I'ld guess the one Camera gets disabled so your drag gets interrupted.
Also using LoadScene instead of LoadSceneAsync causes a visible lag and might also be related to the issue.
I have a maybe a bit more complex solution but that is what I usually do:
1. Have one Global Scene "MainScene"
This Scene contains stuff like e.g. the MainCamera, global ligthning, global manager components that should never be destroyed anyway.
2. Use additive async Scene loading
You said you do not want your user to not note when the scene switches so I would recommend using SceneManager.LoadSceneAsync anyway.
Then in order to not unload the before mentioned MainScene you pass the optional parameter LoadSceneMode.Additive. This makes the new Scene be loaded additional to the already present one. Then later you only have to exchange those by unloading the previously additive loaded scene.
I created a very simple static manager for this:
public static class MySceneManager
{
// store build index of last loaded scene
// in order to unload it later
private static int lastLoadedScene = -1;
public static void LoadScene(int index, MonoBehaviour caller)
{
caller.StartCoroutine(loadNextScene(index));
}
// we need this to be a Coroutine (see link below)
// in order to correctly set the SceneManager.SetActiveScene(newScene);
// after the scene has finished loading. So the Coroutine is required
// in order to wait with it until the reight moment
private static IEnumerator loadNextScene(int index)
{
// start loading the new scene async and additive
var _async = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);
// optionally prevent the scene from being loaded instantly but e.g.
// display a loading progress
// (in your case not but for general purpose I added it)
_async.allowSceneActivation = false;
while (_async.progress < 0.9f)
{
// e.g. show progress of loading
// yield in a Coroutine means
// "pause" the execution here, render this frame
// and continue from here in the next frame
yield return null;
}
_async.allowSceneActivation = true;
// loads the remaining 10%
// (meaning it runs all the Awake and OnEnable etc methods)
while (!_async.isDone)
{
yield return null;
}
// at this moment the new Scene is supposed to be fully loaded
// Get the new scene
var newScene = SceneManager.GetSceneByBuildIndex(index);
// would return false if something went wrong during loading the scene
if (!newScene.IsValid()) yield break;
// Set the new scene active
// we need this later in order to place objects back into the correct scene
// if we do not want them to be DontDestroyOnLoad anymore
// (see explanation in SetDontDestroyOnLoad)
SceneManager.SetActiveScene(newScene);
// Unload the last loaded scene
if (lastLoadedScene >= 0) SceneManager.UnloadSceneAsync(lastLoadedScene);
// update the stored index
lastLoadedScene = index;
}
}
This MySceneManager is a static class so it is not attached to any GameObject or Scene but simply "lives" in the Assets. You can now call it from anywhere using
MySceneManager.LoadScene(someIndex, theMonoBehaviourCallingIt);
The second parameter of type MonoBehaviour (so basically your scripts) is required because someone has to be responsible for running the IEnumerator Coroutine which can't be done by the static class itself.
3. DontDestroyOnLoad
Currently you are adding any GameObject you dragged at any time to DontDestroyOnLoad. But you never undo this so anything you touched meanwhile will be carried on from that moment ... forever.
I would rather use e.g. something like
public static class GameObjectExtensions
{
public static void SetDontDestroyOnLoad(this GameObject gameObject, bool value)
{
if (value)
{
// Note in general if DontDestroyOnLoad is called on a child object
// the call basically bubbles up until the root object in the Scene
// and makes this entire root tree DontDestroyOnLoad
// so you might consider if you call this on a child object to first do
//gameObject.transform.SetParent(null);
UnityEngine.Object.DontDestroyOnLoad(gameObject);
}
else
{
// add a new temporal GameObject to the active scene
// therefore we needed to make sure before to set the
// SceneManager.activeScene correctly
var newGO = new GameObject();
// This moves the gameObject out of the DontdestroyOnLoad Scene
// back into the currently active scene
gameObject.transform.SetParent(newGO.transform, true);
// remove its parent and set it back to the root in the
// scene hierachy
gameObject.transform.SetParent(null, true);
// remove the temporal newGO GameObject
UnityEngine.Object.Destroy(newGO);
}
}
}
This is an Extension Method which allows you to simply call
someGameObject.SetDontDestroyOnLoad(boolvalue);
on any GameObject reference.
Then I changed your script to
public class MoveBall : MonoBehaviour
{
public static Vector2 mousePos = new Vector2();
// On mouse down enable DontDestroyOnLoad
private void OnMouseDown()
{
gameObject.SetDontDestroyOnLoad(true);
}
// Do your dragging part here
private void OnMouseDrag()
{
// NOTE: Your script didn't work for me
// in ScreenToWorldPoint you have to pass in a Vector3
// where the Z value equals the distance to the
// camera/display plane
mousePos = Camera.main.ScreenToWorldPoint(new Vector3(
Input.mousePosition.x,
Input.mousePosition.y,
transform.position.z)));
transform.position = mousePos;
}
// On mouse up disable DontDestroyOnLoad
private void OnMouseUp()
{
gameObject.SetDontDestroyOnLoad(false);
}
}
And in your StarCollision script you only have to exchange
SceneManager.LoadScene(1);
with
MySceneManager.LoadScene(2, this);
Demo
For a little demonstration I "faked" it using two simple scripts
This one in the Main scene
public class LoadFirstscene : MonoBehaviour
{
// Start is called before the first frame update
private void Start()
{
MySceneManager.LoadScene(1, this);
}
}
And this one in the other scenes
public class LoadNextScene : MonoBehaviour
{
[SerializeField] private int nexSceneIndex;
private void Update()
{
if (!Input.GetKeyDown(KeyCode.Space)) return;
MySceneManager.LoadScene(nexSceneIndex, this);
}
}
And have 3 Scenes:
Main: As mentioned contains
the MainCamera
a DirectionalLight
the LoadFirstScene
test: contains
a MoveBall "Sphere"
the LoadNextScene
test2: contains
a MoveBall "Cube"
the LoadNextScene
With the indexes matching the build settings (make sure Main is always at 0 ;) )
I can now switch between test and test2 using the Space key.
If I drag one of the objects meanwhile I can carry it on into the next scene (but only 1 at a time). I can even take it on again back to the first scene in order to have e.g. two sphere objects I can play with ;)
I am making a maze game and the keys which are needed to be collected to complete it wont appear again if the game restarts, I get the following error;
MissingReferenceException: The object of type 'MazeDirectives' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
I am just disabling the MazeKey object, not destroying it, can anyone help? Below is my code;
MazeKey.cs
using UnityEngine;
using System.Collections;
public class MazeKey : MonoBehaviour
{
void OnTriggerEnter2D(Collider2D other)
{
transform.parent.SendMessage("OnKeyFound", SendMessageOptions.DontRequireReceiver);
gameObject.SetActive(false);
}
}
MazeDirectives.cs
MazeGoal mazeGoal;
MazeKey mazeKey;
void StartDirectives()
{
mazeGoal = Instantiate(mazeGoalPrefab, MazeGenerator.instance.mazeGoalPosition, Quaternion.identity) as MazeGoal;
mazeGoal.transform.SetParent(transform);
mazeKeyPositions = MazeGenerator.instance.GetRandomFloorPositions(keysToFind);
for (int i = 0; i < mazeKeyPositions.Count; i++)
{
MazeKey mazeKey = Instantiate(mazeKeyPrefab, mazeKeyPositions[i], Quaternion.identity) as MazeKey;
mazeKey.transform.SetParent(transform);
}
}
To restart the game I use the code below;
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
gameObject.SetActive(true);
}
}
MazeGoal.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class MazeGoal : MonoBehaviour
{
public Sprite closedGoalSprite;
public Sprite openedGoalSprite;
void Start()
{
GetComponentInChildren<SpriteRenderer>().sprite = closedGoalSprite;
}
public void OpenGoal()
{
GetComponentInChildren<SpriteRenderer>().sprite = openedGoalSprite;
}
void OnTriggerEnter2D()
{
transform.parent.SendMessage("OnGoalReached", SendMessageOptions.DontRequireReceiver);
}
Explenation
The exception you get is not talking about the MazeKey object but rather the MazeDirectives component.
Unfortunately you hit the most important information in the comments:
private void Awake()
{
MazeGenerator.OnMazeReady += StartDirectives;
}
so OnMazeReady seems to be static and not instanced so it will not be destroyed when a new Scene is loaded but keeps intact bloating into the new Scene!
When you call
MazeGenerator.OnMazeReady += StartDirectives;
you add the call to the StartDirectives method of an instance of MazeDirectives as listener to that static event.
Now when you reload the Scene all GameObjects and thereby their instances of components are destroyed
=> so is the instance of MazeGenerator ... BUT the static event OnMazeReady is not destroyed!
so after the next Awake call you now have two listeners
The one from the "second"/new loaded Scene
Still the "old" one you added the first time
But since the instance of MazeDirectives you added the first listener for is destroyed when the scene is reload and a new instance generated, you get that exception
MissingReferenceException: The object of type 'MazeDirectives' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
when the method tries to access the transform value of the destroyed instance.
Solution 1a
So you should remove the listener when you destroy the instance
private void OnDestroy()
{
MazeGenerator.OnMazeReady -= StartDirectives;
}
Solution 1b
or overwrite it with only exactly one listener at a time
private void Awake()
{
MazeGenerator.OnMazeReady = StartDirectives;
}
this second aproach obviously is only useful when there is no other instance or class listening to that event. The question is how much sense does it make to use an event than? And I would than anyway remove it if not needed just to be sure
private void OnDestroy()
{
MazeGenerator.OnMazeReady = null;
}
Solution 2
I would prefere this solution.
Don't make MazeGenerator.OnMazeReady static at all. Since anyway I see that you are using a Singleton pattern e.g. in
MazeGenerator.instance.mazeGoalPosition
you could instead just make OnMazeReady Non-static and instead use it the same way:
private void Awake()
{
MazeGenerator.instance.OnMazeReady += startDirectives;
}
so it will be destroyed together with that instance of MazeGenerator.
General note
I would always remove all listeners I ever added as soon as possible to avoid exactly the issue you have.
You could additionally remove it e.g. already inside of StartDirectives to make sure the method is executed only once even if the same Scene "accidentely" invoked OnMazeReady twice.
Hint: I said additionally since it is always save/possible to remove a listener even if it wasn't added before and you should allways leave the one in OnDestroy in case the StartDirectives is never called before the object is destroyed.
Update:
This answer is wrong at first place. The exception is complaining about accessing the MazeDirectives's transform, not mazeGoal object.
But the comments below did give some useful info. So I'm keeping this post for references.
For complete solution, see here.
From the line mazeGoal.transform.SetParent(transform); throws the exception:
MissingReferenceException: The object of type 'MazeDirectives' has been
destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
From here:
The load of a new Scene destroys all current Scene objects.
The mazeGoal has been destroyed when you called the
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
to restart the game.
And from MonoBehaviour.Awake(),
Awake is called only once during the lifetime of the script instance.
Since you only assign the mazeGoal variable inside StartDirectives function which been called in Awake, after loading the same scene again, the actual object of mazeGoal has been destroyed.
If you want to reuse the same object when loading a new scene, you can use DontDestroyOnLoad to keep the mazeGoal object.
Or you can move the StartDirectives to Start function which will be called every time the gameobject is created and reinitialize your mazeGoal.
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(Camera))]
public class CameraFrustumGizmo : MonoBehaviour
{
Camera _camera;
void Start()
{
_camera = GetComponent<Camera>();
}
The Camera is black and when i start to type Ca...there is no Camera.
I'm not getting errors when compiling the script but when running the game i'm getting null exception.
The script is attached to Main Camera.
I tried now this:
using UnityEngine;
using System.Collections;
public class CameraTest : MonoBehaviour {
Camera _camera;
void Start()
{
_camera = GetComponent<Camera>();
}
public virtual void OnDrawGizmos()
{
Matrix4x4 temp = Gizmos.matrix;
Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one);
if (_camera.orthographic)
Now the Camera exist but once i attached the script to the Main Camera i'm getting null exception before even running the game the null is on the line:
if (_camera.orthographic)
NullReferenceException: Object reference not set to an instance of an object
CameraTest.OnDrawGizmos () (at Assets/MyScripts/CameraTest.cs:17)
UnityEditor.DockArea:OnGUI()
OnDrawGizmos will be called in the scene view, which means that it may be called while the game isn't running.
Your Start function isn't called until the game runs, so there's a chance that _camera won't be initialized by the time OnDrawGizmos is called.
Basically, when you're messing around with editor scripting, you need to be very careful about which object references exist. It's usually best if your references can be rebuilt on the fly -- given that Unity reloads assemblies each time it re-compiles code, that is going to happen pretty often anyway.
Usually I'll do this with a property that caches a reference the first time it's needed:
Camera cachedCamera;
Camera _camera {
get {
if (cachedCamera == null) {
cachedCamera = GetComponent<Camera>();
}
return cachedCamera;
}
}
That will still fail in some circumstances, but it gives you the basic idea.
I would like to change the color of multiple gameobjects in Unity using a single script. I'm kinda lost in the way how to do it. I'm new to Unity and this is some sort of basic training for me.
Unity version: 5.3.4
Observed Behavior:
Added the same script to the other gameobjects and all change to the same color
Expected Behavior:
Change the color of the gameobjects individually
List of things tried:
Using the -FindGameObject-
Tried to acces the materials using the -GameObject-
Tried both at the same time
Thinking in multiple scripts to achieve the results I want
Here's the code
C#:
using UnityEngine;
using System.Collections;
public class ChangeColor : MonoBehaviour
{
//If I change these variables to -GameObject-
//It blocks me to access the renderer
//Making the variables public doesn't work either
private Renderer cube;
private Renderer sphere;
void Start ()
{
//Tried here the -FindGameObjectWithTag-
cube = GetComponent<Renderer>();
sphere = GetComponent<Renderer>();
}
void Update ()
{
if(Input.GetKeyDown(KeyCode.A))
{
//Tried here the -FindGameObjectWithTag-
cube.material.color = Color.red;
}
if(Input.GetKeyDown(KeyCode.S))
{
//Tried here the -FindGameObjectWithTag-
sphere.material.color = Color.green;
}
}
}
Maybe I'm doing something wrong, as I said I'm new to Unity, I kindly accept any help, if it is noobfriendly the better.
Thanks
There are a couple different ways you can go about doing this.
If they are all under the same parent without other objects there too you could loop through the children and change their colors
GameObject Parent = GameObject.Find("ParentObject");
for( int x = 0; x > Parent.transform.childCount; x++);
{
Parent.transform.GetChild(x).GetComponent<Renderer>().material.color = Color.red;
}
If there are less than say 20ish, you could create a list and simply drag and drop each transform into the inspector after changing the list's size to the number of objects you have.
/*Make sure you have Using "System.Collections.Generic" at the top */
//put this outside function so that the inspector can see it
public List<Transform> Objs;
// in your function put this (when changing the color)
foreach(Transform Tform in Objs){
Tform.GetComponent<Renderer>().material.color = Color.red;
}
Similarly to 2, if you want to do it all in code you can do
//Outside function
public List<Transform> Objs;
//inside function
Objs.Add(GameObject.Find("FirstObject").transform);
Objs.Add(GameObject.Find("SecondObject").transform);
//... keep doing this
//now do the same foreach loop as in 2
You could search by tag (if you have a lot of objects) (i would imagine that this would take a bit longer just because it is going through each component but I have no evidence to back that up)
//Outside function
public GameObject[] Objs;
//inside function
Objs = GameObject.FindGameObjectsWithTag("ATagToChangeColor");
foreach(Transform Tform in Objs){
Tform.GetComponent<Renderer>().material.color = Color.red();
}
And about this comment:
//If I change these variables to -GameObject-
//It blocks me to access the renderer
//Making the variables public doesn't work either
If you make them of type GameObject then you should easily be able to access the renderer simply by doing this:
public GameObject cube;
public void Start(){
cube.GetComponent<Renderer>().material.color = Color.red();
}
And making the variables public allows unity's inspector to see the variables so that you can change them in the unity editor without having to open the script.