I am working on a project in Unity for Android Portrait mode. I will constantly be modifying the textures of two RawImages, the catch is that the images must be the same width and height(1080x1080, 800x800) 1080x1079 is wrong. Because a lot of phones nowadays are notched, I'm forced to maintain all the UI Elements inside the SafeArea rect. BUT, I don't know how to get the y coordinate of the SafeArea rect relative to the Display. Here's a photo:
So, I only need to get the y coordinate of the SafeArea rect, by my guesses, for the Phone in the example, the y coordinate should be around 100 pixels height. Thank you in advance.
If you are using UI Scale Mode: Scale with screen size Like in this picture, here is my solution for adaptive RectTransform with your safe area (only Vertical):
using UnityEngine;
using UnityEngine.UI;
public class MySafeArea : MonoBehaviour
{
private CanvasScaler canvasScaler;
private float bottomUnits, topUnits;
void Start()
{
canvasScaler = FindObjectOfType<CanvasScaler>();
ApplyVerticalSafeArea();
}
public void ApplyVerticalSafeArea()
{
var bottomPixels = Screen.safeArea.y;
var topPixel = Screen.currentResolution.height - (Screen.safeArea.y + Screen.safeArea.height);
var bottomRatio = bottomPixels / Screen.currentResolution.height;
var topRatio = topPixel / Screen.currentResolution.height;
var referenceResolution = canvasScaler.referenceResolution;
bottomUnits = referenceResolution.y * bottomRatio;
topUnits = referenceResolution.y * topRatio;
var rectTransform = GetComponent<RectTransform>();
rectTransform.offsetMin = new Vector2(rectTransform.offsetMin.x, bottomUnits);
rectTransform.offsetMax = new Vector2(rectTransform.offsetMax.x, -topUnits);
}
}
Just attach this script to your RectTransform with stretched anchors and enjoy the result
Related
I am working in a Game which is pretty similar to Mario. So when player touches the coin object in World Space, I need to animate by moving that coin object to Coin meter, when the render mode of Canvas is Screen Space - Overlay, I can get the sprite object position easily with below code
CoinSprite Code
GameObject coinCanvasObject = Instantiate(prefab, canvas.transform);//Instantiate coin inside Canvas view
coinCanvasObject.transform.position = Camera.main.WorldToScreenPoint(coinSpriteObject.transform.position);//getting coin position from World Space and convert to Screen Space and set to coinCanvasobject position
AnimateCoin animate = coinCanvasObject.GetComponent<AnimateCoin>();
animate.animateCoin(coinSpriteObject.transform.position);
coinSpriteObject.SetActive(false);
AnimateCoin
public class AnimateCoin : MonoBehaviour
{
private float speed = 0f;
private bool isSpawn = false;
private Vector3 screenPos;
public void animateCoin(Vector3 screenPosTemp, Camera cam, Canvas canvas)
{
screenPos = Camera.main.WorldToScreenPoint(screenPosTemp);
isSpawn = true;
}
private void Update()
{
if (isSpawn)
{
speed += 0.025f;
transform.position = Vector3.Lerp(screenPos, targetObject.transform.position, speed);
if (Vector3.Distance(transform.position, targetObject.transform.position) <= 0)
{
StartCoroutine(deActivateCoin());
}
}
}
private IEnumerator deActivateCoin()
{
isSpawn = false;
yield return new WaitForSeconds(0.2f);
gameObject.SetActive(false);
}
}
Since I need to bring particle effect into Canvas view, I am changing the Canvas render mode to Screen Space - Camera.
When I change the Canvas to this render mode I could not get the exact sprite object position to trail the coin effect.
Hope this helps:
public Camera cam; // Camera containing the canvas
public Transform target; // object in the 3D World
public RectTransform icon; // icon to place in the canvas
public Canvas canvas; // canvas with "Render mode: Screen Space - Camera"
void Update()
{
Vector3 screenPos = cam.WorldToScreenPoint(target.position);
float h = Screen.height;
float w = Screen.width;
float x = screenPos.x - (w / 2);
float y = screenPos.y - (h / 2);
float s = canvas.scaleFactor;
icon.anchoredPosition = new Vector2(x, y) / s;
}
PD: It worked perfectly for me in a 2D video game, I didn't test it in a 3D game, but I think it should work too.
I rewrote my previous solution because it might not work correctly on some devices with non-standard resolutions.
This code should always work.
uiObject.anchoredPosition = GetUIScreenPosition(myPin.position, cam3d, uiObject.anchorMin);
public static Vector2 GetUIScreenPosition(Vector3 obj3dPosition, Camera cam3d, Vector2 anchor)
{
Vector2 rootScreen = _rootCanvasRect.sizeDelta;
Vector3 screenPos = cam3d.WorldToViewportPoint(obj3dPosition);
return (rootScreen * screenPos) - (rootScreen * anchor);
}
We take the sizeDelta of our UI Canvas, because it may differ from the screen resolution of the device.
Then we cast the WorldToViewportPoint from our 3d camera to get the relative position on the screen in the format from 0 to 1 by X and Y.
With anchors in the lower left corner ((0,0)(0,0)) this is our final anchoredPosition. However with anchors for example in the center ((0.5,0.5)(0.5,0.5)) we need to adjust the positions by subtracting half the canvas size.
In this example, we will get an unpredictable result when using different min and max anchors in the final object. For example ((0,25,0.25)(0.75,0.75)). But I sincerely doubt that you really need such anchors on an object with a dynamic position depending on the 3d object.
I'm trying to modify a script that lets the player zoom the camera. Before, scrolling to change the fov worked fine but i wasn't happy with the high fov stretching around the edges so I decided to switch to physical camera movement. For some reason my scroll input doesn't work anymore? This is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class cameraManager : MonoBehaviour
{
public GameObject cameraAnchor;
public int cameraState;
public Transform playerPos;
public float maxZoomX;
public float maxZoomY;
public float zoomSensitivity;
// Update is called once per frame
void Update()
{
cameraAnchor.transform.position = (playerPos.position + new Vector3(6,10,0)); //camera anchor position relative to player
if (cameraState == 3){
Vector3 zoom = Camera.current.transform.position;
zoom.x += Input.GetAxis("Mouse ScrollWheel") * zoomSensitivity; // changing camera location with scroll wheel
zoom.y += Input.GetAxis("Mouse ScrollWheel") * zoomSensitivity; // changing camera location with scroll wheel
zoom.x = Mathf.Clamp(zoom.x, cameraAnchor.transform.position.x, cameraAnchor.transform.position.x + maxZoomX); // clamping zoom
zoom.y = Mathf.Clamp(zoom.y, cameraAnchor.transform.position.y, cameraAnchor.transform.position.y + maxZoomY); // clamping zoom
print(Input.GetAxis("Mouse ScrollWheel"));//testing scroll input
Camera.current.transform.position = zoom; //setting zoom
}
else{
Camera.current.transform.position = cameraAnchor.transform.position; //reset camera anchor if not in 3rd person
}
}
}
As you can see above, i've tested scrolling with print, which constantly produces a 0, regardless of whether i'm scrolling or not. I've looked at the input settings for the project and everything looks fine there.
Edit: here's a screenshot of my input page
I had this same problem. I loaded up a new project, copied the Mouse ScrollWheel entry from the input settings, and then pasted it into my old project. This resolved the issue. I didn't figure out what caused the issue in the first place.
I want an UI canvas to follow the camera so it will be in front of the head always and also interactable like VR menu. I'm using the following code to do so.
public class FollowMe : MonoBehaviour
{
public GameObject menuCanvas;
public Camera FirstPersonCamera;
[Range(0, 1)]
public float smoothFactor = 0.5f;
// how far to stay away fromt he center
public float offsetRadius = 0.3f;
public float distanceToHead = 4;
public void Update()
{
// make the UI always face towards the camera
menuCanvas.transform.rotation = FirstPersonCamera.transform.rotation;
var cameraCenter = FirstPersonCamera.transform.position + FirstPersonCamera.transform.forward * distanceToHead;
var currentPos = menuCanvas.transform.position;
// in which direction from the center?
var direction = currentPos - cameraCenter;
// target is in the same direction but offsetRadius
// from the center
var targetPosition = cameraCenter + direction.normalized * offsetRadius;
// finally interpolate towards this position
menuCanvas.transform.position = Vector3.Lerp(currentPos, targetPosition, smoothFactor);
}
}
Unfortunately, the canvas is flickering in front fo the camera and it is not properly positioned. How do I make the menu to follow the camera?|
If there is no reason against it you can use a ScreenSpace - Camera canvas as stated in the docs. Then you can reference your FPS camera as the rendering camera for the canvas.
Easy way to do this is using Screen Space - Camera mode which you can setup from Canvas component and in Render Mode properties.
Second way if you want more control over how your canvas should behave then you can use Canvas Render Mode - "World Space" and then using script you can handle canvas a some gameobject.
The main menu is built with one main Canvas and a GameObject that I show over the Canvas and have a Camera. The GameObject also have his own Canvas for a text.
The default resolution is 1920x1080 so I set it manual the ui elements to fit this resolution.
This screenshot is of the first Canvas settings and the Main Menu hierarchy build :
The reason that I needed another Canvas and the Camera is that this is the only way I could display the GameObject(NAVI) over the first Canvas with the GameObject animations and the text ui.
Both Canvas settings are the same.
Now I changed in the editor before even running the game to another resolution 1024x768.
And now all the ui are messed. It happens the same when running the game through a built exe file on full or windowed screen :
And I have many resolutions each one with some different frame rates so it will be a bit hard and will take time to set each resolution by manual like I did for the default 1920x1080.
This is the script I'm using for changing the resolutions from a dropdown. The script is attached to the first main Canvas under the Main Menu :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.UI;
public class SettingsMenu : MonoBehaviour
{
public AudioMixer audioMixer;
public Text volumeInPercentages;
public Dropdown resolutionDropDown;
[SerializeField]
private Slider _volumeSlider;
[SerializeField]
private Dropdown _dropDownQuality;
private Resolution[] resolutions;
private void Start()
{
resolutions = Screen.resolutions;
resolutionDropDown.ClearOptions();
List<string> options = new List<string>();
int currentResolutionIndex = 0;
for (int i = 0; i < resolutions.Length; i++)
{
string option = resolutions[i].width + " x " + resolutions[i].height
+ " " + resolutions[i].refreshRate.ToString() + " Hz";
options.Add(option);
if (resolutions[i].width == Screen.currentResolution.width &&
resolutions[i].height == Screen.currentResolution.height)
{
currentResolutionIndex = i;
}
}
resolutionDropDown.AddOptions(options);
resolutionDropDown.value = currentResolutionIndex;
resolutionDropDown.RefreshShownValue();
}
public void SetVolume()
{
float volume = _volumeSlider.value;
Debug.Log("Volume " + volume);
audioMixer.SetFloat("MusicVol", Mathf.Log10(volume) * 20);
volumeInPercentages.text = Mathf.Round(volume * 100).ToString() + " %";
}
public void SetQuality()
{
int qualityIndex = _dropDownQuality.value;
QualitySettings.SetQualityLevel(qualityIndex);
}
public void SetFullScreen()
{
Screen.fullScreen = !Screen.fullScreen;
}
public void SetResolution()
{
Resolution resolution = resolutions[resolutionDropDown.value];
Screen.SetResolution(resolution.width, resolution.height, Screen.fullScreen);
}
}
In your case, you're setting the Canvas Scaler to scale with screen size by matching the height, so they'll always fit in the vertical axis. That's an important point.
Take a look in the Rect Transforms of your elements and anchor them in the correct point of the screen.
Probably all your elements are anchored in the center of the screen, causing them to go out of the game scene.
Here are some more detailed information about RectTransforms, Pivot, Anchors...
I have a curious problem with rendering the actual content in NGUI widgets when the underlying panel is moved. For some reason, the content itself doesn't update to correct position even if the bounds are. First image is how it should be and second is how it is sometimes after moving the underlying panel from right to left. You can see from the widget rectangles that they are in correct place but the contents (text or sprite) is misplaced.
I have tried updating, refreshing anchors etc. but none seem to be working. Any ideas? It seems to be a rendering problem. We are using Unity 4.6 and NGUI 3.7.4 currently.
I suppose NGUI don't work well with (parent) gameobject being inactive.
Especially if you try to UIScrollView.ResetPosition, scaling or re-positioning.
so I have an extension that look like this
public static void ResetPositionHack(this UIScrollView scrollView, float animLength = 1f ) {
scrollView.ResetPosition();
scrollView.currentMomentum = Vector3.zero;
KeepMakingDirty.Begin( scrollView, animLength );
}
The method KeepMakingDirty.Begin will attach a MonoBehavior to the object (also make sure no double attachment).
SetDirty() will be called in it's Update() for a specified period of time.
Then the script self-destructs.
This is a NGUI's bug. It is related to UIPanel.UpdateTransformMatrix(), and has been fixed at NGUI3.9.3 or NGUI 3.9.4.
The code blow works fine in my project:
void UpdateTransformMatrix ()
{
int fc = Time.frameCount;
if (cachedTransform.hasChanged)
{
mTrans.hasChanged = false;
mMatrixFrame = -1;
}
if (mMatrixFrame != fc)
{
mMatrixFrame = fc;
worldToLocal = mTrans.worldToLocalMatrix;
Vector2 size = GetViewSize() * 0.5f;
float x = mClipOffset.x + mClipRange.x;
float y = mClipOffset.y + mClipRange.y;
mMin.x = x - size.x;
mMin.y = y - size.y;
mMax.x = x + size.x;
mMax.y = y + size.y;
}
}