I'm trying to achieve this 3d pop out of the screen kind of effect using LibGDX on Android (the camera process described at the given link):
https://www.anxious-bored.com/blog/
but the result I get is stretched objects when the eye moves around.
I simulate the eye position using a gyroscope to get device rotation and assume a fixed distance from the device screen. I've also incorporated ARCore for eye tracking, but that also yields the same result.
Here is a GIF of what I'm seeing:
https://i.stack.imgur.com/YOf6n.gif
Anyone have any idea on what I'm doing wrong?
Here is the relevant code I'm using in Kotlin.
private fun updateCamera() {
val camera = // The Perspective camera instance
// Move the camera to the eye position, but keep the same camera direction
camera.view.setToLookAt(camera.direction, camera.up).mul(GDXHelperInstances.matrix4_1.setToTranslation(GDXHelperInstances.vector3_1.set(camera.position).scl(-1f)))
// Get the size of the screen in meters
val deviceHalfHeight = camera.viewportHeight / Gdx.graphics.ppcY * 0.01f
val deviceHalfWidth = camera.viewportWidth / Gdx.graphics.ppcX * 0.01f
// Get the device position and plane relative to the camera
val pos = Vector3(Vector3.Zero).mul(camera.view)
val plane = Plane(GDXHelperInstances.vector3_1.set(camera.direction).scl(-1f), Vector3.Zero)
// Calculate the bounds of the viewport in virtual space (the device screen dimensions in meters with center at Vector3.Zero) relative to the camera (view space)
val left = pos.x - deviceHalfWidth
val right = pos.x + deviceHalfWidth
val bottom = pos.y - deviceHalfHeight
val top = pos.y + deviceHalfHeight
val nearScale = camera.near / plane.distance(camera.position).absoluteValue
// Off-axis projection
camera.projection.setToProjection(left*nearScale, right*nearScale, bottom*nearScale, top*nearScale, camera.near, camera.far)
// Calculate new asymmetrical frustum
camera.combined.set(camera.projection)
Matrix4.mul(camera.combined.`val`, camera.view.`val`)
camera.invProjectionView.set(camera.combined)
Matrix4.inv(camera.invProjectionView.`val`)
camera.frustum.update(camera.invProjectionView)
}
I've been trying to create a Unity 2D game that supports each and every aspect ratios of devices for both android and tablets. Is there a way to do so that's been provided or recommended by Unity?
There are a few things that should be considered. The first is what elements should be allowed to scale? There are two categories, namely UI and Game Elements.
The Game Elements portion can mean a lot of things. If the game space is limited, the key is typically, including a generous portion of "negative space", or parts of the image that don't affect the game play significantly. For instance, the below image could be cropped from the left and right without affecting the image significantly. Put the center part of the image as the key element, or one side.
One could also stretch the elements, although that might lead to undesirable effects. Having a surplus of image and testing with different aspect rations is the best way typically for such background elements. These background elements can be placed in the background, with the canvas being set to "Scale With Screen Size", and setting the "Screen Match Mode" to the effect that works best for your image. See "Canvas Scaler" for more information.
As for the other UI elements, the key is to use anchor points. You can tell a UI element to take either a number of pixels, or fill a portion of the screen, when you place it. Look at the "Rect Transform" component included with each such UI object. You can adjust these on the screen as well.
Lastly, you could do it programmatically. There exists Screen.height and Screen.width. You could adjust the objects as desired in run time to make it work. I suggest you don't do this for everything, but it might help in some cases.
In my case, I do work by create all of it as a scale
So, it could support no matter screen are
//Find Screen resolution at the splash or loading screen
float scalex = DataFactory.SCREEN_WIDTH / (float)DataFactory.OUR_FIXED_GAME_SCREEN;
float scaley = DataFactory.SCREEN_HEIGHT / (float)DataFactory.OUR_FIXED_GAME_SCREEN;
if (scalex >= scaley)
DataFactory.SCALE = scalex;
else
DataFactory.SCALE = scaley;
//Set all size in game at the start
private int gameWidth = (int) (1400 * DataFactory.SCALE);
private int gameHeight = (int) (800 * DataFactory.SCALE);
private int startGameX = (int) (300 * DataFactory.SCALE);
private int startGameY = (int) (280 * DataFactory.SCALE);
private int objectX = (int) (410 * DataFactory.SCALE) + DataFactory.BEGIN_X;
private int objectY = (int) (979 * DataFactory.SCALE) + DataFactory.BEGIN_Y;
private int objectGapX = (int) (400 * DataFactory.SCALE);
private int objectGapY = (int) (180 * DataFactory.SCALE);
private int objectWidth = (int) (560 * DataFactory.SCALE);
private int objectHeight = (int) (400 * DataFactory.SCALE);
private int xRing = (int) (1005 * DataFactory.SCALE) + DataFactory.BEGIN_X;
private int yRing = (int) (1020 * DataFactory.SCALE) + DataFactory.BEGIN_Y;
private int radiusOutside = (int) (740 * DataFactory.SCALE);
private int radiusInside = (int) (480 * DataFactory.SCALE);
private int radiusObject = (int) (600 * DataFactory.SCALE);
private int yObjectRing = (int) (920 * DataFactory.SCALE) + DataFactory.BEGIN_Y;
* ALL FIXED VALUED IS THE VALUED THAT I CREATE BY BASE ON SINGLE SCREEN *
This is some sample of 3D Game that I made, however, I still use the same concept at GUI part
This is some sample of 2D Game that I used this concept
I know it’s an old post, wanted to show an alternative for this. You could try to define towards which axis you would like to scale your game (ex. all width should be always visible, height should scale respectively to the width): store all scene objects in a parent and scale the parent.
Ex. (my width was fixed and the height got cut off for the width )
bottomRightPosition = Camera.main.ScreenToWorldPoint(new Vector3(0, 0, - Camera.main.transform.position.z));
topLeftPosition = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, -Camera.main.transform.position.z));
float Width = topLeftPosition.x -bottomRightPosition.x
float scale = width / optimizedWorldDistance
gameHolder.transform.localScale = new Vector3(scale,scale,1); // for 2D
Note: my gameHolder is initially of scale (1,1,1);
You should put everything in a main game object and scale it with difference ratio using a simple script (camera 'something' could help you to detect the screen ratio).
That's my idea.
Sorry for my bad English.
Try the new unity3d ui with anchoring.
For all the UI elements you must use the unit UI system, which is the best way to support multi platforms and aspect ratios.
The following content is based on this article:
This article say basically the same things that I'm saying:
1)
Regarding design ONLY in high resolution the article said:
"Another approach is to use higher resolution graphics (in fact the
one with the highest resolution of the device you want to target) and
scale it down on all devices. However, this is not a good idea because
you effectively need much more memory and will lose performance on
low-end devices."
So design in high resolution and then scale down it's not the good approach.
So as the article said the best thing it's to have different images for different resolution (SD, HD UD) and load the right image when the game it's loading: the article said:
"The best approach is to use a different image with the higher resolution and use this image version on the iPhone 4 and the low-res version on an iPhone 3GS, which is effectively what Apple is doing by using images with a #2x suffix for the file name.
In a similar way, you can create all your graphics in ultra-high-resolution needed for the iPad 3 for instance and append another suffix, and load the right image based on the screen resolution the device has. This is called content scaling, as the game was written only for a single "logical" scene size, and all the images & fonts are scaled to the device resolution."
So by using this approach we solved the problem of target devices with differnt RESOLUTIONS.
No like the articole said there is another problem which is target the devices with different ASPECT RATIO:
From the artichle:
"However, this approach is not sufficient when you want to target devices with different aspect ratios"
To do that I usually choose an aspect ratio that can fit the design of my game and use the following script to maintain the same aspect ratio on different devices:
/* The MIT License (MIT)
Copyright (c) 2014, Marcel Căşvan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. */
using System;
using System.Collections;
using UnityEngine;
[ExecuteInEditMode]
[RequireComponent (typeof (Camera))]
public class CameraFit : MonoBehaviour
{
#region FIELDS
public float UnitsForWidth = 1; // width of your scene in unity units
public static CameraFit Instance;
private float _width;
private float _height;
//*** bottom screen
private Vector3 _bl;
private Vector3 _bc;
private Vector3 _br;
//*** middle screen
private Vector3 _ml;
private Vector3 _mc;
private Vector3 _mr;
//*** top screen
private Vector3 _tl;
private Vector3 _tc;
private Vector3 _tr;
#endregion
#region PROPERTIES
public float Width {
get {
return _width;
}
}
public float Height {
get {
return _height;
}
}
// helper points:
public Vector3 BottomLeft {
get {
return _bl;
}
}
public Vector3 BottomCenter {
get {
return _bc;
}
}
public Vector3 BottomRight {
get {
return _br;
}
}
public Vector3 MiddleLeft {
get {
return _ml;
}
}
public Vector3 MiddleCenter {
get {
return _mc;
}
}
public Vector3 MiddleRight {
get {
return _mr;
}
}
public Vector3 TopLeft {
get {
return _tl;
}
}
public Vector3 TopCenter {
get {
return _tc;
}
}
public Vector3 TopRight {
get {
return _tr;
}
}
#endregion
#region METHODS
private void Awake()
{
try{
if((bool)GetComponent<Camera>()){
if (GetComponent<Camera>().orthographic) {
ComputeResolution();
}
}
}catch (Exception e){
Debug.LogException(e, this);
}
}
private void ComputeResolution()
{
float deviceWidth;
float deviceHeight;
float leftX, rightX, topY, bottomY;
#if UNITY_EDITOR
deviceWidth = GetGameView().x;
deviceHeight = GetGameView().y;
#else
deviceWidth = Screen.width;
deviceHeight = Screen.height;
#endif
//Debug.Log("Aspect Ratio " + GetComponent<Camera>().aspect);
if (GetComponent<Camera>().aspect >= 0.7f)
{
UnitsForWidth = 2.2f;
}
else
{
UnitsForWidth = 2f;
}
/* Set the ortograpish size (shich is half of the vertical size) when we change the ortosize of the camera the item will be scaled
* autoamtically to fit the size frame of the camera
*/
GetComponent<Camera>().orthographicSize = 1f / GetComponent<Camera>().aspect * UnitsForWidth / 2f;
//Get the new height and Widht based on the new orthographicSize
_height = 2f * GetComponent<Camera>().orthographicSize;
_width = _height * GetComponent<Camera>().aspect;
float cameraX, cameraY;
cameraX = GetComponent<Camera>().transform.position.x;
cameraY = GetComponent<Camera>().transform.position.y;
leftX = cameraX - _width / 2;
rightX = cameraX + _width / 2;
topY = cameraY + _height / 2;
bottomY = cameraY - _height / 2;
//*** bottom
_bl = new Vector3(leftX, bottomY, 0);
_bc = new Vector3(cameraX, bottomY, 0);
_br = new Vector3(rightX, bottomY, 0);
//*** middle
_ml = new Vector3(leftX, cameraY, 0);
_mc = new Vector3(cameraX, cameraY, 0);
_mr = new Vector3(rightX, cameraY, 0);
//*** top
_tl = new Vector3(leftX, topY, 0);
_tc = new Vector3(cameraX, topY , 0);
_tr = new Vector3(rightX, topY, 0);
Instance = this;
}
private void Update()
{
#if UNITY_EDITOR
ComputeResolution();
#endif
}
private void OnDrawGizmos()
{
if (GetComponent<Camera>().orthographic) {
DrawGizmos();
}
}
private void DrawGizmos()
{
//*** bottom
Gizmos.DrawIcon(_bl, "point.png", false);
Gizmos.DrawIcon(_bc, "point.png", false);
Gizmos.DrawIcon(_br, "point.png", false);
//*** middle
Gizmos.DrawIcon(_ml, "point.png", false);
Gizmos.DrawIcon(_mc, "point.png", false);
Gizmos.DrawIcon(_mr, "point.png", false);
//*** top
Gizmos.DrawIcon(_tl, "point.png", false);
Gizmos.DrawIcon(_tc, "point.png", false);
Gizmos.DrawIcon(_tr, "point.png", false);
Gizmos.color = Color.green;
Gizmos.DrawLine(_bl, _br);
Gizmos.DrawLine(_br, _tr);
Gizmos.DrawLine(_tr, _tl);
Gizmos.DrawLine(_tl, _bl);
}
private Vector2 GetGameView()
{
System.Type T = System.Type.GetType("UnityEditor.GameView,UnityEditor");
System.Reflection.MethodInfo getSizeOfMainGameView =
T.GetMethod("GetSizeOfMainGameView",System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
System.Object resolution = getSizeOfMainGameView.Invoke(null, null);
return (Vector2)resolution;
}
#endregion
}
[1]: http://v-play.net/doc/vplay-different-screen-sizes/
This should sole the different aspect ratios problem. Now if you want anchor some game object the be always in a fixed position event if the game is resized on devices with different aspect ratios, you can use the following script:
/***
* This script will anchor a GameObject to a relative screen position.
* This script is intended to be used with CameraFit.cs by Marcel Căşvan, available here: http://gamedev.stackexchange.com/a/89973/50623
*
* Note: For performance reasons it's currently assumed that the game resolution will not change after the game starts.
* You could not make this assumption by periodically calling UpdateAnchor() in the Update() function or a coroutine, but is left as an exercise to the reader.
*/
/* The MIT License (MIT)
Copyright (c) 2015, Eliot Lash
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. */
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class CameraAnchor : MonoBehaviour {
public enum AnchorType {
BottomLeft,
BottomCenter,
BottomRight,
MiddleLeft,
MiddleCenter,
MiddleRight,
TopLeft,
TopCenter,
TopRight,
};
public AnchorType anchorType;
public Vector3 anchorOffset;
// Use this for initialization
void Start () {
UpdateAnchor();
}
void UpdateAnchor() {
switch(anchorType) {
case AnchorType.BottomLeft:
SetAnchor(CameraFit.Instance.BottomLeft);
break;
case AnchorType.BottomCenter:
SetAnchor(CameraFit.Instance.BottomCenter);
break;
case AnchorType.BottomRight:
SetAnchor(CameraFit.Instance.BottomRight);
break;
case AnchorType.MiddleLeft:
SetAnchor(CameraFit.Instance.MiddleLeft);
break;
case AnchorType.MiddleCenter:
SetAnchor(CameraFit.Instance.MiddleCenter);
break;
case AnchorType.MiddleRight:
SetAnchor(CameraFit.Instance.MiddleRight);
break;
case AnchorType.TopLeft:
SetAnchor(CameraFit.Instance.TopLeft);
break;
case AnchorType.TopCenter:
SetAnchor(CameraFit.Instance.TopCenter);
break;
case AnchorType.TopRight:
SetAnchor(CameraFit.Instance.TopRight);
break;
}
}
void SetAnchor(Vector3 anchor) {
Vector3 newPos = anchor + anchorOffset;
if (!transform.position.Equals(newPos)) {
transform.position = newPos;
}
}
// Update is called once per frame
#if UNITY_EDITOR
void Update () {
UpdateAnchor();
}
#endif
}
Hope this can help, for more info please read the article that I've linked above.
I am trying to pick objects in the bullet physics world but all I seem to be able to pick is the floor/ground plane!!! I am using the Vuforia SDK and have altered the ImageTargets demo code. I have used the following code to project my touched screen points to the 3d world:
void projectTouchPointsForBullet(QCAR::Vec2F point, QCAR::Vec3F &lineStart, QCAR::Vec3F &lineEnd, QCAR::Matrix44F &modelViewMatrix)
{
QCAR::Vec4F normalisedVector((2 * point.data[0] / screenWidth - 1),
(2 * (screenHeight-point.data[1]) / screenHeight - 1),
-1,
1);
QCAR::Matrix44F modelViewProjection;
SampleUtils::multiplyMatrix(&projectionMatrix.data[0], &modelViewMatrix.data[0] , &modelViewProjection.data[0]);
QCAR::Matrix44F inversedMatrix = SampleMath::Matrix44FInverse(modelViewProjection);
QCAR::Vec4F near_point = SampleMath::Vec4FTransform( normalisedVector,inversedMatrix);
near_point.data[3] = 1.0/near_point.data[3];
near_point = QCAR::Vec4F(near_point.data[0]*near_point.data[3], near_point.data[1]*near_point.data[3], near_point.data[2]*near_point.data[3], 1);
normalisedVector.data[2] = 1.0;//z coordinate now 1
QCAR::Vec4F far_point = SampleMath::Vec4FTransform( normalisedVector, inversedMatrix);
far_point.data[3] = 1.0/far_point.data[3];
far_point = QCAR::Vec4F(far_point.data[0]*far_point.data[3], far_point.data[1]*far_point.data[3], far_point.data[2]*far_point.data[3], 1);
lineStart = QCAR::Vec3F(near_point.data[0],near_point.data[1],near_point.data[2]);
lineEnd = QCAR::Vec3F(far_point.data[0],far_point.data[1],far_point.data[2]);
}
when I try a ray test in my physics world I only seem to be hitting the ground plane! Here is the code for the ray test call:
QCAR::Vec3F intersection, lineStart;
projectTouchPointsForBullet(QCAR::Vec2F(touch1.tapX, touch1.tapY), lineStart, lineEnd,inverseProjMatrix, modelViewMatrix);
btVector3 btRayFrom = btVector3(lineEnd.data[0], lineEnd.data[1], lineEnd.data[2]);
btVector3 btRayTo = btVector3(lineStart.data[0], lineStart.data[1], lineStart.data[2]);
btCollisionWorld::ClosestRayResultCallback rayCallback(btRayFrom,btRayTo);
dynamicsWorld->rayTest(btRayFrom, btRayTo, rayCallback);
if(rayCallback.hasHit())
{
char* pPhysicsData = reinterpret_cast<char*>(rayCallback.m_collisionObject->getUserPointer());//my bodies have char* messages attached to them to determine what has been touched
btRigidBody* pBody = btRigidBody::upcast(rayCallback.m_collisionObject);
if (pBody && pPhysicsData)
{
LOG("handleTouches:: notifyOnTouchEvent from physics world!!!");
notifyOnTouchEvent(env, obj,0,0, pPhysicsData);
}
}
I know I am predominantly looking top-down so I am bound to hit the ground plane, I at least know my touch is being correctly projected into the world, but I have objects lying on the ground plane and I can't seem to be able to touch them! Any pointers would be greatly appreciated :)
I found out why I wasn't able to touch the objects - I am scaling the objects up when they are drawn, so I had to scale the view matrix by the same value before I projected my touch point into the 3d world (EDIT I also had the btRayFrom and btRayTo input cooordinates reversed, it is now fixed):
//top of code
int kObjectScale = 100.0f
....
...
//inside touch handler method
SampleUtils::scalePoseMatrix(kObjectScale, kObjectScale, kObjectScale,&modelViewMatrix.data[0]);
projectTouchPointsForBullet(QCAR::Vec2F(touch1.tapX, touch1.tapY), lineStart, lineEnd,inverseProjMatrix, modelViewMatrix);
btVector3 btRayFrom = btVector3(lineStart.data[0], lineStart.data[1], lineStart.data[2]);
btVector3 btRayTo = btVector3(lineEnd.data[0], lineEnd.data[1], lineEnd.data[2]);
My touches are projected correctly now :)