Dropdown For Different Aspect Ratios

Hey all,

I’ve been fiddling with this for awhile now and just thought I’d ask if anyone could think of the setup for this off the top of their heads. I have a button on the top right hand corner of my screen that will make a drop down come down from the top and cover the top half of the screen. The only issue is that I am trying to accommodate multiple aspect ratios and it doesn’t seem like the anchor/pivot system on the UI components can handle something that is anchored on the top of the screen extending down to the middle if the height of the viewport is variable. It needs to not appear on the screen and I cannot manually set the anchor off the screen via the editor and then move it onto screen through code.

Can anyone think of a better way to set this up? Normally I just fix the aspect ratio and tween it onto the screen but that won’t work in this scenario.

I’m having trouble visualising what you are trying to do. Can you provide images/diagram of what you mean?

Sorry yes. I’m having trouble cutting this down much but that first post was hard to follow I admit.

Okay so I want an image entity that fills the top half of the screen of no matter what the aspect ratio is. This is fairly simple to do, you just use the anchor to fill horizontally (from 0 - 1) and fill half vertically (from 0.5 - 1). I then set the pivot on the top of the screen so that I can “zero” the transforms for the entity being on screen. The issue is that this does not make the (0,0,0) local position at the top of the screen. When I want to move it off screen, this entity now has transforms associated with the custom anchor and pivot attributes I just gave it so I can no longer move the entity off screen (lets say putting y at 10,000) and then move it back on screen by setting the entity’s position back to 0,0,0 which I want to put it at the top of the screen.

Here are some images of it working on different aspect ratios:


The “fix” I can think of is just noting what the transform values of the onscreen position are but I can’t do that if users are going to have different initial transform values. This gets even more complicated if you want to support the user’s ability to change their aspect ratio mid game (by flipping their phone for example).

So TLDR: I am having trouble tweening an entity off screen and then back on when the entity uses anchors and pivots to fill the top half of the screen. This is because it has transforms associated with its local position. I cannot tween it back to its original position at the top of the screen because that value might change in the middle of the application running.

Ah, I see. Try tweening the global position to -2 on the Y instead (just remember you can’t tween the global position directly and have to use the update callback: see this reply: [SOLVED] Tweening to World Coordinates Instead of Local Coordinates - #9 by mcmorry)

The global position for elements are normalised between -1 and 1 for the min and max bounds of the screen. A global position of -1, -1 would be bottom left (or top left, I can’t remember which direction the Y axis is in)

See this thread for more details: 2D image follows 3D object - #3 by yaustar

Worked beautifully. Thanks so much. So for this to stick, I just have to allow the anchor/pivot system to control the local position and only touch the global position of the ui entities, is that correct? What happens if I translate an entity locally 100 units in the x direction that is filling the screen but then the user changes the aspect ratio. Does it continue to be the size of the screen just translated in the x direction 100 units? Seems like there are almost two layers of local translation at play here. 1 layer at the screen’s ratio level and another layer being whatever the developer does to the entity in local space.

The anchor/pivot system/local position is to control the position relative to it’s parents. The global position sets the position in the screen space and updates the local position accordingly.

Much like setPosition vs setLocalPosition with a entity in the world.

Sorry, I don’t understand. It depends on how the element has been setup and how it’s anchored and how the screen element scale value. If you move an element 100 units to the left, its always going to be 100 units to left relative to the parent and taking into account of the anchors regardless of the actual screen size.

Hmm… I think there may be some misunderstanding here?

Okay let me try again. Sorry I seem to be having trouble explaining myself clearly. Probably coming from me not understanding the system correctly.

So, let’s say that you have a 100 by 100 pixel screen. An image entity is the root of the 2D screen (there are no parents affecting its translation) and its anchors make it fill the entire 100x100 pixel screen. The pivot is at the top of the screen so the global translation should be (0.5,1,0) while the local translation should be (50, 100, 0). If I translate this entity locally up by 50 pixels the new local position should be (50,150,0). If I then change the size of the screen to 100x500 pixels, is the new local y position 550, or does the aspect ratio change overwrite the local change I just made because the pivot is always at the top of the screen?

I guess what I’m really asking here is, does an aspect ratio change make the engine recalculate the local position? Thats what I meant by multiple layers. Whenever there is an aspect ratio change, does it take into account the anchors and pivots to calculate local position and then add in whatever the user has done on top of that or does it overwrite it?

Local position is always relative to the anchor to the parent. The situation where this might not be be true is if it’s using split anchors and there’s some extra logic applied for position due to this.

That is the situation I’m asking about. I’ve been using split anchors in this situation. Sorry if I didn’t make that clear.

Are you able to point me to what the extra logic is?

If you have an element that has it’s pivot in the centre (0.5, 0.5) and split anchors on the X axis (left and right).

The element is sized by the anchors and margins. This means the width will change and so will the X local position because now the pivot is positioned differently on the screen as it’s been resized by the anchors.

You cannot change the local position along the axis you have split anchors on

Hi @Jake_Johnson and @yaustar, I hope you don’t mind me to add to this thread as I was planning to make a similar drop down. Here is a small test project https://playcanvas.com/editor/scene/1345410. If I set the screen scale blend to 1 then I can set the drop down to to be just off top of the screen fine either by adding 1 to the Y world position or by adding 360 to Y local position. However if scale blend is less than 1 then at certain device orientations eg phone portrait mode, the drop down that I want hidden just off the top of the screen becomes partly visible due to the scale blend scaling. So the question is how to set the Y position correctly for any value of scale blend so that the drop down is always correctly positioned just off the top of the screen, ready to be tweened onto the screen?

@Kulodo133 Do you have an image of what you want it actually look like? It’s bit hard to tell from your description

Sure will provide in a few hours.

Here is my dummy drop down menu in scene linked above.

Let’s say I want to move “Frame” off the top of the screen. I can edit the local Y position or the world Y position. In the next screenshot adding 360 to Local Y position would move it just off the top of the screen. I’ve added 355 just so you just see the bottom of the Frame in these screenshots. The scale blend is 1. The Landscape result on the right is what I expect.

Next I switch to Portrait. Again the scale blend is 1 and the result is what I expect.

Next I set the scale blend to 0.5. In Landscape the result is what I expect but in Portrait I see the drop down Frame extending down from the top where I want it to be be the same as the case where scale-blend is 1 ie hidden off the top of the screen.

How can I reliably position the Frame just off the top of the screen that is not effected by scale blend and as the user may be rotating a device between landscape and portrait?

As you have split Y anchors and you are trying to position it on the Y axis, you will have to be aware of screen resizes (event https://developer.playcanvas.com/en/api/pc.GraphicsDevice.html#event:resizecanvas) and your UI code will have to accommodate accordingly and move your frame.

If the Y anchor was not split, this would be easier as the Y position would be relative to the fixed Y anchor and you wouldn’t need to handle this but it would assume that the frame is a fixed height rather than always half the screen

Thanks @yaustar that works.

var UiTest = pc.createScript('uiTest');

UiTest.prototype.initialize = function() {
    this.frame = this.app.root.findByName('Frame');
    this.setFramePosition();
    this.app.graphicsDevice.on('resizecanvas', this.setFramePosition, this);
};

UiTest.prototype.setFramePosition = function() {
    var localPosition = this.frame.getLocalPosition();
    var height = this.frame.element.height;
    var scaledHeightFactor = height / 360;
    var newYPosition = scaledHeightFactor * 355;
    this.frame.setLocalPosition(localPosition.x, newYPosition,localPosition.z );
};
1 Like

For completeness I added a tween to show the effect I was going for. It could be improved by considering the current value of the tween when rotating the device rather then starting from the beginning, and also by considering runtime changes to the screen scale blend ( which is unlikely )

2 Likes

Could you explain a couple lines in your code? specifically these two:

var scaledHeightFactor = height / 360;
var newYPosition = scaledHeightFactor * 355;

How do the 360 and the 355 integers play into this?

Hi @Jake_Johnson, in the test app, linked above, before I added the tween, I just wanted to show the bottom of the drop down while most of it was positioned off the top of the screen just so I could see what was going on. Hence the 355 rather than 360 ( which was half the screen reference height). You can ignore that now. Here is the relevant code now with the tween added. The key point of this code is the this.frame.element.height; contains the height that has been scaled up/down due to screen scale blend, and it can be used to set the local Y position.

var UiTest = pc.createScript('uiTest');

UiTest.prototype.initialize = function() {
    this.tween = null;
    this.frame = this.app.root.findByName('Frame');
    this.updateTween();
    this.app.graphicsDevice.on('resizecanvas', this.updateTween, this);
};


/**
 * Improvements:
 * - consideration the current value of the tween when stopping the tween and start a new tween
 * - unlikely to happen but if screen scale blend changes at runtime, then call this method.
 */
UiTest.prototype.updateTween = function() {
    var _this = this;
    var localPosition = this.frame.getLocalPosition();
    var height = this.frame.element.height;

    if (this.tween) {
        this.tween.stop();
    }

    let v = {h:0};
    let end = {h:height};
    let duration = 1;

    this.tween = _this.app.tween(v).to(end, duration, pc.Linear).yoyo(true).repeat(Infinity).on('update', function () {
        _this.frame.setLocalPosition(localPosition.x, v.h,localPosition.z );
    }).start();

};

1 Like