Coordinates again

I am happy with the latest update of the manual and example on setting up a 2D screen in the editor so I finally managed to get the 0,0 bottom screen.
I used to have viewport on top in the hierarchy but now I have viewport under the TCastleImage. Viewport is autosized and set transparant, so it is the same size as the background picture and 0,0 is bottom left picture. At last. It has cost me many weeks of frustration because 0,0 was always centre of the screen.

But!
When I add a Transform in the editor under the viewport interface it is still 0,0 in the centre of the screen.
When I set up a Transform translation 0,0 in code it is set allright. (bottom left of the background). So how can I get the transforms set up in cge to background picture coordinates so they have the same translation as in the code? Do I have to change something in the root interface?

The things you set by code should always have the same effect as the things you set using the editor. That’s because they really just execute the same thing. Editor just modifies the properties of actual objects, with the same implementation as they have in game.

If this is not the case, submit a minimal testcase to reproduce the problem.

Assuming you created viewport using “Viewport (Configured for 2D)” menu item in the editor (which equals to creating in code MyViewport := TCastleViewport.Create(...) and then calling MyViewport.Setup2D) then the (0,0) translation is in the bottom-left corner of the viewport.

I just confirmed it by a quick test.

Note: If I add a TCastleScene that visualizes an image (by setting MyScene.URL to castle-data:/test_texture.png in editor) then the image center is in the bottom-left corner of the viewport. That is how TCastleScene is supposed to work (it’s the best default in general, even though probably not what you want in this case – but I’ll explain below what to do). See screenshot.

If you want to move the image you can just set scene translation. E.g. here I set the Translation X and Y = 128, as my test image has size 256x256. This makes the bottom-left image corner match the bottom-left viewport corner, and it will work regardless of how you move/resize your viewport or window.

If you want to center the image, and make it always centered (regardless of viewport/image size), you can instead leave scene Translation at 0, and only adjust Viewport.Camera.Orthographic.Origin to 0.5,0.5. This effectively moves the camera to the middle of the viewport, and so for every TCastleTransform inside the viewport the transformation (0,0) will now mean “the middle of the viewport”, not “bottom-left viewport corner”.

Of course you can also center the image by just moving it, i.e. set particular Scene.Translation.X/Y – but then you assume a particular viewport and image size. Though it may be OK, if you use UI scaling (so viewport will be treated as having the same size, regardless of the actual window size) and you don’t plan to tweak the image.

Attaching the project so you can play with:

my-new-project-v2d.zip (135.3 KB)

Please note that in this project the Transform1 (used as Scene1 parent) is completely unnecessary, it doesn’t make any transformation, everything I described above would be completely valid anyway. I only added it for test.

Many thanks for this explanation. It all makes sense so I wondered why it did not work in my design.
Then I found out I had only looked at the parent transform translation coordinates of my sprite character and not at its underlying child scenes translation coordinates. They were still set to the middle of the screen (old setup). So I set them all to 0,0 and now it’s fine. :slight_smile:

You can ignore my previous (still unanswered posts) about it now, as they were all related to this.
I guess I do not need the ViewportTo2D conversion anymore (except for the windows mouse correction).

Now I have 2 questions on expanding this (see pictures)

  1. How can I get rid of the black borders when I maximise the window to full screen? The borders are not there if I just open the standard window size.
  2. I cannot get the sorting of transforms right, depending on their Y value.
    The first picture shows the scenes in order of loading from left to right; each scene to the right has a smaller Y value so it has to be placed in front of the previous one.
    So without sorting the first loaded transform (the girl in red) is always placed in front of the others. I get that.

But when I use SortBackToFront it should be the other way around: every transform right of the previous one should be placed in front of the previous one, as its Y coordinate is smaller.

As you can see the 3rd and 5th characters are placed behind the previous. I don’t get it.
This is the code that should sort it out.
I tried the Viewport.Items.SortBackToFront2D on different places but the result was always as above.
How can I get the transform with the smallest Y coordinate always place in front?


Procedure TStatePlay.CreateNPC;  // load all NPC characters that are on location
var I, NR: Integer;
begin
  NR := 0;  //start with no NPC on location

  for I := 1 to 6 do 
  begin
    if NPCinfo[I].Location = Location.Name then
    begin
      inc(NR); // add this NPC to location
      Location.NPC[NR] := TAvatar.Create;
      Location.NPC[NR].Personalia.FirstName := lowercase(NPCinfo[I].Personalia.FirstName);
      Location.NPC[NR].Personalia.LastName:= lowercase(NPCinfo[I].Personalia.LastName);
      Location.NPC[NR].Appearance.Outfit:= 'casual';
     
      Location.NPC[NR].Directory := 'castle-data:/characters/' + NPCinfo[I].Personalia.FullName + '/' + NPCinfo[I].Appearance.Outfit + '/' + NPCinfo[I].Personalia.FirstName;

      Location.NPC[NR].X := NPCinfo[I].X;
      Location.NPC[NR].Y := NPCinfo[I].Y;
 
      Location.NPC[NR].S := 0.75 - (0.0010 * NPCinfo[I].Y);

      NPCScene[NR] := TCastleTransform.Create(Application);
      Viewport.Items.Add(NPCScene[NR]);
 
     Location.NPC[NR].StandIdleScene.URL:= Location.NPC[NR].Directory + 'standidle.starling-xml';
   
     NPCScene[NR].Add(Location.NPC[NR].StandIdleScene);
 
    Viewport.Items.SortBackToFront2D;
  end;

  NPCScene[NR].Scale := Vector3(Location.NPC[NR].S, Location.NPC[NR].S, Location.NPC[NR].S);

Depends. What would you like to happen there?

The borders appear because your image aspect ratio doesn’t match your final window aspect ratio (I guess - I do not see your code). I.e. the rendering area (window size, but without Windows titlebar) has more “panoramic” aspect ratio (width / height) than your image.

When the image aspect ratio doesn’t match your final window aspect ratio, something must happen:

  1. Either the image “fits” within the window, which means you will see the background underneath. So black margin on top+bottom or on left+right. If you have set your viewport to be FullSize and left Viewport.Transparent at false (default) then the background color is configurable by Viewport.BackgroundColor. You can customize this color, or set Viewport.Transparent to true and then the other UI controls underneath will be visible.

    IOW, the black margins are the safest way to ensure that all your viewport is visible.

  2. Or the image “encloses” the window. The the parts of the image are “cut off”, either from top+bottom, or from left+right (depending on whether user has smaller or larger aspect ratio than your image).

    Remember that it means that part of your world will be “cut off” from view. Place your characters etc. such that they are always visible within the safe area in this case (visible in all sensible window aspect ratios).

  3. There is also a mixed approach. You can show black margin on top+bottom (when user aspect ratio is more square) but make the image enclose the window when the user aspect ratio is more panoramic. So you will never see black margin on left+right.

To implement option 3 (which I would advise you as its simplest) just set Viewport.Camera.Orthographic.Width to your image width. Leave the Viewport.Camera.Orthographic.Height at default (zero). This way the world inside the viewport (background image, and everything else) can always safely “think” that the viewport width is just constant, regardless of how user resizes the window.

Attaching an example that shows this.

my-new-project-v2d-2.zip (135.3 KB)

(

To implement option 2, you would set from code either Viewport.Camera.Orthographic.Width or Viewport.Camera.Orthographic.Height to non-zero, and the other to zero, in the overridden state Resize method, depending on current window window width / height. So the viewport size would either adjust horizontally or vertically. But as this is more complicated, and not really useful in practice (it is too easy to accidentally place NPC at the left/right edge of the world then, and it will not be visible for users with square screens) I don’t advise this method.

)

Note that you can set in CGE Window.FullScreen := true to make the rendering area match your screen. The full-screen window means that decorations (like Windows titlebar) are hidden. You can even toggle it during the game, like if Event.IsKey(keyF11) them Window.FullScreen := not Window.FullScreen;. Of course you still have to account that in practice, users can have any screen size, so your game must look good on any window aspect ratio anyway (even if you force the window to always be FullScreen).

To be clear, the Viewport.Items.SortBackToFront2D; sorts by looking at bounding boexs and decreasing Z. But you make it indirectly depending on Y by setting scale to 0.75 - (0.0010 * NPCinfo[I].Y);,

Note: The code your attach has wrong indentation, and there is missing end.

I am not 100% sure what is the problem – I would need to have a testcase that I can compile and run. I know you attached some testcases in older threads, and I didn’t have time to address your questions then – sorry! But I suppose the code+design also changed since then, so you need to attach a new testcase to get best help. Try to minimize it (code and design) as much as possible.

But I can point out:

  • you probably wanted to assign NPCScene[NR].Scale := ... within the above if NPCinfo[I].Location = Location.Name then. Otherwise you just override the Scale value for the previous NR value.

  • you want to call Viewport.Items.SortBackToFront2D; once it is all set, at the very end.

  • I would call it NPCTransform, not NPCScene as it is not a TCastleScene, it is TCastleTransform.

  • Make sure the scale is always >= 0 to avoid behavior you probably don’t expect. Make sure that 0.75 - (0.0010 * NPCinfo[I].Y) is always > 0, make a warning/exception in code if it is not.

So it should look like this

for ... do
begin
  if some condition then
  begin
    NewTransform := TCastleTransform.Create(Application);
    NewTransform.Add(SomeScene);
    NewTransform.Scale := Vector3(S, S, S);
    Viewport.Items.Add(NewTransform);
  end;
end;

// after everything is done 
Viewport.Items.SortBackToFront2D;

Note that instead of scaling in Z, you can also move in Z. And only scale in XY for visual effect.

I would avoid in general scaling if you don’t really want to scale (“squeeze things”), and instead you actually wan to move it (“to put something in front/back”). While scaling in Z doesn’t change anything for sprite sheets (so it should be harmless), but it would squeeze other formats that are 3D or that have layers (like 2D Spine).

So you can do this:

for ... do
begin
  if some condition then
  begin
    NewTransform := TCastleTransform.Create(Application);
    NewTransform.Add(SomeScene);
    NewTransform.Translation := Vector3(0, 0, Z); // maybe Z can be taken from S, but it doesn't have to
    NewTransform.Scale := Vector3(S, S, 1);
    Viewport.Items.Add(NewTransform);
  end;
end;

// after everything is done 
Viewport.Items.SortBackToFront2D;

I tested this solution on your example from Setting Scene coordinates - part 2 , this is what I would suggest:

I choose this option.
First thing I noticed was that my designed Transforms in CGE were immediately sized smaller.
No problem because they were already too large and now they are the size when I originally designed them when I was still working with Window instead of Viewports. :slight_smile:
The black borders remain but the viewport is indeed sized to the background graphic now, so that when my character “walks” off location it is not shown anymore on the border (which was part of the viewport).

Yes, but I need scaling because the further away the smaller the characters have to be.
But you got me thinking and then I realised I confused the scaling with the Translation Z values.
I studied your

and now I have the following complete code that actually works!

Procedure TStatePlay.CreateNPC;  // load all NPC characters that are on location
var I, NR: Integer;
begin
  NR := 0;  //start with no NPC on location
  for I := 1 to NPCAmount do //  check all NPC for their location
  begin
    if NPCinfo[I].Location = Location.Name then
    begin
      inc(NR); // add this NPC to location

      Location.NPC[NR] := TAvatar.Create;
      Location.NPC[NR].ID := NPCinfo[I].ID;
      Location.NPC[NR].Personalia.FirstName := lowercase(NPCinfo[I].Personalia.FirstName);
      Location.NPC[NR].Personalia.LastName:= lowercase(NPCinfo[I].Personalia.LastName);
      Location.NPC[NR].Personalia.FullName:= NPCinfo[I].Personalia.FirstName + ' ' + NPCinfo[I].Personalia.LastName;
      Location.NPC[NR].Appearance.Outfit:= 'casual';
      Location.NPC[NR].Directory := 'castle-data:/characters/' + NPCinfo[I].Personalia.FullName + '/' + NPCinfo[I].Appearance.Outfit + '/' + NPCinfo[I].Personalia.FirstName;

      if URIFileExists(Location.NPC[NR].Directory + 'standidle.starling-xml') then Location.NPC[NR].StandIdleScene.URL:= Location.NPC[NR].Directory + 'standidle.starling-xml';

      Location.NPC[NR].X := NPCinfo[I].X;
      Location.NPC[NR].Y := NPCinfo[I].Y;
      Location.NPC[NR].Z := NPCinfo[I].Y;
      Location.NPC[NR].S := 1 - (0.0010 * NPCinfo[I].Y);

      NPCTransform[NR] := TCastleTransform.Create(Application);
      NPCTransform[NR].Add(Location.NPC[NR].StandIdleScene);
      NPCTransform[NR].Translation := Vector3(NPCinfo[I].X, NPCinfo[I].Y, Location.NPC[NR].Z);
      NPCTransform[NR].Scale := Vector3(Location.NPC[NR].S, Location.NPC[NR].S, Location.NPC[NR].S);
      Viewport.Items.Add(NPCTransform[NR]);
     end;

    Viewport.Items.SortBackToFront2D; 
end;

As proof I send you the picture again. I have added a 7th character with a Y value that is 1 higher than the 6th character. And it is placed allright behind character 6.
I am very happy with this.

Thanks for all the time on helping me with the questions. I wrecked your last night’s sleep. I should send you a bunch of flowers. :wink:
No, I will raise my pledges as a Patreon. Keep up the good work!

One more question:

If my PlayerTransform is walking out of the location I want to load the next location.

PlayerTransform.Translation := Vector3(Player.X, Player.Y, 10);

if Player.X > 2560 then …(load next location)
Works okay with the background width of 2560, but with
if Player.X < 0 then … it already happens when Player.X is not 0.
I need to make Player.X approx. -400 to get to the left exit of the background. I don’t get it. is there still something in the design that has to be adjusted?

If you literally test if Player.X < 0 then then my guess is that the check is correct, Player.X really reaches 0, however your sprite sheet pivot is more to the right than you think.

The PlayerTransform.Translation is just a point, and where exactly is this point with regards to your sprite (in the middle, on the left, on the right) depends on whether you have some translations in your sprite (i.e. in hierarchy of TCastleTransform in PlayerTransform). By default the pivot is in the middle.

In the general case, you should get the bounding box PlayerTransform.LocalBoundingBox and adjust to it. E.g. to fire something as soon as character touches the left edge:

if Player.X + PlayerTransform.LocalBoundingBox.Min.X < 0 then

To fire something when character fully disappears at the left edge:

if Player.X + PlayerTransform.LocalBoundingBox.Max.X < 0 then

In a specific application, you can practically hardcode this size by just doing

if Player.X < -400 then

and if this looks good, then this is a simple and good solution.

It all makes sense but I was still wondering why the leaving location Player.X > 2560 was working okay with the point in the middle and Player.X < 0 not.
Meanwhile I sorted out another problem on the sorting and scaling (described above, I will update this because I found that it did not work okay despite my latest code update above).
Then I came back, made the Player.X value shown on screen with label and noticed my character actually walking to player.X 0. (!)
My guess is that the PlayerTransform was scaled larger than its child scenes and now with editing the above problem the side effect was that it was set allright eventually.

Thanks. Indeed the loading of the next screen should execute when the whole transform is out of sight.
I did not know how to do this so you already answered my next question.
:slight_smile:

I reworked the displaying order and scaling because when I added my main character it was always in front of the NPC characters. Then I found out Z should be negative.

Now I have:

Procedure TStatePlay.CreateNPC;  // load all NPC characters that are on location
var I, NR: Integer;
begin
  NR := 0;  //start with no NPC on location
  for I := 1 to NPCAmount do // NPCAmount do // check all NPC for their location
  begin
    if NPCinfo[I].Location = Location.Name then
    begin
      inc(NR); // add this NPC to location

      Location.NPC[NR] := TAvatar.Create;
      Location.NPC[NR].ID := NPCinfo[I].ID;
      Location.NPC[NR].Personalia.FirstName := lowercase(NPCinfo[I].Personalia.FirstName);
      Location.NPC[NR].Personalia.LastName:= lowercase(NPCinfo[I].Personalia.LastName);
      Location.NPC[NR].Personalia.FullName:= NPCinfo[I].Personalia.FirstName + ' ' + NPCinfo[I].Personalia.LastName;
      Location.NPC[NR].Appearance.Outfit:= 'casual';
      Location.NPC[NR].Character.Relationship:= NPCinfo[I].Character.Relationship;
      Location.NPC[NR].Character.Obedience:= NPCinfo[I].Character.Obedience;
      Location.NPC[NR].Directory := 'castle-data:/characters/' + NPCinfo[I].Personalia.FullName + '/' + NPCinfo[I].Appearance.Outfit + '/' + NPCinfo[I].Personalia.FirstName;

      if URIFileExists(Location.NPC[NR].Directory + 'standidle.starling-xml') then Location.NPC[NR].StandIdleScene.URL:= Location.NPC[NR].Directory + 'standidle.starling-xml';

      Location.NPC[NR].X := NPCinfo[I].X;
      Location.NPC[NR].Y := NPCinfo[I].Y;
      Location.NPC[NR].Z := -NPCinfo[I].Y/10;
      Location.NPC[NR].S := 1 - (0.0010 * NPCinfo[I].Y);

      NPCTransform[NR] := TCastleTransform.Create(Application);
      NPCTransform[NR].Add(Location.NPC[NR].StandIdleScene);
      NPCTransform[NR].Translation := Vector3(NPCinfo[I].X, NPCinfo[I].Y, Location.NPC[NR].Z);
      NPCTransform[NR].Scale := Vector3(Location.NPC[NR].S, Location.NPC[NR].S, Location.NPC[NR].S);
      Viewport.Items.Add(NPCTransform[NR]);
     end;         

Then with similar (but designed in the editor) the Player.
And then in TUIState update:

Player.Z := -Player.Y/10;
Player.S := 1.5 - (0.0010 * Player.Y);
PlayerTransform.Translation := Vector3(Player.X, Player.Y, Player.Z);
PlayerTransform.Scale := Vector3(Player.S, Player.S, Player.S); 

for Count := 1 to Location.NPC_Total do
begin
   NPCTransform[Count].Translation := Vector3(Location.NPC[Count].X, Location.NPC[Count].Y, `Location.NPC[Count].Z); 
end;

The placing order depending on Y positions works okay, except the scaling of the PlayerTransform when moving up the screen; it only gets smaller from a certain point and so it is too big compared to NPC. (see picture 3)
How can I adjust this?

Sorry, I truncated the scale value with the label output.
I added the values of the left most NPC (the lady in red) :slight_smile: to compare.
Though “her” scaling value is smaller (0.4) than the main player with almost equal Y values, the main player is still larger.
As I have told before I am bad at Mathematics, so how can I make the main player scaling cope with the NPC’s?

Got it fixed.
I took the minimum Y value of the player character (on design), put the same Y coordinates of the lady in red and scaled it the same size. The other NPC automatically followed.
:slight_smile:
I am still open for any ideas on improvement.

I made a small video on the result.
The player character starts at the reference coordinates that are the same as the first NPC.
Scaling and walking between characters works.
There is no boundary and collision checking (yet) so the player walks through the NPC :wink:
And I have to set the playanimation frame for direction so that with every stop the character is not always showing the first (front) image.
But I am glad this works at last. Next thing will be adding the diagonal walking and moving to positions with mouse clicks.

https://www.youtube.com/watch?v=ZVHniwQOzBc

1 Like

@michalis An additional question on this: I would like to set the destination coordinates of a moving scene not the centre/middle of the boundingbox but at the feet of a sprite. So if walking north
something like if Player.Y = Player. Y+ PlayerTransform. LocalBoundingBox.Max.Y/2 then… .
I do not know if this is the right approach?

Update:
Hm, it works but is not accurate with scaling because (I guess) the boundingbox size remains the maximum size, so when the spritescene is scaled smaller it still responds to the full dimension size of boundingbox.
So how can I get this right?

To get the size right, you need to query the correct box size.

  • PlayerTransform.LocalBoundingBox is the box of things in PlayerTransform, but disregarding any transformation (translation, scaling, rotation) at PlayerTransform (or PlayerTransform parents). It only looks at transformation inside children of PlayerTransform.

  • PlayerTransform.BoundingBox is the box of things in PlayerTransform, taking into account the transformation (translation, scaling, rotation) at PlayerTransform. So it is affected e.g. by PlayerTransform.Scale. And by all transformations in PlayerTransform children, as above. It still ignores the scaling above the PlayerTransform, i.e. by PlayerTransform parents.

  • PlayerTransform.WorldBoundingBox is the box of things in PlayerTransform, taking into account all the transformations. Including the transformation of parents above PlayerTransform. Up to the root, which is Viewport.Items.

There is a no direct answer “which to use” – because it depends. You have to make sure to “think” (and make calculations) within a consistent coordinate space, e.g. calculate size in the same coordinate space where you perform the movement.

Thanks for pointing out the differences between the Boundingboxes as I found them confusing.
As you can see in my last demo in topic “creating a strategic adventure game step by step” I already have sorted it out by using the Boundingbox.min.Y and using Worldboundingbox.
So the character now stops when his feet are at the destination point instead of the middle pivot.
:slight_smile: