Drawing Between Layers

Jan 5, 2012 at 11:22 PM
Edited Jan 6, 2012 at 5:05 AM

I've been eyeballing the demo code for a while now, and although I believe I understand how the BeforeDraw and AfterDraw events work, I cannot seem to get them to work for me. I can get my character sprite to draw ontop of all layers by using a standard draw method, but obviously the dynamic I could get by being able to place him underneath layers would be most desirable. Can anybody elaborate on how this feature works?

Edit: Also, this is unrelated, but I'm having a hell of a time placing a character in world coordinates and then transforming the camera to center itself on him, I feel like a total idiot. XD

 

Great tool, though, I'm determined to figure this out, this system is far too powerful to pass up. Just any help is massively appreciated!

 

Edit2: inb4 check other discussions, I have been reading all the descriptions and trying to pick any pieces of useful information I can out of them, I'm just sort of struggling here. I don't want to hard code my character to the center of the screen, because that seems like a really clunky way to solve the problem, I'd like the viewport to follow him as far as it can, because that makes it more logical when the character reaches edges. I just have no idea what that should look like in my character draw method.

Coordinator
Jan 6, 2012 at 7:51 AM

Regarding BeforeDraw and AfterDraw Layer events, have a look at this discussion.

As for making the camera follow the player character, have a look at this discussion. It details both rigid centering of the camera and also how to make it follow the player lazily. Using the second technique it is also possible to limit the viewport around the edges of the map, so that the viewport never oversteps the map's bounds.

Jan 6, 2012 at 7:55 AM

I think I may have solved my character coordinate issue, my math just sucks, so I'm cleaning that up.

However, I've tried using the BeforeDraw/AfterDraw events, and either way it seems like my character just disappears (probably behind the map), and I have no idea why? I've even tried using the BeforeDraw method on the foremost layer, which seems to be the way you'd use it if I wanted something to appear at the very front (judging from what I've read in your source code), but he still vanishes.

Coordinator
Jan 6, 2012 at 8:00 AM
Did you try to draw on the topmost layer using the AfterDraw event? This makes sure that the sprite is drawn on top of the whole map. The layers are indexed from 0, 1 onwards and the topmost layer is the one with the highest index number. Also, try using absolute coordinates to draw your sprite for now, such as (0, 0) to see if the sprite is drawn on the top-left corner regardless of scrolling.

On 6 January 2012 09:56, TheBroodian <notifications@codeplex.com> wrote:

From: TheBroodian

I think I may have solved my character coordinate issue, my math just sucks, so I'm cleaning that up.

However, I've tried using the BeforeDraw/AfterDraw events, and either way it seems like my character just disappears (probably behind the map), and I have no idea why? I've even tried using the BeforeDraw method on the foremost layer, which seems to be the way you'd use it if I wanted something to appear at the very front (judging from what I've read in your source code), but he still vanishes.

Read the full discussion online.

To add a post to this discussion, reply to this email (tIDE@discussions.codeplex.com)

To start a new discussion for this project, email tIDE@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com


Jan 6, 2012 at 8:11 AM

I ran my code twice, once verifying that my character is drawn on screen at all, and then I commented out my typical draw code and inserted it into the event handler method. I have my game displaying his screen coordinates and according to those he should be somewhere on screen, but he's nowhere to be seen. :X

Coordinator
Jan 6, 2012 at 8:40 AM
Are you using the SpriteBatch property from the DisplayDevice within your AfterDraw event code? This is important as otherwise your sprite will not be interleaved correctly with the map's layers. Can you perhaps post the event code here?

On 6 January 2012 10:11, TheBroodian <notifications@codeplex.com> wrote:

From: TheBroodian

I ran my code twice, once verifying that my character is drawn on screen at all, and then I commented out my typical draw code and inserted it into the event handler method. I have my game displaying his screen coordinates and according to those he should be somewhere on screen, but he's nowhere to be seen. :X

Read the full discussion online.

To add a post to this discussion, reply to this email (tIDE@discussions.codeplex.com)

To start a new discussion for this project, email tIDE@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com


Jan 6, 2012 at 8:44 AM

That would probably be the part I'm missing. While you're having a look, maybe you wouldn't mind helping me with my clamp method to keep my sprite on screen? My math is just awful. It makes me want to cry. XD

 

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using xTile;
using xTile.Dimensions;
using xTile.Display;
using xTile.Layers;
using Heart_of_Fire.James;

namespace Heart_of_Fire.Screens
{
    class GameplayScreen:GameScreen
    {
        #region Fields

        James.James James;
        Map map;
        xTile.Dimensions.Rectangle viewPort;
        #endregion

        #region Constructor

        public GameplayScreen(James.James james, Map aMap, xTile.Dimensions.Rectangle ViewPort)
        {
            map = aMap;
            James = james;
            viewPort = ViewPort;
            map.Layers[4].AfterDraw += OnAfterLayerDraw;
            James.WorldLocation = map.GetLayer("Collision").ConvertLayerToMapLocation(new Location(12, 17), viewPort.Size);
            viewPort.Location = new Location(0, 0);
        }

        #endregion

        #region Private Methods

        private void repositionCamera()
        {
            James.Location = new Vector2(((float)James.WorldLocation.X-(float)viewPort.Location.X), ((float)James.WorldLocation.Y-(float)viewPort.Location.Y));
            viewPort.Location.X = (int)MathHelper.Clamp(
                viewPort.Location.X,
                ((James.WorldLocation.X + (James.FrameWidth / 2)) + (viewPort.Location.X)) - (viewPort.Location.X + (viewPort.Width / 4)),
                ((James.WorldLocation.X + (James.FrameWidth / 2)) + (viewPort.Location.X)) + (viewPort.Location.X + (viewPort.Width / 4)));
            viewPort.Location.Y = (int)MathHelper.Clamp(
                viewPort.Location.Y,
                ((James.WorldLocation.Y + (James.FrameHeight / 2)) + (viewPort.Location.Y)) - (viewPort.Location.Y+(viewPort.Height / 4)),
                ((James.WorldLocation.Y + (James.FrameHeight / 2)) + (viewPort.Location.Y)) + (viewPort.Location.Y+(viewPort.Height / 4)));
        }

        private void keepCameraInBounds()
        {
            viewPort.Location.X = Math.Max(0, viewPort.X);
            viewPort.Location.Y = Math.Max(0, viewPort.Y);
            viewPort.Location.X = Math.Min(
                map.DisplayWidth - viewPort.Width, viewPort.X);
            viewPort.Location.Y = Math.Min(
                map.DisplayHeight - viewPort.Height, viewPort.Y);
        }

        private void OnAfterLayerDraw(object sender, LayerEventArgs layerEventArgs)
        {
            SpriteBatch spriteBatch = ScreenManager.SpriteBatch;

            if (visible)
            {
                spriteBatch.Begin();
                James.Draw(spriteBatch);
                spriteBatch.End();
            }
        }

        public Microsoft.Xna.Framework.Rectangle WorldToScreen(Microsoft.Xna.Framework.Rectangle worldRectangle, xTile.Dimensions.Rectangle ViewPort)
        {
            return new Microsoft.Xna.Framework.Rectangle(
                worldRectangle.Left - (int)ViewPort.X,
                worldRectangle.Top - (int)ViewPort.Y,
                worldRectangle.Width,
                worldRectangle.Height);
        }

        #endregion

        #region Public Methods

        public override void Activate()
        {
            base.Activate();
        }

        #endregion
         
        #region Update and Draw

        public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)
        {
            if (active)
            {
                James.Update(gameTime);
                map.Update(gameTime.ElapsedGameTime.Milliseconds);
                repositionCamera();
                keepCameraInBounds();
            }
        }

        public override void Draw(GameTime gameTime)
        {
            SpriteBatch spriteBatch = ScreenManager.SpriteBatch;
            SpriteFont font = ScreenManager.Font;

            if (visible)
            {
                map.Draw(ScreenManager.mainGame.mapDisplayDevice, viewPort, new Location(0,0),false);

                spriteBatch.Begin();
                //James.Draw(spriteBatch);
                spriteBatch.DrawString(
                    font,
                    James.Location.X.ToString(),
                    new Vector2(20, 20),
                    Color.White);
                spriteBatch.DrawString(
                    font,
                    James.Location.Y.ToString(),
                    new Vector2(20, 40),
                    Color.White);
                spriteBatch.DrawString(
                    font,
                    viewPort.Location.X.ToString(),
                    new Vector2(20, 60),
                    Color.White);
                spriteBatch.DrawString(
                    font,
                    viewPort.Location.Y.ToString(),
                    new Vector2(20, 80),
                    Color.White);
                spriteBatch.DrawString(
                    font,
                    James.WorldLocation.X.ToString(),
                    new Vector2(20, 100),
                    Color.White);
                spriteBatch.DrawString(
                    font,
                    James.WorldLocation.Y.ToString(),
                    new Vector2(20, 120),
                    Color.White);
                spriteBatch.End();
            }
        }

        #endregion
    }
}

 

 

Jan 6, 2012 at 8:57 AM

After plugging in the DisplayDevice's SpriteBatch instead of my own, my character is drawing correctly, so that was the part I was missing. I still cannot figure out what values I need to be able to form a camera clamp that will allow my character some flex space in between the 1/4th-3/4th X and Y zones of my viewport. It seems like it should be 5th grade math and I'm still unable to figure it out, lol. Regardless, thank you for your time taken to help me with that much!

Coordinator
Jan 6, 2012 at 9:02 AM
As I thought, you are using a different SpriteBatch instance than the one used by XnaDisplayDevice. You should keep a global reference to this instance, maybe keep a reference to it in your ScreenManager class for convenience.

As for your math code, I think you first need to separate your world coordinate calculations from your viewport (basically camera) calculations. For instance, you should keep your player's location in absolute map coordinates as if the whole thing is one big screen, then take care of collisions, either with tiles or with the map boundaries. You can then compute the viewport coordinates separately, basically by computing a desired target location for the camera and the having the current viewport location try to follow this target. This is achieved by making the current viewport location move towards the target. This can be done by computing the offset vector from the current viewport location to the target, and computing a "velocity" for the viewport location to have it move towards the target. You can have this either as a constant velocity, say, by normalizing the offset and multiplying with some value, like 100 to have it follow the player at 100 pixels per second, or else you make the velocity proportional to the offset distance so that the viewport moves faster when it is further away from the target, resulting in the "camera" rushing and then slowing down when the player is almost in the centre of view. You can also clamp the viewport location separately so it doesn't exceed map boundaries. This way, the camera can stop at a map edge whilst the player can also get close to the edge but not exceed it (because the player's position is clamped to world coords by the previous computaitons). All this is explaned in the link I posted earlier.

On 6 January 2012 10:44, TheBroodian <notifications@codeplex.com> wrote:

From: TheBroodian

That would probably be the part I'm missing. While you're having a look, maybe you wouldn't mind helping me with my clamp method to keep my sprite on screen? My math is just awful. It makes me want to cry. XD

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using xTile;
using xTile.Dimensions;
using xTile.Display;
using xTile.Layers;
using Heart_of_Fire.James;

namespace Heart_of_Fire.Screens
{
    class GameplayScreen:GameScreen
    {
        #region Fields

        James.James James;
        Map map;
        xTile.Dimensions.Rectangle viewPort;
        #endregion

        #region Constructor

        public GameplayScreen(James.James james, Map aMap, xTile.Dimensions.Rectangle ViewPort)
        {
            map = aMap;
            James = james;
            viewPort = ViewPort;
            map.Layers[4].AfterDraw += OnAfterLayerDraw;
            James.WorldLocation = map.GetLayer("Collision").ConvertLayerToMapLocation(new Location(12, 17), viewPort.Size);
            viewPort.Location = new Location(0, 0);
        }

        #endregion

        #region Private Methods

        private void repositionCamera()
        {
            James.Location = new Vector2(((float)James.WorldLocation.X-(float)viewPort.Location.X), ((float)James.WorldLocation.Y-(float)viewPort.Location.Y));
            viewPort.Location.X = (int)MathHelper.Clamp(
                viewPort.Location.X,
                ((James.WorldLocation.X + (James.FrameWidth / 2)) + (viewPort.Location.X)) - (viewPort.Location.X + (viewPort.Width / 4)),
                ((James.WorldLocation.X + (James.FrameWidth / 2)) + (viewPort.Location.X)) + (viewPort.Location.X + (viewPort.Width / 4)));
            viewPort.Location.Y = (int)MathHelper.Clamp(
                viewPort.Location.Y,
                ((James.WorldLocation.Y + (James.FrameHeight / 2)) + (viewPort.Location.Y)) - (viewPort.Location.Y+(viewPort.Height / 4)),
                ((James.WorldLocation.Y + (James.FrameHeight / 2)) + (viewPort.Location.Y)) + (viewPort.Location.Y+(viewPort.Height / 4)));
        }

        private void keepCameraInBounds()
        {
            viewPort.Location.X = Math.Max(0, viewPort.X);
            viewPort.Location.Y = Math.Max(0, viewPort.Y);
            viewPort.Location.X = Math.Min(
                map.DisplayWidth - viewPort.Width, viewPort.X);
            viewPort.Location.Y = Math.Min(
                map.DisplayHeight - viewPort.Height, viewPort.Y);
        }

        private void OnAfterLayerDraw(object sender, LayerEventArgs layerEventArgs)
        {
            SpriteBatch spriteBatch = ScreenManager.SpriteBatch;

            if (visible)
            {
                spriteBatch.Begin();
                James.Draw(spriteBatch);
                spriteBatch.End();
            }
        }

        public Microsoft.Xna.Framework.Rectangle WorldToScreen(Microsoft.Xna.Framework.Rectangle worldRectangle, xTile.Dimensions.Rectangle ViewPort)
        {
            return new Microsoft.Xna.Framework.Rectangle(
                worldRectangle.Left - (int)ViewPort.X,
                worldRectangle.Top - (int)ViewPort.Y,
                worldRectangle.Width,
                worldRectangle.Height);
        }

        #endregion

        #region Public Methods

        public override void Activate()
        {
            base.Activate();
        }

        #endregion
         
        #region Update and Draw

        public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)
        {
            if (active)
            {
                James.Update(gameTime);
                map.Update(gameTime.ElapsedGameTime.Milliseconds);
                repositionCamera();
                keepCameraInBounds();
            }
        }

        public override void Draw(GameTime gameTime)
        {
            SpriteBatch spriteBatch = ScreenManager.SpriteBatch;
            SpriteFont font = ScreenManager.Font;

            if (visible)
            {
                map.Draw(ScreenManager.mainGame.mapDisplayDevice, viewPort, new Location(0,0),false);

                spriteBatch.Begin();
                //James.Draw(spriteBatch);
                spriteBatch.DrawString(
                    font,
                    James.Location.X.ToString(),
                    new Vector2(20, 20),
                    Color.White);
                spriteBatch.DrawString(
                    font,
                    James.Location.Y.ToString(),
                    new Vector2(20, 40),
                    Color.White);
                spriteBatch.DrawString(
                    font,
                    viewPort.Location.X.ToString(),
                    new Vector2(20, 60),
                    Color.White);
                spriteBatch.DrawString(
                    font,
                    viewPort.Location.Y.ToString(),
                    new Vector2(20, 80),
                    Color.White);
                spriteBatch.DrawString(
                    font,
                    James.WorldLocation.X.ToString(),
                    new Vector2(20, 100),
                    Color.White);
                spriteBatch.DrawString(
                    font,
                    James.WorldLocation.Y.ToString(),
                    new Vector2(20, 120),
                    Color.White);
                spriteBatch.End();
            }
        }

        #endregion
    }
}

Read the full discussion online.

To add a post to this discussion, reply to this email (tIDE@discussions.codeplex.com)

To start a new discussion for this project, email tIDE@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com


Jan 6, 2012 at 9:12 AM

My world coordinate calculations are stored separately in my character class, as are the methods for collision detection, I'm essentially just trying to create a simple camera clamp method to keep the character on screen within a certain area. I will probably figure that out at this point now that I can see my character on screen. While I'm at it, I have one last question for you, I'm using a separate layer for collision, if I make it transparent, does the game still recognize its existence and the tile values within that transparent layer?

Coordinator
Jan 6, 2012 at 9:39 AM
Yes, an invisible layer will still be there, as long as you do not exclude it via the content pipeline properties for the map. But don't worry about this, by default it is included.

On 6 January 2012 11:12, TheBroodian <notifications@codeplex.com> wrote:

From: TheBroodian

My world coordinate calculations are stored separately in my character class, as are the methods for collision detection, I'm essentially just trying to create a simple camera clamp method to keep the character on screen within a certain area. I will probably figure that out at this point now that I can see my character on screen. While I'm at it, I have one last question for you, I'm using a separate layer for collision, if I make it transparent, does the game still recognize its existence and the tile values within that transparent layer?

Read the full discussion online.

To add a post to this discussion, reply to this email (tIDE@discussions.codeplex.com)

To start a new discussion for this project, email tIDE@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com


Jan 6, 2012 at 9:40 AM

Thank you again for taking some of your own time to answer my questions, you've made something incredible, it's good to know that you support it so diligently!

Jan 6, 2012 at 10:48 AM

Sorry to trouble again, but I'm missing a tiny piece of the puzzle that I can't find myself. I reworked my camera positioning to work more fluidly (as suggested), and it works a little better, but there's something wrong with my calculation to find the actual screen position for my character.

Right now it is "character screen location = world location of my character - viewport world location", which is close, but I notice as my viewport relocates to place my character closer to its center, my character is rising off of the surface he had collided with. My collision code works with my character's world location, not screen location, so this indicates to me that my math to find my character's actual screen location is wrong, but I don't know what factor I'm missing. It seems like either some sort of offset or scaling issue that I'm not aware of. Anybody have any ideas?

Coordinator
Jan 6, 2012 at 11:39 AM
You mentioned that you're rendering the character between layers so if you're using layers of different sizes to achieve a parallax effect, this may be the issue.

Another thing, make sure that you compute the character's screen coordinates *after* any clamping of the viewport as otherwise you will get incorrect positioning at the map edges.

Regarding this project, there are a number of fixes and features planned but sadly time is always in short supply! But I try to keep up with help requests as best as I can.

On 6 January 2012 12:48, TheBroodian <notifications@codeplex.com> wrote:

From: TheBroodian

Sorry to trouble again, but I'm missing a tiny piece of the puzzle that I can't find myself. I reworked my camera positioning to work more fluidly (as suggested), and it works a little better, but there's something wrong with my calculation to find the actual screen position for my character.

Right now it is "character screen location = world location of my character - viewport world location", which is close, but I notice as my viewport relocates to place my character closer to its center, my character is rising off of the surface he had collided with. My collision code works with my character's world location, not screen location, so this indicates to me that my math to find my character's actual screen location is wrong, but I don't know what factor I'm missing. It seems like either some sort of offset or scaling issue that I'm not aware of. Anybody have any ideas?

Read the full discussion online.

To add a post to this discussion, reply to this email (tIDE@discussions.codeplex.com)

To start a new discussion for this project, email tIDE@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com


Jan 6, 2012 at 7:00 PM
Edited Jan 6, 2012 at 7:22 PM

How do I find the amount of offset the paralax is causing?

Edit: Figure I'll show my work for my camera calculations:

Edit2: I guess a better question to ask is, if I place him ontop of a layer that I don't intend to be affected by paralax scrolling, do I even have to take paralax into consideration?

this has my camera follow my character if he steps outside of certain bounds based on world location:

 

private void repositionCamera()
        {
            Location offSet = new Location(0, 0);

            if (James.WorldLocation.X + (James.FrameWidth / 2) > (viewPort.Location.X + ((viewPort.Width / 2) + (viewPort.Width / 4))))
            {
                offSet = new Location(1, offSet.Y);
            }
            if (James.WorldLocation.X + (James.FrameWidth / 2) < (viewPort.Location.X + (viewPort.Width / 4)))
            {
                offSet = new Location(-1, offSet.Y);
            }
            if (James.WorldLocation.Y + (James.FrameHeight / 2) > (viewPort.Location.Y + ((viewPort.Height / 2) + (viewPort.Height / 4))))
            {
                offSet = new Location(offSet.X, 1);
            }
            if (James.WorldLocation.Y + (James.FrameHeight / 2) < (viewPort.Location.Y + (viewPort.Height / 4)))
            {
                offSet = new Location(offSet.X, -1);
            }

            viewPort.Location += offSet;
        }

This clamps my camera to the bounds of the map draw area:

private void keepCameraInBounds()
        {
            viewPort.Location.X = Math.Max(0, viewPort.X);
            viewPort.Location.Y = Math.Max(0, viewPort.Y);
            viewPort.Location.X = Math.Min(
                map.DisplayWidth - viewPort.Width, viewPort.X);
            viewPort.Location.Y = Math.Min(
                map.DisplayHeight - viewPort.Height, viewPort.Y);
        }

This is supposed to calculate my character's actual screen location:

private void calculateJamesLocation()
        {
            James.Location = new Vector2(
                ((float)James.WorldLocation.X - (float)viewPort.Location.X), 
                ((float)James.WorldLocation.Y - (float)viewPort.Location.Y));
        }

And then this is my Update code to make it all do stuff:

public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)
        {
            if (active)
            {
                James.Update(gameTime);
                map.Update(gameTime.ElapsedGameTime.Milliseconds);
                repositionCamera();
                keepCameraInBounds();
                calculateJamesLocation();
            }
        }