Unity3D has a Screen class with an orientation property that allows you to force orientation in code, which lets you have different scenes with different orientations (useful in mini-games).
Setting this works fine for Android but crashes on iOS. What is the fix?
The problem is the file UnityViewControllerBaseiOS.mm that gets generated during the build for iOS has an assert in it which inadvertently prevents this property from being used. It is possible to create a post-build class that runs after the iOS build files have been generated that can alter the generated code before you compile it in XCode.
Just create a C# script named iOSScreenOrientationFix.cs and paste in the following code - adapted from this Unity3D forum post. Note that this file must be placed in a folder named Editor, or in one of its subfolders.
using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
using System.IO;
namespace Holovis
{
public class iOSScreenOrientationFix : MonoBehaviour
{
#if UNITY_CLOUD_BUILD
// This method is added in the Advanced Features Settings on UCB
// PostBuildProcessor.OnPostprocessBuildiOS
public static void OnPostprocessBuildiOS (string exportPath)
{
Debug.Log("OnPostprocessBuildiOS");
ProcessPostBuild(BuildTarget.iPhone,exportPath);
}
#endif
[PostProcessBuild]
public static void OnPostprocessBuild(BuildTarget buildTarget, string path)
{
#if !UNITY_CLOUD_BUILD
ProcessPostBuild(buildTarget, path);
#endif
}
private static void ProcessPostBuild(BuildTarget buildTarget, string path)
{
if (buildTarget == BuildTarget.iOS)
{
#if !UNITY_CLOUD_BUILD
Debug.Log("Patching iOS to allow setting orientation");
#endif
string filePath = Path.Combine(path, "Classes");
filePath = Path.Combine(filePath, "UI");
filePath = Path.Combine(filePath, "UnityViewControllerBaseiOS.mm");
Debug.Log("File Path for View Controller Class: " + filePath);
string classFile = File.ReadAllText(filePath);
string newClassFile = classFile.Replace("NSAssert(UnityShouldAutorotate()", "//NSAssert(UnityShouldAutorotate()");
File.WriteAllText(filePath, newClassFile);
}
}
}
}
You can set it in a scene by attaching the following MonoBehaviour to a game object
using UnityEngine;
namespace Holovis
{
public class SetDeviceOrientation : MonoBehaviour
{
public ScreenOrientation orientation = ScreenOrientation.AutoRotation;
void Awake()
{
Screen.orientation = orientation;
}
}
}
NOTE: Setting Screen.orientation has no effect when running on desktop, in the Unity editor, or when testing using Unity Remote.
Related
This was originally coded in unity 2018 then ported to unity 2019 because my main project is on that version. It works perfectly in unity 2018 (aside from that button.clicked += used to be button.AddListener())
The only relevant bit of the code is lines 12 - 17 but who knows.
Script is attached to object with the Button component.
Unity 2019.4.11f1
Returns the following error with the setup listed above:
ArgumentException: GetComponent requires that the requested component 'Button' derives from MonoBehaviour or Component or is an interface.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UIElements;
using UnityEngine.Events;
public class ButtonProperties : MonoBehaviour {
public ButtonType buttonType;
public string dest;
Button button;
void Start()
{
button = GetComponent<Button>();
//perform different functions dependant on the button type
if (buttonType == ButtonType.Scene)
{
//add ButtonLoadScene as a listener to the the button
button.clicked += ButtonLoadScene;
}
else if (buttonType == ButtonType.Menu)
{
button.clicked += ButtonLoadMenu;
}
//change button text to "Load Scene" + the name of the scene
string str = "Load " + dest;
GetComponentInChildren<TextElement>().text = str;
}
//private scene loader because event listeners cant take arguments >:(
void ButtonLoadScene()
{
SceneManager.LoadScene(dest);
}
void ButtonLoadMenu()
{
//array of all canvases in the scene
Canvas[] canvases = GameObject.FindObjectsOfType<Canvas>();
//foreach canvas
foreach (Canvas canvas in canvases)
{
//if the canvases name is the desired name
if (canvas.name == dest)
{
//turn all canvases off
foreach (Canvas c in canvases)
{
c.enabled = false;
}
//turn current canvas on
canvas.enabled = true;
//break loop
break;
}
}
}
}
public enum ButtonType
{
Scene,
Menu
};
I also tested this by adding a NewBehaviourScript to the button with Button button; and, in the start function, Start() {button = GetComponent<Button>();}
Same error
I believe you are using the wrong namespace for your button. Try changing:
using UnityEngine.UIElements;
to
using UnityEngine.UI;
There is a Button in both namespaces, but the one in UnityEngine.UI is likely the component you're working with.
Had somebody else on the team test the build, as I started to think from the comment thread, I was missing the UI library. Changing it to the UI library was the solution but not for me until I restarted the computer. Thank you Thomas Finch for the help.
I'm trying to save the name of the last level my player gets in and I wanted to use Json and the streaming assets folder, so I get a JSON file in a folder in my unity project
└───Assets
└───[...] (all my other unity project folder)
└───StreamingAssets
lastlevel.json
I created an object ProgressSavior with and attached script to save in this JSON file the name of the level and also add a function to get the level name, so that in my main menu when I press play my last scene played is loaded
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.SceneManagement;
public class ProgressSavior : MonoBehaviour
{
string chemin = Application.streamingAssetsPath + "/lastlevel.json";
Regex levelRegex = new Regex(#"^level_[1-9][0-9]*$");
string Json;
string levelname;
Level Saved;
// Start is called before the first frame update
void Start()
{
levelname = SceneManager.GetActiveScene().name;
Json = File.ReadAllText(chemin);
Saved = JsonUtility.FromJson<Level>(Json);
if (levelRegex.IsMatch(levelname)) {
save();
}
}
bool ShouldWeSave(string s)
{
Debug.Log(s);
Debug.Log(Saved.name);
return int.Parse(s.Split('_')[1]) > int.Parse(Saved.name.Split('_')[1]);
}
public void save()
{
if (ShouldWeSave(levelname))
{
Saved.name = levelname;
Json = JsonUtility.ToJson(Saved);
File.WriteAllText(chemin, Json);
} else
{
Debug.LogWarning("saving was Useless");
}
}
public string load()
{
return Saved.name;
}
}
public class Level
{
public string name;
}
And on PC it work wonderfully but when I build the project on android it won't work and I don't understand why,
On Android, the path will be different, as mentioned in the official documentation here.
Use "jar:file://" + Application.dataPath + "!/assets" instead.
GameContainer script:
public class GameContainer : MonoBehaviour
{
public List<Game> Games;
public void AddGame()
{
Games.Add(new Game());
}
}
Game Class:
[System.Serializable]
public class Game
{
public List<GameType> gameTypes;
public void addGameType()
{
gameTypes.Add(new GameType());
}
}
GameType Class
[System.Serializable]
public class GameType
{
}
and my OnInspectorGUI method in custom editor
public override void OnInspectorGUI()
{
var targetScript = target as GameContainer;
var centeredStyle = GUI.skin.GetStyle("Label");
centeredStyle.alignment = TextAnchor.UpperCenter;
centeredStyle.fontStyle = FontStyle.Bold;
EditorGUILayout.LabelField("Games", centeredStyle);
for(int i = 0;i<targetScript.Games.Count; i++)
{
Game game = targetScript.Games[i];
//here is the LINE CAUSING A PROBLEM
Debug.Log(game.gameTypes.Count);
GUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.Space();
GUILayout.BeginVertical("Game Types", "window");
if (GUILayout.Button("+"))
{
game.addGameType();
}
GUILayout.EndVertical();
GUILayout.EndVertical();
EditorGUILayout.Space();
}
if (GUILayout.Button("+"))
{
targetScript.AddGame();
}
}
the problem is with this line:
//here is the LINE CAUSING A PROBLEM
Debug.Log(game.gameTypes.Count);
when i hit AddGame Button, all draw calls after this line will be ignored for newly added element and its not shown till next change in code and refresh in the editor, if i remove this line, everything works just fine.
but if i try to use gameType list by any mean, it will not show correct view in inspector.
what the problem is?
I recommend using EditorGUILayout instead of old GUILayout class.
here's link to document for it:
https://docs.unity3d.com/ScriptReference/EditorGUILayout.html
Although unity introduced a new way to make custom editors lately that is called UI Elements.
You can create your own editors with layered architecture with xml,css like language.
here's some useful YouTube links for you:
https://www.youtube.com/watch?v=sVEmJ5-dr5E
https://www.youtube.com/watch?v=MNNURw0LeoQ
https://www.youtube.com/watch?v=GSRVI1HqlF4
And lastly you can check this beautiful editor attributes too:
https://github.com/dbrizov/NaughtyAttributes
Problem
The Unity project I build is targeting iOS, Android and Windows X64. I have two scenes, A and B, whereas A is the main menu scene of my game and scene B is kind of a level selection scene in which the user can choose a level to play. From scene A I can navigate to scene B and back again. When running the game in the Unity Editor, everything behaves as expected. The problem arises, when I run the game on the target platforms (real devices). Then, when navigating like A --> B --> A, I end up in scene A being rendered as a black screen, except the FPSIndicator game object which is still rendered and doing its job. The FPSIndicator game object is a small piece of code which draws itself to the scene in the OnGUI callback. Nothing else is displayed.
Setup of Scene A
I have a Unity UI button there ("Drag and Drop"), which, when clicked, loads scene B using this code:
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.EventSystems;
public class GameTypeButtonController : MonoBehaviour, IPointerClickHandler
{
public ButtonSounds ButtonSounds;
public string SceneNameToLoad;
public GameType GameType;
public void OnPointerClick(PointerEventData eventData)
{
StartCoroutine(Do());
}
private IEnumerator Do()
{
var animator = gameObject.GetComponent<Animator>();
if (animator != null)
{
animator.SetTrigger("Clicked");
}
var audioSource = gameObject.GetComponent<AudioSource>();
if (audioSource != null)
{
var clip = GetRandomAudioClip(ButtonSounds);
audioSource.clip = clip;
audioSource.Play();
yield return new WaitWhile(() => audioSource.isPlaying);
}
Logger.LogInfo("[GameTypeButtonController.Do] Setting game type " + GameType);
GameManager.Instance.CurrentGameType = GameType;
SceneManager.LoadScene(SceneNameToLoad);
}
private AudioClip GetRandomAudioClip(ButtonSounds buttonSounds)
{
var numberOfAudioClips = buttonSounds.AudioClips.Length;
var randomIndex = Random.Range(0, numberOfAudioClips);
return buttonSounds.AudioClips[randomIndex];
}
}
The scene looks like this:
Setup of Scene B
In scene B, I have a button in the lower left which brings me back to scene A when clicked. This is not a Unity UI button, but a regular sprite with a CircleCollider2D attached. The script on that button looks like this:
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
public class HomeButtonController : MonoBehaviour
{
public ButtonSounds ButtonSounds;
public string SceneNameToLoad;
void OnMouseDown()
{
StartCoroutine(Do());
}
private IEnumerator Do()
{
var animator = gameObject.GetComponent<Animator>();
if (animator != null)
{
animator.SetTrigger("Clicked");
}
var audioSource = gameObject.GetComponent<AudioSource>();
if (audioSource != null)
{
var clip = GetRandomAudioClip(ButtonSounds);
audioSource.clip = clip;
audioSource.Play();
yield return new WaitWhile(() => audioSource.isPlaying);
}
SceneManager.LoadScene(SceneNameToLoad);
}
private AudioClip GetRandomAudioClip(ButtonSounds buttonSounds)
{
var numberOfAudioClips = buttonSounds.AudioClips.Length;
var randomIndex = UnityEngine.Random.Range(0, numberOfAudioClips);
return buttonSounds.AudioClips[randomIndex];
}
}
The scene looks like this:
General Notes
Two objects use DontDestroyOnLoad: GameManager and MusicPlayer.
What I have checked so far
Scenes are properly referenced in Build Settings
As I use Unity Cloud Build, I have disabled the Library Caching feature to avoid issues with old build artifacts (so every time I build, I do a proper, clean build)
I can locally build all three platforms (Unity reports it as "Build successful"). So no build errors.
I am using LoadSceneMode.Single (default)
I am using the same Unity version locally and in Unity Cloud Build: 2018.3.0f2
Update 2019-02-19:
When I navigate from a third scene C back to scene A using the same mechanism (a sprite button calling a coroutine), I also end up on the very same black screen. So the issue probably exists within scene A?
Update 2 from 2019-02-19:
Here's my GameManager code:
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public EventHandler<LevelStartedEventArgs> LevelStarted;
public EventHandler<LevelFinishedEventArgs> LevelFinished;
// General
public GameType CurrentGameType;
public GameScene CurrentScene;
public int CurrentLevel;
public static GameManager Instance;
public GameLanguage Language;
public bool IsMusicEnabled;
private string gameStateFile;
void Start()
{
if (Instance == null)
{
gameStateFile = Application.persistentDataPath + "/gamestate.dat";
Load(gameStateFile);
DontDestroyOnLoad(gameObject);
Instance = this;
}
else if (Instance != this)
{
Destroy(gameObject);
}
}
public void Save()
{
Logger.LogInfo("[GameManager.Save] Saving game state to " + gameStateFile);
var bf = new BinaryFormatter();
var file = File.Create(gameStateFile);
var gameState = new GameState();
gameState.Language = Language;
gameState.IsMusicEnabled = IsMusicEnabled;
bf.Serialize(file, gameState);
file.Close();
Logger.LogInfo("[GameManager.Save] Successfully saved game state");
}
public void Load(string gameStateFile)
{
Logger.LogInfo("[GameManager.Load] Loading game state from " + gameStateFile);
if (File.Exists(gameStateFile))
{
var bf = new BinaryFormatter();
var file = File.Open(gameStateFile, FileMode.Open);
var gameState = (GameState)bf.Deserialize(file);
file.Close();
Language = gameState.Language;
IsMusicEnabled = gameState.IsMusicEnabled;
}
Logger.LogInfo("[GameManager.Load] Successfully loaded game state");
}
[Serializable]
class GameState {
public GameLanguage Language;
public bool IsMusicEnabled;
}
}
Thanks for any hints!
So, I finally managed to solve the issue on my own. Here's a list of steps I went through in order to eliminate my issue:
I installed the "Game development with Unity" workload in Visual Studio 2017 in order to be able to use the Visual Studio Debugger. That gave me the possibility to properly debug my scripts (setting breakpoints, inspect values, ...) and go through them, step by step. See this link for some details on how to accomplish this. Unfortunately, this did not solve my problem yet, but gave a good basis for troubleshooting my issue.
As the issue described above, also arised on Windows x64 builds, I've decided to troubleshoot my issue on a windows build. So I reconfigured my Windows build in the Build Player Settings, by setting Script Debugging to true, Development build to true, Copy PDB filesto true. Then I've ran another windows build.
If you now run your game, it will show a development console in the lower left screen, giving a lot of useful information (exceptions thrown, etc...). Also you can open the log file directly from within the game, which provides a LOT of useful informations to tackle down the real issue.
So, I had some PlatformNotSupportedExceptions in the logs, which I was able to fix and some other errors. Now it is working.
Although, I've answered my own question, I hope this will be useful to some of you.
I have an AnimatorController and want to override it at runtime with another clip. The project should run as WebGL project so it is not possbile to create a new AnimatorController because it is in the UnityEditor namespace which is not exported when building the project as WebGL project (if you have an solution for that you can post it too).
So i tried to use the AnimatorOverrideController but I don't know why the animation isn't playing...
Basic_Run_02 is loaded by default before pressing play and it should be overridden with the clip in the SadWalk.fbx.
Anyone an idea why the animation is not playing?
public class AnimationHelper : MonoBehaviour
{
public AnimationClip state;
public AnimatorOverrideController overrideController;
void Start(){
state = Resources.Load("SadWalk", typeof(AnimationClip)) as AnimationClip;
PlayMotion (state.name);
}
void PlayMotion(string name){
RuntimeAnimatorController myController = gameObject.GetComponent<Animator>().runtimeAnimatorController;
overrideController = new AnimatorOverrideController ();
overrideController.name = "Test";
overrideController.runtimeAnimatorController = gameObject.GetComponent<Animator>().runtimeAnimatorController;
overrideController ["Basic_Run_02"] = state;
//Debug.Log (overrideController.clips.ToString);
gameObject.GetComponent<Animator>().runtimeAnimatorController = overrideController;
gameObject.GetComponent<Animator> ().StartPlayback ();
}
}
The Animator and RuntineAnimatorController classes are also available in the UnityEngine namespace, so you should do a couple of changes like below:
gameObject.GetComponent<UnityEngine.Animator>().runtimeAnimatorController = overrideController;
And the overrideController should be of type UnityEngine.RuntimeAnimatorController, example:
public AnimatorOverrideController overrideController;
Now if you export to WebGL you will have your code working.