[solved] How to achieve this type of camera

Yeah, I got this from just googling the game and it makes me sad :confused: I hadn’t seen this game before. They try something different and it gets bashed because it doesn’t do what everyone else does already. I like the aesthetic and I think they did a really great job with the sprites so it works well with the environment.

1 Like

Thanks for your comment mate - yeah, I think you and Jjagg are right on this one. I tried to do that today by using the Unproject function to get the correct screen position for the sprites. Something like this:

            Vector3 unprojected = graphics.GraphicsDevice.Viewport.Unproject(position, camera.Projection, camera.View, World());
            screenPosition = new Vector2(unprojected.X, unprojected.Y);
            rectangle = new Rectangle((int)screenPosition.X, (int)screenPosition.Y, texture.Width, texture.Height);

But something with the matrices is not clicking right, and I am getting strange results. This is the position of a billboard in 3D space:

And this is the sprite drawn at unprojected screen position:

I’m not sure, but maybe I need an additional matrix here to transform the world position somehow?

You shouldn’t be using unproject.
You’re character’s position is stored in world space, right? I.e. it has a 3D position, stored in the same space as your models’ positions are stored. You can apply your transforms to that position (just like you do to models) to get the screen position. Isn’t that what you did in the first image? Then you need to figure out scaling.

I used have a project like this and we called it 2.5 games not 3D games, where the world is 3D and all characters are just a texture of BILLBOARD from sprite sheet.

I will just use BILLBOARD in a 2.5 game type ^ _^ Y

Something like this I created from my old games

Actually, @guy120334913 I just remembered there are some factory methods in Matrix for billboarding. You might be able to leverage either Matrix.CreateConstrainedBillboard or Matrix.CreateBillboard. With the Constrained variant you can specify an axis to rotate around instead of rotating in full 3D.

I’m really curious as to how this could be achieved. Could this be a combination of a perspective camera for the 3D environment and an orthographic camera for the billboards? I’m puzzled >:(((

Looking at the lines in that screenshot it’s clearly orthographic for everything. In perspective projection there’d be progressiveness to the angles of those drawn lines. It looks to just be an orthographic projection that’s been angled down 45 degrees and left-or-right (who knows) 45 degrees. Just because it’s orthographic doesn’t mean it can’t look off into a direction other than directly down a Unit-Axis.

I’m not sure you’d ever really be able to tell just by looking at a still image. If you pull the camera back far enough and use a tight field of view, you can get very near parallel lines with a perspective projection.

You could also do manual billboarding in the shader, google for xna particle sample to see a good base for that.

Ah, I didn’t know that. I thought that this is what the unproject method was for? What would be the correct use of it?

In fact I tried to do what @markus suggested earlier, but it yielded a similar result.

Yeah, the character has a 3D position in world space. In the first image I am using a 3D plane with the sprite used as a texture, the second image was my attempt to draw a 2D sprite at the unprojected on-screen position.

I will try to use the Billboarding functions for Matrix - I wasn’t able to get it to work as I wanted the first time around, but maybe I should look into it again.

It’s to transform a point from screen space coordinates (in pixels) to world space coordinates. The XNA docs mention how to use it for mouse picking. You can use unproject to project mouse location (in screen space) to a point in world space on the near plane and one on the far plane. You can then construct a ray between these points to test which objects in the scene it intersects.

So in other words I’ve been trying to use a method that does the absolute opposite of what I wanted to do :sweat_smile:

1 Like

I didn’t test the code before posting it, but I’m pretty sure it’s right. I successfully used that method in the past. If you have some code to share, I’d be happy to take a look.

Sure, I will post the code when I get home. I would say this is likely the problem with how I set up my camera and matrices, rather than with your code. I’m saying this partly because I’ve tried implementing other stuff today (including a billboard matrix with Matrix.CreateConstrainedBillboard) and something is not clicking right.

Ok, so this is the code I used to get the on screen position of the Rectangle for the sprite:

            Vector4 worldPos4 = new Vector4(position, 1);
            Vector4 screenPos4 = Vector4.Transform(worldPos4, camera.ViewProjection);
            Vector2 screenPos = new Vector2(screenPos4.X, screenPos4.Y) / screenPos4.W;

            Rectangle rectangle = new Rectangle((int)screenPos.X, (int)screenPos.Y, texture.Width, texture.Height);
            spriteBatch.Draw(texture, rectangle, Color.White);

And these are my matrices:

        public Matrix Projection
        {
            get
            {
                return Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), aspectRatio, nearClipPlane, farClipPlane);
            }
        }
        
        public Matrix View
        {
            get
            {
                return Matrix.CreateLookAt(position, cameraLookAt, Vector3.Up);
            }
        }

        public Matrix ViewProjection
        {
            get
            {
                return View * Projection;
            }
        }
        protected virtual Matrix World()
        {
            Matrix Scale = Matrix.CreateScale(1f);
            Matrix Rotation = Matrix.CreateFromYawPitchRoll(rotation.Y, rotation.Z, rotation.X);
            Matrix Translation = Matrix.CreateTranslation(position);

            Matrix result = Scale * Rotation * Translation;

            return result;
        }

This is more or less the result I’m expecting (sprite drawn at the center of the screen):

And this is what I’m getting (ignore the scaling of the sprite, this is a separate issue):

I’m not sure what I’m doing wrong, since I’m a noob still, but it’s gotta be something, so if you have an idea I’d appreciate any help.

I believe what you are looking for here is:

Orthogonal Projection
https://www.bing.com/images/search?q=orthogonal+projection&FORM=HDRSC2

Are you using World anywhere? Usually you multiply position * World * View * Projection to get screen position, but you’re skipping the multiplication of World. Although that’s for when you start with a position in object space. Since you’re just putting your sprite at a single point, it’s possible position is already in world space, but then I’m not sure why you have the method World() at all.

EDIT: I think you need the final transformation from screen position (-1 to +1) to pixel coordinates (0 to +1):
0.5f * (new Vector2(screenPos.x, -screenPos.y) + 1)

1 Like

This is the moment when you should use graphics.GraphicsDevice.Viewport.Project() :wink: to project the sprite’s position from 3D to the screen in screen coordinates
Or do it yourself as WVP matrix as @jnoyola suggested.

1 Like

Ok, got it.

I had to add the transformation to pixel coordinates as @jnoyola and @markus mentioned, and multiply the coordinates by Viewport.Width and Viewport.Height to get the correct position:

            Vector2 pixelPos = (new Vector2(screenPos.X, -screenPos.Y) + Vector2.One) / 2 * (graphics.GraphicsDevice.Viewport.Width/graphics.GraphicsDevice.Viewport.Height);

            Point rectPosition = new Point((int)(pixelPos.X * graphics.GraphicsDevice.Viewport.Width) - texture.Width / 2, (int)(pixelPos.Y * graphics.GraphicsDevice.Viewport.Height) - texture.Height);

Here is the final result (added one more sprite to check if they will all be positioned correctly):

Now I have to fix the scaling issue and sort the sprites correctly by depth, but I should be able to do that myself. Thanks for your help everyone, this is exaclty what I wanted!

6 Likes

Great you got it working. There’s still a small problem with the code though. The division of viewport width by viewport height doesn’t make sense here, you should use Vector2(viewportWidth, viewportHeight) instead. Then you don’t need the multiplications later.

Vector2 pixelPos = (new Vector2(screenPos.X, -screenPos.Y) + Vector2.One) / 2 * new Vector2(graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height);
Point rectPosition = new Point((int)(pixelPos.X) - texture.Width / 2, (int)(pixelPos.Y) - texture.Height);

It’s probably working fine right now because this is an integer division, which means that the result will also be truncated to integer. With your current viewport resolution that just happens to be exactly 1. As soon as you use a wider viewport this division may result in a 2, which will cause problems.

1 Like

Sure, thanks. As you said fixing this did not really change anything visibly, but I’m hoping it will help me avoid issues later. Thanks again!