How to retrieve AssetReference from Scene? - unity3d

I have a Scene marked as Addressable. Given the Scene itself, is it possible to get the AssetReference for that scene?
For example, in the following code snippet, what would ?? need to be?
Scene activeScene = SceneManager.GetActiveScene();
// What can I call here to get the AssetReference?
AssetReference activeSceneReference = ??;

If this is possible at all I honetsly can't tell since I never worked with Addressables so far.
However, following the APIs this might be possible but requires multiple steps:
Since the activeScene is a loaded scene and the same scene can be loaded multiple times via additive loading it is not actually an asset anymore and therefore doesn't have an asset reference at all.
So the first step is actually
Get the asset for a loaded scene
Having your loaded scene you can still get its original asset path via Scene.path.
var activeScene = SceneManager.GetActiveScene();
var activeScenePath = activeScene.path;
And then use AssetDatabase.LoadAssetAtPath to load the original asset
var activeSceneAsset = AssetDataBase.LoadAssetAtPath<Scene>(activeScenePath);
Now that we have the actual asset of the loaded scene we can
Get the GUID of an Asset
Having already the asset activeSceneAsset you can use AssetDatabase.TryGetGUIDAndLocalFileIdentifier in order to optain the GUID we need in the last step:
if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(activeSceneAsset, out var guid, out var file))
{
// TODO see below
}
and now having that guid we can use it in order to
Get the AssetReference via the GUID
The reason why we wanted to get the GUID is because we can now use it for the AssetReference constructor taking the GUID as parameter:
var activeSceneReference = new AssetReference(guid);
NOTE: All this is ofcourse only possible in the Unity Editor itself since AssetDataBase does not exist outside of it.

Related

URL updated for GeoAnchors in RealityKit with anchor info after being removed from ARView

I'm using GeoTrackingExample project from Apple:
GeoTrackingExample
and there is a bug in the logic where after you reset the AR Session, it still retains the anchor information in the URL. So the steps are as follows:
start app
tap in the arview and place an object
select Reset AR Session from the menu
tap in the arview to place an object
select save anchor from the menu
save file to device
Reset AR Session
Load the saved file
When the file loads, it will load both the initial object placed in the AR view in step 2 then removed in step 3 and the added object that was placed in step 4. It should only have saved the object added in step 4, not both objects from step 2 and 4. When stepping though the code to debug, it appears that the the anchors are removed from the ARview but somehow the URL will contain the path for both the anchors. What else do I need to do to clear so that objects from previous sessions are not added to the URL in the current session? Is the URL modified when the object is added to an ARView? Please help! Thanks
The example code from Apple uses a computed var to store the array of placed Geoanchors in the scene but since the computed variable is read-only, the values in the array cannot be removed when the scene is reset so it was keeping a long list every time a model was added to the scene with no way to delete them (that I know of.) Fixed the issue by not using a computed variable and modifying the code accordingly:
var currentAnchors: [ARAnchor] = [] // from
var currentAnchors: [ARAnchor] {
return arView.session.currentFrame?.anchors ?? []}
append the geo anchor to the array when the anchor is placed in the scene
add currentAnchors.removeAll() in the reset AR session function
Not sure if this is helpful at all to anyone out there but I'm glad I figured it out!
** Anyone know why they used computed variable for storing AR Anchors?

SWIFT: Load different reality Scenes via Variable

So, i am trying to load different Scenes made in RealityComposer, depending on a variable.
What worked so far:
let SceneAnchor = try! Experience1.loadScene()
arView.scene.anchors.append(SceneAnchor)
return arView
Now i looked into apples Documentation and saw the possibility of:
if let anchor = try? Entity.loadAnchor(named: "Scene") {
arView.scene.addAnchor(anchor)
}
where i thought i could just change "Scene" to "Scene(myVar)"
But once i have more than one scene in my file the first solution doesnt work anymore
and the second one doesnt work as well.
What am i missing?
I also looked into working with filenames and was able to make an array of all my .reality Files and Store them in an Array, so i thought i could recall that via the index, but
arrayName[1].loadScene() doesnt seem to work either, eventhough i can print the filenames to console.
Thanks in advance :)
The fact is that Reality Composer creates a separate static method for each scene load. The name of such method is load+scene name. So, if you have 2 scenes in your Exprerience.xcproject with the names Scene and Scene1, then you have 2 static methods
let scene = Experience.loadScene()
let scene1 = Experience.loadScene1()
Unfortunately it is not possible to use the scene name as a parameter, so, you need to use the switch statement in your app to select the appropriate method.

Can I programatically load scenes in the Unity editor?

I'm using the A* pathfinding algorithm for my 2D game (from my understanding, Unity Nav Meshes don't work in 2D). I would like to be able to pre-calculate navigation grids for all of my scenes, and save them in resource files that can be loaded whenever the player enters a new scene. Rather than having to remember to click "calculate" for every scene -- and remember to recalculate all of my scenes if I make a change to my navigation grids -- I want to be able to programatically have the Unity Editor iterate though each scene and calculate the grids.
Is there a way to create a command in the Unity editor that will iteratively open each scene in the editor and run a method on a MonoBehaviour that's in the scene? Alternatively, is there another way to accomplish what I'm trying to do?
Yes you can!
In editmode you can't use SceneManager but have to use the EditorSceneManager.
First of all you need the scenes you want to iterate.
Could be e.g. a public static field with a list of SceneAsset in the Inspector where you simply reference the scenes
public static List<SceneAsset> Scenes = new List<SceneAsset>();
or you could get them by script e.g. for only use the scenes added to the build settings using EditorBuildSettings.scenes
List<EditorBuildSettingsScene> Scenes = EditorBuildSettings.scenes;
For both you can get a list of the scene paths e.g. using LinQ Select (this is basically a kind of shortcut for a foreach loop) and AssetDatabase.GetAssetPath like
List<string> scenePaths = Scenes.Select(scene => AssetDatabase.GetAssetPath(scene)).ToList();
for the EditorBuildSettingsScene from EditorBuildSettings.scenes you can also simply use
List<string> scenePaths = Scenes.Select(scene => scene.path).ToList();
Now you can iterate over them all and do your stuff by using EditorSceneManager.OpenScene, EditorSceneManager.SaveScene and EditorSceneManager.CloseScene (and if you need it AssetDatabase.SaveAssets)
foreach(string scenePath in scenePaths)
{
// Open a scene e.g. in single mode
var currentScene = EditorSceneManager.OpenScene(scenePath);
/* Do your calculation for currentScene */
// Don't know if it makes changes to your scenes .. if not you can probably skip this
EditorSceneManager.SaveScene(currentScene);
// Finally Close and remove the scene
EditorSceneManager.CloseScene(currentScene, true);
}
// you might be doing changes to an asset you want to save when done
AssetDatabase.SaveAssets();
Before starting you should probably ask to save the current open scene(s) using EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo
if(EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
{
// Saved => the foreach loop here
}
else
{
// aborted => do nothing
}
Than in order to finally start that method the simplest would be to add a [MenuItem]
public static class Calculation
{
[MenuItem("YourMenu/RunCalculation")]
public static void RunCalculation()
{
// all the before mentioned snippets here
// depending how exactly you want to do it
}
}
This will add a new menu YourMenu with one entry RunCalculation to the top menubar of the Unity Editor.
Note:
Since this uses a lot of types (EditorSceneManager etc) that only exist in the UnityEditor namespace you should either place the whole script in an Editor folder (so it is ignored in the final build) or use pre-processors like
#if UNITY_EDITOR
// ... code here
#endif
so in the build the code is also ignored.
Note that I'm assuming so far you also did your calculation in editmode. The thing is if this calculation relies somewhere aon any Start or Awake method you have to call it manually from that editorscript before running the calculation.

SceneManager.LoadScene(string) doesn't recognize scene name

I've seen similar questions all over the internet, but none seem to apply to my situation.
I have a very simple project with two scenes: MainMenuScene and OtherScene. OtherScene is nothing but a blank canvas, and MainMenuScene just has a button which is meant to be used to navigate to OtherScene. The Main Camera has the following script attached to it:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class LaunchScene : MonoBehaviour {
public void LaunchSceneOnButtonClick(int index)
{
SceneManager.LoadScene(index);
}
public void LaunchSceneOnButtonClick(string sceneName)
{
SceneManager.LoadScene(sceneName);
}
}
and in the OnClick() of the button, this script is called. I have made sure to add both levels to the Build Settings.
If I call the version of the LanchSceneOnButtonClick which takes an integer argument (and specify the appropriate value in the OnClick() method) then the desired scene loads.
However, if I call the version which takes a string argument and use the exact name of the scene I want to load (and I've quadruple checked that it is indeed the exact string, that the string name has no spaces or hidden characters, and have copy pasted the scene name into the OnClick() argument field) the scene does not load and instead I get the error message
Scene "OtherScene" couldn't be loaded because it has not been added to
the build settings or the AssetBundle has not been loaded.
Since using the scene index works, we know it was added to the Build Settings properly, so I reason that there is some issue with the scene name. But I've copied it exactly. I want to avoid using the full path of the scene in case I later decide to move scenes around to different folders, so what are my options here?
Thanks in advance.
EDIT:
Adding screenshots.
Scene in folder structure:
Scenes in build settings:
OnClick() input:
Keep in mind I've also tried the input to OnClick() with and without quotation marks.
The only possible problems are these:
The string passed is different from the scene name; bear in mind, the string used in LoadScene is not case sensitive, i.e. you can use OtherScene, OTHERscene, etc.
The scene in the build settings is not active (i.e. with the checkmark on the left disabled).
The scene isn't present at all in the build settings.
The full path of the scene is irrelevant btw, you can have OtherScene inside any dir/subdir in the Assets, and you'll need only the name of the scene. You need the full path of the scene only if you have more than scene with the same name in different paths (and anyway you shouldn't, it's such an obvious terrible practice).
EDIT: You say that you tried even without " (as you should do, when entering string fields in the inspector you don't need them), what happens if you modify the method body to:
public void LaunchSceneOnButtonClick(string sceneName)
{
SceneManager.LoadScene("OtherScene");
}
Try this and report back what happens please.
I had also the problem that
public void loadScene(string scene)
{
SceneManager.LoadScene(scene);
}
did not work for me in the first place. What did I do? I'm saving the names of my scenes in a List (read from a file) and from that List I choose the scene I need.
But somehow the reading progress seems to give me some unwanted characters so the scene loading does not work as intended. When I added .Trim() it started to work.
For a better understanding, here's an example:
string s = sceneList[1].Trim();
this.loadScene(s);
public void loadScene(string scene)
{
SceneManager.LoadScene(scene);
}
So make sure that you get rid of unwanted characters.

Swift/SpriteKit - Any way to pool/cache SKReferenceNodes?

Is there a way to pool/cache SKReferenceNodes in SpriteKit using Swift?
I am creating a game using xCodes visual level editor. I am creating different .sks files with the visual level editor that I am than calling in code when I need to. I am calling them in code because I am using them to create random levels or obstacles so I don't need all of them added to the scene at once.
At the moment I am doing it like this
I create a convince init method for SKReferenceNodes to init them with URLs. I am doing this because there is some sort of bug calling SKReferenceNodes by file name directly (https://forums.developer.apple.com/thread/44090).
Using such an extension makes makes the code a bit cleaner.
extension SKReferenceNode {
convenience init(roomName: String) {
let path: String
if let validPath = NSBundle.mainBundle().pathForResource(roomName, ofType: "sks") {
path = validPath
} else {
path = NSBundle.mainBundle().pathForResource("RoomTemplate", ofType: "sks")! // use empty roomTemplate as backup so force unwrap
}
self.init(URL: NSURL(fileURLWithPath: path))
}
}
and than in my scenes I can create them and add them like so (about every 10 seconds)
let room = SKReferenceNode(roomName: "Room1") // will cause lag without GCD
room.position = ...
addChild(room)
This works ok but I am getting some lag/stutter when creating these. So I am using GCD to reduce this to basically no stutter. It works well but I am wondering if I can preload all .sks files first.
I tried using arrays to do this but I am getting crashes and it just doesn't seem to work (I also get a message already adding node that has a parent).
I was trying to preload them like so at app launch
let allRooms = [SKReferenceNode]()
for i in 0...3 {
let room = SKReferenceNode(roomName: "Room\(i)")
allRooms.append(room)
}
and than use the array when I need too. This doesn't work however and I am getting a crash when trying to use code like this
let room = allRooms[0]
room.position =
addChild(room) // causes error/crash -> node already has parent
Has anyone done something similar? Is there another way I can pool/cache those reference nodes?. Am i missing something here?
Speaking about the SKReferenceNode preload, I think the policy to be followed is to load your object, find what kind they are and use the official preloading methods available in Sprite-Kit:
SKTextureAtlas.preloadTextureAtlases(_:withCompletionHandler:)
SKTexture.preloadTextures(_:withCompletionHandler:)
To avoid this kind of error you should create separate instances of the nodes.
Try to doing this:
let room = allRooms[0]
room.position = ...
room.removeFromParent()
addChild(room)
I just figured it out, I was just being an idiot.
Using arrays like I wanted to is fine, the problem I had which caused the crash was the following.
When the game scene first loads I am adding 3 rooms but when testing with the arrays I kept adding the same room
let room = allRooms[0]
instead of using a randomiser.
This obviously meant I was adding the same instance multiple times, hence the crash.
With a randomiser, that doesn't repeat the same room, this does not happen.
Furthermore I make sure to remove the room from the scene when I no longer need it. I have a node in the rooms (roomRemover) which fires a method to remove/create a new room once it comes into contact with the player.
This would be the code in DidBeginContact.
guard let roomToRemove = secondBody?.node.parent?.parent?.parent?.parent else { return }
// secondBody is the roomRemover node which is a child of the SKReferenceNode.
// If I want to remove the whole SKReferenceNode I need to add .parent 4 times.
// Not exactly sure why 4 times but it works
for room in allRooms {
if room == roomToRemove {
room.removeFromParent()
}
}
loadRandomRoom()
Hope this helps someone trying to do the same.