Does Unity have a built in function that is triggered when Screen.height or Screen.width change? - unity3d

I have build some custom UI scaling features for a mobile app built in Unity to compensate for various phone screen ratios. I would also like protection against screen size changes, as with the rise of foldable phones, I presume that is a risk.
The preliminary solution I have implemented is to have a script attached to the objects that must potentially resize, structured like this:
public class ScreenElementSizer : MonoBehaviour {
private int screenWidth_1 = Screen.width;
private int screenHeight_1 = Screen.height;
// Start is called before the first frame update
void Start() {
ScreenResized();
}
// Resizing functions here
private void ScreenResized() {
}
// On every screen refresh
void Update()
{
if ((Screen.width != screenWidth_1) || (Screen.height != screenHeight_1)) {
ScreenResized();
screenWidth_1 = Screen.width;
screenHeight_1 = Screen.height;
}
}
As you can see it's a pretty simple solution where I'm storing the prior sample's Screen.width and Screen.height in variables (screenWidth_1 & screenHeight_1), then comparing them on each sample (update) and if there is a discrepancy (screen change) trigger the resizer script.
It works fine of course. I think this is pretty standard coding logic. It's probably not expensive to run one/two extra if statements like this per object per sample.
I'm just new to Unity and wondering if there's any better, built in, or more efficient way to do this task.
To be clear, I am aware of the built in canvas resizer tools that scale based on width and height. I specifically am referring to the case where you want to apply some special script based resizing logic like this when the screen size changes.
Thanks for any thoughts.

There is no built-in event, but you can make your own event.
DeviceChange.cs -
using System;
using System.Collections;
using UnityEngine;
public class DeviceChange : MonoBehaviour
{
public static event Action<Vector2> OnResolutionChange;
public static event Action<DeviceOrientation> OnOrientationChange;
public static float CheckDelay = 0.5f; // How long to wait until we check again.
static Vector2 resolution; // Current Resolution
static DeviceOrientation orientation; // Current Device Orientation
static bool isAlive = true; // Keep this script running?
void Start() {
StartCoroutine(CheckForChange());
}
IEnumerator CheckForChange(){
resolution = new Vector2(Screen.width, Screen.height);
orientation = Input.deviceOrientation;
while (isAlive) {
// Check for a Resolution Change
if (resolution.x != Screen.width || resolution.y != Screen.height ) {
resolution = new Vector2(Screen.width, Screen.height);
if (OnResolutionChange != null) OnResolutionChange(resolution);
}
// Check for an Orientation Change
switch (Input.deviceOrientation) {
case DeviceOrientation.Unknown: // Ignore
case DeviceOrientation.FaceUp: // Ignore
case DeviceOrientation.FaceDown: // Ignore
break;
default:
if (orientation != Input.deviceOrientation) {
orientation = Input.deviceOrientation;
if (OnOrientationChange != null) OnOrientationChange(orientation);
}
break;
}
yield return new WaitForSeconds(CheckDelay);
}
}
void OnDestroy(){
isAlive = false;
}
}
Implementation -
DeviceChange.OnOrientationChange += MyOrientationChangeCode;
DeviceChange.OnResolutionChange += MyResolutionChangeCode;
DeviceChange.OnResolutionChange += MyOtherResolutionChangeCode;
void MyOrientationChangeCode(DeviceOrientation orientation)
{
}
void MyResolutionChangeCode(Vector2 resolution)
{
}
Credit - DougMcFarlane

Related

Simple Coroutine Animation script breaks unity

Alright, so I'm using a coroutine to wait 6 seconds before executing a method to change a float in an animator, really simple stuff. My issue is that something in this script is causing my unity editor to completely lock up when I place it on a gameobject, and I don't know why. I don't think I have any infinite loops going, but I'm not sure. Anyone have any ideas? thx ahead of time.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class handanimatordock : MonoBehaviour
{
public Animator hand;
private float Blendfloat;
bool ChangeHand = false;
private float timefloat = 0.0f;
// Start is called before the first frame update
void Start()
{
StartCoroutine(waiter());
}
// Update is called once per frame
void Update()
{
}
public void changeHands()
{
if (ChangeHand == false)
{
ChangeHand = true;
while (Blendfloat != 1.0f)
{
Blendfloat = Blendfloat + 0.01f;
hand.SetFloat("Blend", Blendfloat);
}
}
else
{
ChangeHand = false;
while (Blendfloat != 0.0f)
{
Blendfloat = Blendfloat - 0.01f;
hand.SetFloat("Blend", Blendfloat);
}
}
}
IEnumerator waiter()
{
//Wait for 4 seconds
yield return new WaitForSeconds(6);
changeHands();
StartCoroutine(waiter());
}
}
You probably have an infinite loop in
while (Blendfloat != 1.0f)
{
Blendfloat = Blendfloat + 0.01f;
hand.SetFloat("Blend", Blendfloat);
}
Never directly compare two float values using == or !=.
Due to floating point impression a value like
10f * 0.1f
might end up being 1.000000001 or 0.99999999 though logically you would expect exactly 1. So your condition is probably never false!
Usually you rather give it a certain range like e.g.
while(Mathf.Abs(Blendfloat - 1) > certainThreshold)
Unity has for that Mathf.Approximately
while(!Mathf.Approximately(Blendfloat, 1f)
which basically equals comparing to a threshold of Mathf.Epsilon
which(Math.Abs(Blendfloat - 1) > Mathf.Epsilon)
Note that anyway what you have right now will execute the entire loop in one single frame.
If you really wanted it to fade over time you need to do one iteration per frame in e.g. a Coroutine!

What I Have Learned About Unity ScrollRect / ScrollView Optimization / Performance

ScrollView performance is a real drag (get it?), especially on mobile platforms. I frequently found myself getting less that 15 fps which left the user experience feeling jolting and underwhelming. After a lot of research and testing I have compiled a checklist to drastically improve performance. I now get at least 30 fps, with the majority of the CPU time allocated to WaitForTargetFPS.
I hope this helps anyone who is also having trouble in this area. Optimization solutions are hard to come by. Feel free to use and modify any of my code here.
ONE:
.GetComponent<>() calls are inefficient, especially outside the editor. Avoid using these in any kind of Update() method.
TWO:
OnValueChanged() is called every frame that the ScrollView is being dragged. It is therefore in a sense equivalent to Update() so you should avoid using .GetComponent<>() calls in this method.
THREE:
Whenever any element on a Canvas is changed the entire Canvas must rebuild its batches. This operation can be very expensive. It is therefore recommended to split your UI elements across at least two Canvases, one for elements that are changed rarely or never and one elements that change often.
Whenever a ScrollView scrolls the entire Canvas it is on is dirtied. It is therefore recommended that you have each ScrollView on a separate Canvas.
Unity Canvas Rebuilds Explanation: https://unity3d.com/learn/tutorials/topics/best-practices/fill-rate-canvases-and-input?playlist=30089
FOUR:
EventSystem.Update() handles the input detection in a scene, using raycasts to filter through the hierarchy in order to find a component to accept this input. Therefore these calculations are only done when interacting with the scene, like when a ScrollView is being scrolled. Removing unnecessary RaycastTarget properties from graphics and text will improve this processing time. It mightn't make a big difference, but if you're not careful enough objects can make the input handling time really add up.
FIVE:
With any kind of mask component, even a RectMask2D, all objects in a ScrollView are batched and rendered. If you have a lot of elements in your ScrollView, it is recommended that you utilize some kind of pooling solution. There are many of these available on the app store.
Unity Pooling Explanation: https://unity3d.com/learn/tutorials/topics/best-practices/optimizing-ui-controls
If, however, your project is incompatible with this, needing persistent elements, I would recommend that you hide your off screen objects to reduce performance overhead. Transform.SetParent() and GameObject.SetActive() are both resource intensive methods, instead attach a CanvasGroup component to each element and adjust the alpha value to achieve the same effect.
Here is a static script to detect if an object is visible or not and to set the alpha accordingly:
using UnityEngine;
using UnityEngine.UI;
public class ScrollHider : MonoBehaviour {
static public float contentTop;
static public float contentBottom;
static public bool HideObject(GameObject givenObject, CanvasGroup canvasGroup, float givenPosition, float givenHeight) {
if ((Mathf.Abs(givenPosition) + givenHeight > contentTop && Mathf.Abs(givenPosition) + givenHeight < contentBottom) || (Mathf.Abs(givenPosition) > contentTop && Mathf.Abs(givenPosition) < contentBottom)) {
if (canvasGroup.alpha != 1) {
canvasGroup.alpha = 1;
}
return true;
} else {
if (canvasGroup.alpha != 0) {
canvasGroup.alpha = 0;
}
return false;
}
}
static public void Setup(Scroll givenScroll) {
contentTop = (1 - givenScroll.verticalNormalizedPosition) * (givenScroll.content.rect.height - givenScroll.viewport.rect.height);
contentBottom = contentTop + givenScroll.viewport.rect.height;
}
}
SIX:
Unity's built in ScrollRect component allows for broad, modular functionality. However, in terms of performance it can be noticeably slower than if you were to write your own. Here is a Scroll script that achieves the same ends, but only supports the vertical, clamped and inertia properties of Unity's ScrollRect.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
public class Scroll : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IDragHandler, IScrollHandler {
private Camera mainCamera;
private RectTransform canvasRect;
public RectTransform viewport;
public RectTransform content;
private Rect viewportOld;
private Rect contentOld;
private List<Vector2> dragCoordinates = new List<Vector2>();
private List<float> offsets = new List<float>();
private int offsetsAveraged = 4;
private float offset;
private float velocity = 0;
private bool changesMade = false;
public float decelration = 0.135f;
public float scrollSensitivity;
public OnValueChanged onValueChanged;
[System.Serializable]
public class OnValueChanged : UnityEvent { }
[HideInInspector]
public float verticalNormalizedPosition
{
get
{
float sizeDelta = CaculateDeltaSize();
if (sizeDelta == 0) {
return 0;
} else {
return 1 - content.transform.localPosition.y / sizeDelta;
}
}
set
{
float o_verticalNormalizedPosition = verticalNormalizedPosition;
float m_verticalNormalizedPosition = Mathf.Max(0, Mathf.Min(1, value));
float maxY = CaculateDeltaSize();
content.transform.localPosition = new Vector3(content.transform.localPosition.x, Mathf.Max(0, (1 - m_verticalNormalizedPosition) * maxY), content.transform.localPosition.z);
float n_verticalNormalizedPosition = verticalNormalizedPosition;
if (o_verticalNormalizedPosition != n_verticalNormalizedPosition) {
onValueChanged.Invoke();
}
}
}
private float CaculateDeltaSize() {
return Mathf.Max(0, content.rect.height - viewport.rect.height); ;
}
private void Awake() {
mainCamera = GameObject.Find("Main Camera").GetComponent<Camera>();
canvasRect = transform.root.GetComponent<RectTransform>();
}
private Vector2 ConvertEventDataDrag(PointerEventData eventData) {
return new Vector2(eventData.position.x / mainCamera.pixelWidth * canvasRect.rect.width, eventData.position.y / mainCamera.pixelHeight * canvasRect.rect.height);
}
private Vector2 ConvertEventDataScroll(PointerEventData eventData) {
return new Vector2(eventData.scrollDelta.x / mainCamera.pixelWidth * canvasRect.rect.width, eventData.scrollDelta.y / mainCamera.pixelHeight * canvasRect.rect.height) * scrollSensitivity;
}
public void OnPointerDown(PointerEventData eventData) {
velocity = 0;
dragCoordinates.Clear();
offsets.Clear();
dragCoordinates.Add(ConvertEventDataDrag(eventData));
}
public void OnScroll(PointerEventData eventData) {
UpdateOffsetsScroll(ConvertEventDataScroll(eventData));
OffsetContent(offsets[offsets.Count - 1]);
}
public void OnDrag(PointerEventData eventData) {
dragCoordinates.Add(ConvertEventDataDrag(eventData));
UpdateOffsetsDrag();
OffsetContent(offsets[offsets.Count - 1]);
}
public void OnPointerUp(PointerEventData eventData) {
dragCoordinates.Add(ConvertEventDataDrag(eventData));
UpdateOffsetsDrag();
OffsetContent(offsets[offsets.Count - 1]);
float totalOffsets = 0;
foreach (float offset in offsets) {
totalOffsets += offset;
}
velocity = totalOffsets / offsetsAveraged;
dragCoordinates.Clear();
offsets.Clear();
}
private void OffsetContent(float givenOffset) {
float newY = Mathf.Max(0, Mathf.Min(CaculateDeltaSize(), content.transform.localPosition.y + givenOffset));
if (content.transform.localPosition.y != newY) {
content.transform.localPosition = new Vector3(content.transform.localPosition.x, newY, content.transform.localPosition.z);
}
onValueChanged.Invoke();
}
private void UpdateOffsetsDrag() {
offsets.Add(dragCoordinates[dragCoordinates.Count - 1].y - dragCoordinates[dragCoordinates.Count - 2].y);
if (offsets.Count > offsetsAveraged) {
offsets.RemoveAt(0);
}
}
private void UpdateOffsetsScroll(Vector2 givenScrollDelta) {
offsets.Add(givenScrollDelta.y);
if (offsets.Count > offsetsAveraged) {
offsets.RemoveAt(0);
}
}
private void LateUpdate() {
if (viewport.rect != viewportOld) {
changesMade = true;
viewportOld = new Rect(viewport.rect);
}
if (content.rect != contentOld) {
changesMade = true;
contentOld = new Rect(content.rect);
}
if (velocity != 0) {
changesMade = true;
velocity = (velocity / Mathf.Abs(velocity)) * Mathf.FloorToInt(Mathf.Abs(velocity) * (1 - decelration));
offset = velocity;
}
if (changesMade) {
OffsetContent(offset);
changesMade = false;
offset = 0;
}
}
}
A nice article explains that the default targetFrameRate might be responsible for the unsmooth scrolling behavior of the scrollView. This can be resolved via:
Application.targetFrameRate = 60; // or whatever you wish. 60 turned out enough for us
Of course this setting is only effective if you have resolved the performance issues (as it is nicely explained by Phedg1).

How to calculate sensitivity based on the width/height of the screen?

Here's my idea: I wanted to have a scrollview where the user could both scroll the component in it, and click on it.
After hours of testing/search, I've finally managed to make this working with the following code.
My problem is in the comparison Math.Abs(eventData.delta.x) > 1.0f. I consider that if the mouse "moves more than 1.0f" then it's dragging, else I consider this as a click.
The value 1.0f works perfectly on all the devices with a mouse = it's easy not to move, and big screen (tablets), when you click. But on smartphones, ie my Galaxy S6 or the 3 other ones I've tried, it's very sensitive and almost impossible to make a click.
How could you programmatically handle this? Is there a DPI or something to take in account and based on this, multiply my 1.0f by the resolution?
public class BoardHandler : EventTrigger
{
private static GameObject _itemDragged;
private static bool _isDragging;
private static ScrollRect _scrollRect;
public override void OnPointerClick(PointerEventData data)
{
if (_isDragging) {
return;
}
Debug.Log("Click");
}
public override void OnBeginDrag(PointerEventData eventData)
{
if (Math.Abs(eventData.delta.x) > 1.0f ||
Math.Abs(eventData.delta.y) > 1.0f) {
_scrollRect.OnBeginDrag(eventData);
_isDragging = true;
}
}
public override void OnDrag(PointerEventData eventData)
{
if (_isDragging) {
_scrollRect.OnDrag(eventData);
}
}
public override void OnEndDrag(PointerEventData eventData)
{
if (!_isDragging) {
return;
}
_scrollRect.OnEndDrag(eventData);
_isDragging = false;
}
private void Start()
{
_scrollRect = GetComponentInParent<ScrollRect>();
}
}
I've used TouchScript in the past to cover multiple devices within one project, like from 9 screens 6k screen array to tablets. They have a set of utilities to handle multiple resolution and dpi.
Check out the the UpdateResolution method in TouchManagerInstance.
hth.

Moving the user with google cardboard SDK

I'm using the google cardboard SDK and i have a script attached to the child "Head" in the GvrMain prefab. There's also a Character controller connected and the vrCamera variable has been set to the "Head" object to this so that i can move the user based on the code which will follow.
The issue i'm having is that i want to move the character when the user tilts their head at an angle, all this works fine but the issue is that when the simpleMove function is called nothing is happening... I've logged to see if any of the variables upto the point are empty but none of them seem to be; just this function doesn't seem to be getting called. Code below.
public class VRLookWalk : MonoBehaviour {
public Transform vrCamera;
public float toggleAngle = 30.0f;
public float speed = 3.0f;
public bool moveForward;
private CharacterController myCC;
// Use this for initialization
void Start () {
myCC = GetComponent<CharacterController> ();
}
// Update is called once per frame
void Update () {
if (vrCamera.eulerAngles.x >= toggleAngle && vrCamera.eulerAngles.x < 90.0f)
{
moveForward = true;
} else {
moveForward = false;
}
if (moveForward)
{
Vector3 forward = vrCamera.TransformDirection (Vector3.forward);
myCC.SimpleMove (forward * speed);
}
}
}

Unity3D scene not working properly and lagging after reloading

I have looked all over internet, but I cannot find a solution to my problem. I am trying to create a game and it has 3 scenes: the Game Start, Main and Game over.
The problem is that when trying to load the main level from other scenes it does not do what it has to do (e.g. jump) and the scene itself is lagging. When I try to load only the Main Scene it works ok, but after the character dies and the scene is reloaded, it starts to lag again and I cannot jump or do anything it is supposed to do.
Any ideas on what the problem might be?
using UnityEngine;
using System;
public class Player : MonoBehaviour
{
// The force which is added when the player jumps
// This can be changed in the Inspector window
public Vector2 jumpForce = new Vector2(0, 300);
private bool shouldJump = true;
// Update is called once per frame
private float jumpThreshold = 0.5f;
private float previousJumpTime;
void FixedUpdate ()
{
// Jump
float mc = MicControl.loudness;
//Debug.Log (mc);
if (mc>1.3f && shouldJump)
{
shouldJump = false;
previousJumpTime = Time.time;
GetComponent<Rigidbody2D>().velocity = Vector2.zero;
GetComponent<Rigidbody2D>().AddForce(jumpForce);
}
if (!shouldJump)
{
if(Time.time-previousJumpTime>jumpThreshold)
{
shouldJump = true;
}
}
// Die by being off screen
Vector2 screenPosition = Camera.main.WorldToScreenPoint(transform.position);
if (screenPosition.y > Screen.height || screenPosition.y < 0)
{
Die();
}
}
// Die by collision
void OnCollisionEnter2D(Collision2D other)
{
Die();
}
void Die()
{
Application.LoadLevel ("main");
}
}
You said your level is called Main, but in the code you are loading "main", i'm not sure if that's the problem, but you seem to be loading the level correctly, so check if is main or Main the exact name of the level.
Also, when compiling, make sure you have all levels checked
With provided data its impossible to say what causes low performance, but I recommend you to use Profiler tool (can be found in personal version of Unity 5) and figure out what scripts and functions are problematic.
Also, try to avoid calling GetComponent<Rigidbody2D>() in Update/FixedUpdate/LateUpdate and cache this component instead.
I second what Utamaru said.
Rigidbody2D myRigidbody2d;
void Start ()
{
myRigidbody2d = GetComponent<Rigidbody2D>(); //Do this once
}
Inside your fixed update, you can do this:
void FixedUpdate ()
{
// Jump
float mc = MicControl.loudness;
//Debug.Log (mc);
if (mc>1.3f && shouldJump)
{
shouldJump = false;
previousJumpTime = Time.time;
myRigidbody2d.velocity = Vector2.zero;
myRigidbody2d.AddForce(jumpForce);
}
if (!shouldJump)
{
if(Time.time-previousJumpTime>jumpThreshold)
{
shouldJump = true;
}
}
// Die by being off screen
Vector2 screenPosition = Camera.main.WorldToScreenPoint(transform.position);
if (screenPosition.y > Screen.height || screenPosition.y < 0)
{
Die();
}
}
I can't see the rest of your code so I am not really sure that is the problem but give this a try.