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?
Related
I have fields that are set in the inspector.
The length of this array is then printed to the console when the game plays as expected
public void Start() {
Debug.Log(bootstrapData.Length);
}
public void printData()
{
Debug.Log(bootstrapData.Length);
}
But when I run a test calling the printData in play mode the bootstrapData is null as they are not set from the inspector I am assuming.
[UnityTest]
public IEnumerator test()
{
GameObject go = new GameObject();
SpriteBootstrap spriteBootstrap = go.AddComponent<SpriteBootstrap>();
spriteBootstrap.printData(); // null pointer here, data not set
//... other irrelevant code
}
Is this normal behaviuor and can I get around this without manualy setting the data in the test.
Creating a prefab of the game object and loading it means the fields are set by default.
In this example I am loading a prefab that contains a chunk script, you can then test the chunk script as I was trying to do with the SpriteBootstrap above.
GameObject chunkGameObject = MonoBehaviour.Instantiate((Resources.Load<GameObject>("Prefabs/Chunk")));
Chunk chunk = firstChunkGameObject.GetComponent<Chunk>();
// you can now use chunk to test
In Unity3D Version 2017 you could add multiple EventTriggers at once by doing this:
for (int i = 0; i < 20; i++)
{
EventTrigger.Entry pDown = new EventTrigger.Entry();
pDown.eventID = EventTriggerType.PointerDown;
pDown.callback.AddListener((eventdata) => { InventorySlotCopy(); });
slots[i].GetComponent<EventTrigger>().triggers.Add(pDown);
EventTrigger.Entry pUp = new EventTrigger.Entry();
pUp.eventID = EventTriggerType.PointerUp;
pUp.callback.AddListener((eventdata) => { InventorySlotInsert(); });
slots[i].GetComponent<EventTrigger>().triggers.Add(pUp);
}
where slots is just an array of GameObjects, each with an Image and an EventTrigger attachted to it.
However, using the same code as above in Unity3D Version 2019 results in adding those EventTriggers but not in assigning the functions to the Listener.
How is that been done in Unity2019?
First of all:
You will not see the added listeners in the Inspector!
In the Inspector you only see the permanent listeners, not the ones added on runtime! These are basically UnityEvent<BaseEventData>'s so see Manual: UnityEvents
When a UnityEvent is added to a MonoBehaviour it appears in the Inspector and persistent callbacks can be added.
The only way to add permanent listeners is either via the Inspector or a custom EditorScript! But this is not what you want to do here anyway.
I would slightly modify your script to make it more secure. I simply log some messages for the methods when they get called to show that they get called.
public class Example : MonoBehaviour
{
// Rather make these directly of type EventTrigger
// So you don't have to use GetComponent later and can be sure that these array only receives
// objects that actually have that Component attached!
public EventTrigger[] slots;
private void Start()
{
// Instead of a hardcoded index either iterate using slots.Length
// or simply directly foreach
foreach (var slot in slots)
{
var pDown = new EventTrigger.Entry
{
eventID = EventTriggerType.PointerDown
};
pDown.callback.AddListener(eventData => { InventorySlotCopy(); });
slot.triggers.Add(pDown);
var pUp = new EventTrigger.Entry
{
eventID = EventTriggerType.PointerUp
};
pUp.callback.AddListener(eventData => { InventorySlotInsert(); });
slot.triggers.Add(pUp);
}
}
private void InventorySlotCopy()
{
Debug.Log(nameof(InventorySlotCopy));
}
private void InventorySlotInsert()
{
Debug.Log(nameof(InventorySlotInsert));
}
}
The rest depends on your setup. We don't know what objects your slots are but there are basically two(three) options:
Option 1 - UI
If these slot objects are UI elements in a Canvas such as Image or Text.
This seems to be the case for your specifically.
Make sure that there is an EventSystem present in the scene.
Usually one gets created when creating the first Canvas.
If not create it now via Hierachy View → right click → UI → EventSystem
Make sure you have the option RayCast Target enabled on your slot's UI Components.
Result:
Options 2 - 3D Objects (Option 3 - 3D Objects with 2D Colliders)
Just adding this for other users.
If these objects are not UI elements but rather 3D objects make sure
Again you need the EventSystem as before
The slots have Collider Components attached to the same GameObject the EvenTrigger Component is attached to or any of its children (it may be nested).
Your Camera needs the Component PhysicsRaycaster attached. Make sure here that the eventMask includes the layer(s) your slot objects are placed on. (For Option 3 using 2D Colliders instead use the Physics2DRaycaster)
Result:
As you can see the methods will not be visible in the Inspector (due to the reason mentioned before) but the added listener methods still get called.
I am working on a AR project, where the virtual objects will be shown/hide in the scene based on information found in a text file. The text files will be updated from an external service. So I need to read the file on a frequent interval and update the scene. As a result I only have the Camera object and I am rendering the scene in OnPreCull() method.
The text files contain many objects but not all the objects are within the scene at any instance of time. I was looking for a way to render only those objects that are within the scene.
Will creating and placing the gameobjects in the OnPreCull() method crate any performance issue?
Will creating and placing the gameobjects in the OnPreCull() method crate any performance issue?
Yes absolutely ... so would it if you do it in Update or any other repeatedly called method.
Instead you should rather Instantiate objects in Awake and only activate or deactivate them.
Let's say you have 3 objects A, B and C than I would make a kind of controller class that looks like
public class ObjectsController : MonoBehaviour
{
// Define in which intervals the file should be read/ the scene should be updated
public float updateInterval;
// Prefabs or simply objects that are already in the Scene
public GameObject A;
public GameObject B;
public GameObject C;
/* Etc ... */
// Here you map the names from your textile to according object in the scene
private Dictionary<string, GameObject> gameObjects = new Dictionary<string, gameObjects>();
private void Awake ()
{
// if you use Prefabs than instantiate your objects here; otherwise you can skip this step
var a = Instantiate(A);
/* Etc... */
// Fill the dictionary
gameObjects.Add(nameOfAInFile, a);
// OR if you use already instantiated references instead
gameObjects.Add(nameOfAInFile, A);
}
}
private void Start()
{
// Start the file reader
StartCoroutine (ReadFileRepeatedly());
}
// Read file in intervals
private IEnumerator ReadFileRepeatedly ()
{
while(true)
{
//ToDo Here read the file
//Maybe even asynchronous?
// while(!xy.done) yield return null;
// Now it depends how your textile works but you can run through
// the dictionary and decide for each object if you want to show or hide it
foreach(var kvp in gameObjects)
{
bool active = someConditionDependingOnTheFile;
kvp.value.SetActive(active);
// And e.g. position it only if active
if (active)
{
kvp.value.transform.position = positionFromFile;
}
}
// Wait for updateInterval and repeat
yield return new WaitForSeconds (updateInterval);
}
}
If you have multiple instances of the same prefab you also should have a look at Object Pooling
I'd recommend adding each of the game objects to a registry and the switching them on or off (dis/enable SetActive) via the registry class's Update() cycle.
One Update() process to retrieve and handle the server file, another Update() process to dis/enable objects. Might sound oversimplified however it's the fastest way I think of getting the result.
Good Luck!
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);
}
I couldn't find any topic matching my scenario, so I made a new one.
I have a prefab of type Light called rewardHighlight.
I have a script called Reward which has a parameter Highlight and instantiates the Light using Light rewardHighlight = (Light) Instantiate (Highlight);.
I used drag and drop to set the default value of Highlight parameter of said script to RewardHighlight prefab.
When I drag and drop the script onto an object, it works correctly.
When I add Reward script at runtime using gameObject.AddComponent<Reward>(); I'm getting The thing you want to instantiate is null.
All help much appreciated!
#update
Full code (stripped for clarity):
Reward2.cs
using UnityEngine;
using System.Collections;
public class Reward2 : MonoBehaviour {
public Light Highlight;
private Light rewardHighlight;
// Use this for initialization
void Start () {
rewardHighlight = (Light) Instantiate (Highlight);
rewardHighlight.transform.position = new Vector3(transform.position.x, transform.position.y + 1, transform.position.z);
}
// Update is called once per frame
void Update () {
}
}
Test.cs
using UnityEngine;
using System.Collections;
public class Test : MonoBehaviour {
// Use this for initialization
void Start () {
gameObject.AddComponent<Reward2>();
}
// Update is called once per frame
void Update () {
}
}
It looks like Unity scripts 'forget' their defaults at runtime.
After you call AddComponent in Test.Start you need to assign a value to the Highlight field on the Reward2 instance returned by AddComponent.
For example, your Test.Start may look a little like this:
void Start()
{
Reward2 obj = gameObject.AddComponent<Reward2>();
obj.Highlight = <SOME VALUE>;
}
Now, the value assigned to Highlight cannot simply be an instance of the Light component, e.g. obj.Highlight = new Light(). The reason for this is the Instantiate method creates an instance of a game object and not an individual component. If you use AddComponent<T> it still creates an instance of a game object, but will automatically locate component of type T on the new game object and will return that.
So, in order to make this work you can change the Test class to either:
1) Expose a field of type Light, similar to the Reward2 class, and assign that field to the Highlight property:
public Light HighlightPrefab;
...
void Start()
{
Reward2 obj = gameObject.AddComponent<Reward2>();
obj.Highlight = HighlightPrefab;
}
or
2) create a game object in the Start method, add a light component to it and then assign that component to the Highlight field:
void Start()
{
Reward2 obj = gameObject.AddComponent<Reward2>();
GameObject gameObjectForLight = new GameObject();
Light newLight = gameObjectForLight.AddComponent<Light>();
// Initialize newLight here
obj.Highlight = newLight;
}
In either case when Reward2.Start calls Instantiate it will see that the Highlight field is a component, it will then get the game object to which the component is attached and it will create a new instance of that game object.