Accessing Unity package version at runtime - c#

I'm working on a custom analytics system (which is a custom package) and it would be super helpful to know which version of this package is integrated in the unity app when I'm looking into the data.
Is there any quick solution to retrieve the pacakge version at runtime ?

As said what you could do is using a ScriptableObject just for storing the version for the runtime later.
You could e.g. do it like
// See https://docs.unity3d.com/Manual/PlatformDependentCompilation.html
#if UNITY_EDITOR
using UnityEditor;
#endif;
public class PackageVersion : ScriptableObject
{
// See https://docs.unity3d.com/ScriptReference/HideInInspector.html
[HideInInspector] public string Version;
#if UNITY_EDITOR
// Called automatically after open Unity and each recompilation
// See https://docs.unity3d.com/ScriptReference/InitializeOnLoadMethodAttribute.html
[InitializeOnLoadMethod]
private static void Init()
{
// See https://docs.unity3d.com/ScriptReference/Compilation.CompilationPipeline-compilationFinished.html
// Removing the callback before adding it makes sure it is only added once at a time
CompilationPipeline.compilationFinished -= OnCompilationFinished;
CompilationPipeline.compilationFinished += OnCompilationFinished;
}
private static void OnCompilationFinished()
{
// First get the path of the Package
// This is quite easy since this script itself belongs to your package's assemblies
var assembly = typeof(PackageVersion).Assembly;
// See https://docs.unity3d.com/ScriptReference/PackageManager.PackageInfo.FindForAssembly.html
var packageInfo = PackageManager.PackageInfo.FindForAssembly();
// Finally we have access to the version!
var version = packageInfo.version;
// Now to the ScriptableObject instance
// Try to find the first instance
// See https://docs.unity3d.com/ScriptReference/AssetDatabase.FindAssets.html
// See https://docs.unity3d.com/ScriptReference/PackageManager.PackageInfo-assetPath.html
var guid = AssetDataBase.FindAssets($"t:{nameof(PackageVersion)}", packageInfo.assetPath). FirstOrDefault();
PackageVersion asset;
if(!string.isNullOrWhiteSpace(guid))
{
// See https://docs.unity3d.com/ScriptReference/AssetDatabase.GUIDToAssetPath.html
var path = AssetDatabase.GUIDToAssetPath(guid);
// See https://docs.unity3d.com/ScriptReference/AssetDatabase.LoadAssetAtPath.html
asset = AssetDatabase.LoadAssetAtPath<PackageVersion>(path);
}
else
{
// None found -> create a new one
asset = ScriptableObject.CreateInstance<PackageVersion>();
asset.name = nameof(PackageVersion);
// make it non editable via the Inspector
// See https://docs.unity3d.com/ScriptReference/HideFlags.NotEditable.html
asset.hideFlags = HideFlags.NotEditable;
// Store the asset as actually asset
// See https://docs.unity3d.com/ScriptReference/AssetDatabase.CreateAsset.html
AssetDataBase.CreateAsset(asset, $"{packageInfo.assetPath}/{nameof(PackageVersion)}");
}
asset.Version = version;
// See https://docs.unity3d.com/ScriptReference/EditorUtility.SetDirty.html
EditorUtility.SetDirty(asset);
}
#endif
}
Now wherever needed you can just reference that PackageVersion object and access the version on runtime.
Note: Typed on smartphone but I hope the idea gets clear

Related

ML.NET in Unity

I cannot figure out how to use ML.NET in Unity.
What I did:
Converted my project to be compatible with framework 4.x.
Converted api compatibility level to framework 4.x.
Made assets/plugins/ml folder and droped in Microsoft.ML apis with corresponding xmls.
Marked all ml.dlls platform settings to be only 86_64 compatible (this was redundant).
I can now:
Call ML apis and create MlContext, TextLoader, and do the training of a model. When a model is trained I can also evaluate the trained model, but...
I cannot:
When trying to get a prediction out of the model I get an error:
github comment on issue from 28.12.18 (there is also a whole project attached there, you can see the code there)
The same code works in visual studio solution.
public float TestSinglePrediction(List<double> signal, MLContext mlContext, string modelPath)
{
ITransformer loadedModel;
using (var stream = new FileStream(modelPath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
loadedModel = mlContext.Model.Load(stream);
}
var predictionFunction = loadedModel.MakePredictionFunction<AbstractSignal, PredictedRfd>(mlContext);
var abstractSignal = new AbstractSignal()
{
Sig1 = (float)signal[0],
Sig2 = (float)signal[1],
Sig3 = (float)signal[2],
Sig4 = (float)signal[3],
Sig5 = (float)signal[4],
Sig6 = (float)signal[5],
Sig7 = (float)signal[6],
Sig8 = (float)signal[7],
Sig9 = (float)signal[8],
Sig10 = (float)signal[9],
Sig11 = (float)signal[10],
Sig12 = (float)signal[11],
Sig13 = (float)signal[12],
Sig14 = (float)signal[13],
Sig15 = (float)signal[14],
Sig16 = (float)signal[15],
Sig17 = (float)signal[16],
Sig18 = (float)signal[17],
Sig19 = (float)signal[18],
Sig20 = (float)signal[19],
RfdX = 0
};
var prediction = predictionFunction.Predict(abstractSignal);
return prediction.RfdX;
}
This is the method that returns an error line:
var predictionFunction = loadedModel.MakePredictionFunction<AbstractSignal, PredictedRfd>(mlContext);
Starting with Unity 2018.1, unity can target .net 4.x. So you would need to set the .net version to .NET 4.x Equivalent, or .net standard 2.0 (https://blogs.unity3d.com/2018/03/28/updated-scripting-runtime-in-unity-2018-1-what-does-the-future-hold/) and make sure you add your dll to the project as a reference in visual studio. If you don't add it as a reference, then visual sudio doesn't know about it.
As Nick said in his post**, it should work with Unity if you follow those steps.
However, at the time I am writing this post, the ML.NET team has not yet done comprehensive testing with Unity, so it's not completely surprising that it's not working out of the box. This issue has been opened on the ML.NET Github repository. I suggest keeping an eye on that issue for the status of Unity support.
** Nick:
Starting with Unity 2018.1, unity can target .net 4.x. So you would need to set the .net version to .NET 4.x Equivalent, or .net standard 2.0 (https://blogs.unity3d.com/2018/03/28/updated-scripting-runtime-in-unity-2018-1-what-does-the-future-hold/) and make sure you add your dll to the project as a reference in visual studio. If you don't add it as a reference, then visual sudio doesn't know about it.
As follows is a bit modyfied Iris Example from https://learn.microsoft.com/en-us/dotnet/machine-learning/tutorials/iris-clustering (that one does not work anymore due to some ML API changes)
First make sure that you have the latest .net version installed and that your Unity version is at least 2019.2.0f1 (this was a preview version) or higher.
Creste a new unity project. Create a Plugins folder inside your Assets folder. Import all ML .Net APIs into that folder (Might be a foolish thing to do, but I have forehand created a visual studio soution and added all those APIs to that solution via nuget, and than just copied those dll files to Assets/Plugins folder in my unity project).
In Assets folder create an Data folder and paste iris.data file from https://github.com/dotnet/machinelearning/blob/master/test/data/iris.data into it.
Create a script named MLuTest and paste into it the following code:
public class MLuTest : MonoBehaviour{
static readonly string _dataPath = Path.Combine(Environment.CurrentDirectory, "Assets", "Data", "iris.data");
static readonly string _modelPath = Path.Combine(Environment.CurrentDirectory, "Assets", "Data", "IrisClusteringModel.zip");
MLContext mlContext;
void Start()
{
Debug.Log("starting...");
mlContext = new MLContext(seed: 0);
IDataView dataView = mlContext.Data.ReadFromTextFile<IrisData>(_dataPath, hasHeader: false, separatorChar: ',');
string featuresColumnName = "Features";
var pipeline = mlContext.Transforms
.Concatenate(featuresColumnName, "SepalLength", "SepalWidth", "PetalLength", "PetalWidth")
.Append(mlContext.Clustering.Trainers.KMeans(featuresColumnName, clustersCount: 3));//read and format flowery data
var model = pipeline.Fit(dataView);//train
using (var fileStream = new FileStream(_modelPath, FileMode.Create, FileAccess.Write, FileShare.Write))//save trained model
{
mlContext.Model.Save(model, fileStream);
}
var predictor = mlContext.Model.CreatePredictionEngine<IrisData, ClusterPrediction>(model);//predict
IrisData Setosa = new IrisData
{
SepalLength = 5.1f,
SepalWidth = 3.5f,
PetalLength = 1.4f,
PetalWidth = 0.2f
};
Debug.Log(predictor.Predict(Setosa).PredictedClusterId);
Debug.Log("...done predicting, now do what u like with it");
}
}
public class IrisData
{
[LoadColumn(0)]
public float SepalLength;
[LoadColumn(1)]
public float SepalWidth;
[LoadColumn(2)]
public float PetalLength;
[LoadColumn(3)]
public float PetalWidth;
}
public class ClusterPrediction
{
[ColumnName("PredictedLabel")]
public uint PredictedClusterId;
[ColumnName("Score")]
public float[] Distances;
}
This should work right out of the box ... well it did for me. Where you could mess up is when getting api files, they could be different version from mine or just some .net framework compatible. So get the content of my Plugins folder (mind you all those apis may not be necesery, do the cherrypicking yourselve):https://github.com/dotnet/machinelearning/issues/1886
It used to be (in previous unity versions) that some player settings had to be changed but i did not have to do it. But anhoo here are mine:
I hope this helps, since Unity update 19.2 I have not had any problems mentioned in previous posts in this thread.

Unity Cloud Build: post export method

Problem:
I can't seem to figure out the right signature for Unity cloud build's post export method. According to the documentation:
The fully-qualified name of a public static method you want us to call
after we finish the Unity build process (but before Xcode). For
example: ClassName.CoolMethod or NameSpace.ClassName.CoolMethod. No
trailing parenthesis, and it can't have the same name as your
Pre-Export method! This method must accept a string parameter, which
will receive the path to the exported Unity player (or Xcode project
in the case of iOS).
Here is my code:
public static void OnPostprocessDevBuildIOS(string ExportPath)
{
var projPath = ExportPath + "/Unity-iPhone.xcodeproj/project.pbxproj";
var proj = new PBXProject();
var nativeTarget =
proj.TargetGuidByName(PBXProject.GetUnityTargetName());
var testTarget =
proj.TargetGuidByName(PBXProject.GetUnityTestTargetName());
string[] buildTargets = {nativeTarget, testTarget};
proj.ReadFromString(File.ReadAllText(projPath));
proj.SetBuildProperty(buildTargets, "ENABLE_BITCODE", "NO");
File.WriteAllText(projPath, proj.WriteToString());
}
and here is the error:
I've tried multiple test method signatures and can't seem to get anything to work. I've even tried just a method that logs out the path.
Additional Information:
Unity Version: 5.3.1f
Unity Cloud Build: 5.3.1f
Target: iOS 8.0+
Also, my cloud build settings script is located in the editor folder as required.
Ok so I got the the bitCode disabling post process to work with the following code, but only when I build manually. When I build from cloud build, with no error the app freezes at the splash screen. When I build from my local machine, the app runs just fine.
[PostProcessBuild]
public static void OnPostprocessBuild(BuildTarget buildTarget, string path)
{
if (buildTarget == BuildTarget.iOS)
{
string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
PBXProject proj = new PBXProject();
proj.ReadFromString(File.ReadAllText(projPath));
string nativeTarget = proj.TargetGuidByName(PBXProject.GetUnityTargetName());
string testTarget = proj.TargetGuidByName(PBXProject.GetUnityTestTargetName());
string[] buildTargets = new string[]{nativeTarget, testTarget};
proj.SetBuildProperty(buildTargets, "ENABLE_BITCODE", "NO");
File.WriteAllText(projPath, proj.WriteToString());
}
}
I too had the same issue "splash screen stuck" right after launch....
I solved this issue. Please use the below code.
Tested in Unity 5.4.1p2 and Xcode 7.3.
using UnityEngine;
using System.Collections;
using UnityEditor;
using System.IO;
using UnityEditor.Callbacks;
#if UNITY_IOS
using UnityEditor.iOS.Xcode;
#endif
public class Postprocessor : AssetPostprocessor
{
#if UNITY_IOS
[PostProcessBuild]
public static void OnPostprocessBuild(BuildTarget buildTarget, string path)
{
if (buildTarget == BuildTarget.iOS)
{
string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
PBXProject proj = new PBXProject();
proj.ReadFromString(File.ReadAllText(projPath));
string target = proj.TargetGuidByName("Unity-iPhone");
proj.SetBuildProperty(target, "ENABLE_BITCODE", "false");
File.WriteAllText(projPath, proj.WriteToString());
// Add url schema to plist file
string plistPath = path + "/Info.plist";
PlistDocument plist = new PlistDocument();
plist.ReadFromString(File.ReadAllText(plistPath));
// Get root
PlistElementDict rootDict = plist.root;
rootDict.SetBoolean("UIRequiresFullScreen",true);
plist.WriteToFile(plistPath);
}
}
#endif
}
In fact OnPostprocessBuild is always called, so you don't have to put anything in post export method field, which is designed for more specific methods.

Dynamically Load and Activate a dataset (vuforia and unity)

I want to make a system that I can use to download various targets dynamically from my website without using "Cloud" system.
I also want to save the dataset to .xml and .dat formats which I want to activate from my saving device.
There are a lot of methods and pages to doing that with vuforia and unity, but unfortunately when I test it I receive an error for all of them.
It seems that i have made a mistake in my code or a vuforia class was changed.
For instance please look this link:
https://developer.vuforia.com/library/articles/Solution/Unity-Load-DataSet-from-SD-Card
I got Error: Using Vuforia;
I placed the .xml and .dat files in "Application.persistentDataPath + "/" + "Building1.xml"
i used this Script "DataSetLoadBehavior" that attached "AR Camera and placed my code in it. I got an Error:
NullReferenceException: Object reference not set to an instance of an
object DataSetLoadBehaviour.OnInitialized () (at Assets/Qualcomm
Augmented Reality/Scripts/DataSetLoadBehaviour.cs:49)
DataSetLoadBehaviour.Start () (at Assets/Qualcomm Augmented
Reality/Scripts/DataSetLoadBehaviour.cs:80)
My code is this:
unity 4.2 pro and vuforia 2.8.9 or 3.0.9
/*==============================================================================
Copyright (c) 2010-2014 Qualcomm Connected Experiences, Inc.
All Rights Reserved.
Confidential and Proprietary - Qualcomm Connected Experiences, Inc.
==============================================================================*/
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// This behaviour allows to automatically load and activate one or more DataSet on startup
/// </summary>
public class DataSetLoadBehaviour : DataSetLoadAbstractBehaviour
{
[HideInInspector, SerializeField]
public List<string> mDataSetsToActivate2 = new List<string>();
[SerializeField, HideInInspector]
public List<string> mDataSetsToLoad2 = new List<string>();
protected DataSetLoadBehaviour()
{
}
private void OnDestroy()
{
QCARAbstractBehaviour behaviour = (QCARAbstractBehaviour) UnityEngine.Object.FindObjectOfType(typeof(QCARAbstractBehaviour));
if (behaviour != null)
{
}
}
public void OnInitialized()
{
if (QCARRuntimeUtilities.IsQCAREnabled())
{
foreach (string str in this.mDataSetsToLoad2)
{
if (!DataSet.Exists(str, QCARUnity.StorageType.STORAGE_ABSOLUTE))
{
Debug.LogError("Data set " + str + " does not exist.");
}
else
{
ImageTracker tracker = TrackerManager.Instance.GetTracker<ImageTracker>();
DataSet dataSet = tracker.CreateDataSet();
if (!dataSet.Load(str))
{
Debug.LogError("Failed to load data set " + str + ".");
}
else if (this.mDataSetsToActivate2.Contains(str))
{
tracker.ActivateDataSet(dataSet);
}
}
}
}
}
public void OnTrackablesUpdated()
{
}
private void Start()
{
QCARAbstractBehaviour behaviour = (QCARAbstractBehaviour) UnityEngine.Object.FindObjectOfType(typeof(QCARAbstractBehaviour));
if (behaviour != null)
{
mDataSetsToLoad2.Add(Application.persistentDataPath + "/" + "Building1.xml");
OnInitialized();
}
}
public override void AddOSSpecificExternalDatasetSearchDirs()
{
#if UNITY_ANDROID
if (Application.platform == RuntimePlatform.Android)
{
// Get the external storage directory
AndroidJavaClass jclassEnvironment = new AndroidJavaClass("android.os.Environment");
AndroidJavaObject jobjFile = jclassEnvironment.CallStatic<AndroidJavaObject>("getExternalStorageDirectory");
string externalStorageDirectory = jobjFile.Call<string>("getAbsolutePath");
// Get the package name
AndroidJavaObject jobjActivity = new AndroidJavaClass("com.unity3d.player.UnityPlayer").GetStatic<AndroidJavaObject>("currentActivity");
string packageName = jobjActivity.Call<string>("getPackageName");
// Add some best practice search directories
//
// Assumes just Vufroria datasets extracted to the files directory
AddExternalDatasetSearchDir(externalStorageDirectory + "/Android/data/" + packageName + "/files/");
// Assume entire StreamingAssets dir is extracted here and our datasets are in the "QCAR" directory
AddExternalDatasetSearchDir(externalStorageDirectory + "/Android/data/" + packageName + "/files/QCAR/");
}
#endif //UNITY_ANDROID
}
void Update()
{
}
}
Yeah, Vuforia has has changed a lot.
You will now have to include Vuforia as a header in order for it to work
using Vuforia;
Hope this works.
If it says Vuforia hasn't been found it's probably because you haven't imported the Unitypackage for Vuforia. You can follow these instructions.
Also, I believe you haven't followed the steps to Migrating your Unity Project. The new Vuforia doesn't support ImageTracker anymore, hence you will have to change all instances of ImageTracker to ObjectTracker

BundleTransformer.Less inject variables depending on context/request

We would like the use the bundling mechanism of System.Web.Optimization in combination with the Less transformer.
The problem is that the same application/server serves pages for different branded websites. So depending on the 'SiteContext' the same .less files are used but different values should be used by the .less variables. So we want the (re)use the same less files but with different variables depending on the context of the request.
I tried a couple of different theories:
In all 3 cases I setup different bundles depending on the SiteContext.
1 inject an #import directive with the themed variables by using a custom VirtualPathProvider that intercepts the variables.less file.
So I have:
the styling file eg: header.less (imports the variables file)
the variables file: variables.less
a themed variables file: variables-theme.less (injected in variables.less via the VirtualPathProvider)
This is not working because the BundleTransformer cache sees this as the same file and doesn't know about the SiteContext. The cache key is based on the Url of the IAsset and we cannot influence this behavior.
2 Replace the variables.less import by variables-themed.less with an custom transformer that runs before the Less transformer.
Again no luck, same caching issues.
And as a side effect, the extra transformer was not called in debug because the assets are not bundled but called individually by the LessAssetHandler. This could be solved by writing your own AssetHandler that calls all required transformers.
3 create themed Asset names that are resolved by a custom VirtualPathProvider
Eg. Add header-themeX.less to the bundle, this file doesn't exist but you resolve this file to header.less and use method 2 to set the correct variables file import. (replace the import of the variables.less to the themed version).
Once again no luck. I think this could solve the caching issue if it wasn't for the Bundle.Include(string virtualPath) that does a File.Exists(path) internally. It doesn't pass via the CustomVirtualPathProvider.
Am I looking to deep to solve this?
All ideas are welcome, I can imagine that this will become a problem to more and more people as the System.Web.Optimization library gets more popular...
Keep in mind that:
we have a lot of .less/css files
we will have 5 or so themes
we like to keep things working in visual studio (that is why header.less has a ref. to variables.less)
Thanks for any feedback.
Michael!
You use the Microsoft ASP.NET Web Optimization Framework and the Bundle Transformer in multi-tenant environment, so you need to replace some components of the System.Web.Optimization and create own versions of the debugging HTTP-handlers (see «Problem: LESS file imports are added to BundleResponse.Files collection» discussion). As far as I know, Murat Cakir solve all these problems in the SmartStore.NET project.
In the Bundle Transformer there are 2 ways to inject of LESS-variables:
Look a properties GlobalVariables and ModifyVariables of LESS-translator:
using System.Collections.Generic;
using System.Web.Optimization;
using BundleTransformer.Core.Builders;
using BundleTransformer.Core.Orderers;
using BundleTransformer.Core.Transformers;
using BundleTransformer.Core.Translators;
using BundleTransformer.Less.Translators;
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
var nullBuilder = new NullBuilder();
var nullOrderer = new NullOrderer();
var lessTranslator = new LessTranslator
{
GlobalVariables = "my-variable='Hurrah!'",
ModifyVariables = "font-family-base='Comic Sans MS';body-bg=lime;font-size-h1=50px"
};
var cssTransformer = new CssTransformer(new List<ITranslator>{ lessTranslator });
var commonStylesBundle = new Bundle("~/Bundles/BootstrapStyles");
commonStylesBundle.Include(
"~/Content/less/bootstrap-3.1.1/bootstrap.less");
commonStylesBundle.Builder = nullBuilder;
commonStylesBundle.Transforms.Add(cssTransformer);
commonStylesBundle.Orderer = nullOrderer;
bundles.Add(commonStylesBundle);
}
}
Create a custom item transformation:
using System.Text;
using System.Web.Optimization;
public sealed class InjectContentItemTransform : IItemTransform
{
private readonly string _beforeContent;
private readonly string _afterContent;
public InjectContentItemTransform(string beforeContent, string afterContent)
{
_beforeContent = beforeContent ?? string.Empty;
_afterContent = afterContent ?? string.Empty;
}
public string Process(string includedVirtualPath, string input)
{
if (_beforeContent.Length == 0 && _afterContent.Length == 0)
{
return input;
}
var contentBuilder = new StringBuilder();
if (_beforeContent.Length > 0)
{
contentBuilder.AppendLine(_beforeContent);
}
contentBuilder.AppendLine(input);
if (_afterContent.Length > 0)
{
contentBuilder.AppendLine(_afterContent);
}
return contentBuilder.ToString();
}
}
And register this transformation as follows:
using System.Web.Optimization;
using BundleTransformer.Core.Orderers;
using BundleTransformer.Core.Bundles;
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
var nullOrderer = new NullOrderer();
const string beforeLessCodeToInject = #"#my-variable: 'Hurrah!';";
const string afterLessCodeToInject = #"#font-family-base: 'Comic Sans MS';
#body-bg: lime;
#font-size-h1: 50px;";
var commonStylesBundle = new CustomStyleBundle("~/Bundles/BootstrapStyles");
commonStylesBundle.Include(
"~/Content/less/bootstrap-3.1.1/bootstrap.less",
new InjectContentItemTransform(beforeLessCodeToInject, afterLessCodeToInject));
commonStylesBundle.Orderer = nullOrderer;
bundles.Add(commonStylesBundle);
}
}
Both ways have disadvantage: the injection of LESS-variables does not work in debug mode.

Change application type with Mono.Cecil?

How can I modify an application from Console Application Type to Windows Application Type and vice versa with Mono.Cecil?
To convert a console .exe to windows .exe, you can use:
var file = "foo.exe";
var module = ModuleDefinition.ReadModule (file);
// module.Kind was previously ModuleKind.Console
module.Kind = ModuleKind.Windows;
module.Write (file);
The other way around is as simple as choosing the appropriate ModuleKind value. From Cecil's source:
public enum ModuleKind {
Dll,
Console,
Windows,
NetModule,
}
For people who needed more help on this like me :)
you may need the apt pacakge libmono-cecil-cil-dev
//mono-cecil-set-modulekind-windows.cs
using System;
using Mono.Cecil;
namespace CecilUtilsApp {
class CecilUtils {
static void Main(string[] args) {
var file = args[0];
var module = ModuleDefinition.ReadModule (file);
module.Kind = ModuleKind.Windows;
module.Write (file);
}
}
}
// -----
//Makefile
//mono-cecil-set-modulekind-eq-windows.exe:
// mcs $(shell pkg-config --libs mono-cecil) ./mono-cecil-set-modulekind-windows.cs
./mono-cecil-set-modulekind-windows.exe myprog.exe

Categories