views:

402

answers:

3

I have a base class called Component. I also have 2 interfaces I2DComponent, and I3DComponent. I am currently working on the I2DComponent.

The implementation:

/// <summary>
/// Represtions a 2D object. These objects will be drawn after 3D objects,
/// and will have modifiers automatically provided in the editor.
/// </summary>
public interface I2DComponent
{
    /// <summary>
    /// Gets or sets the rectangle.
    /// </summary>
    /// <value>The rectangle.</value>
    Rectangle Rectangle { get; set; }

    /// <summary>
    /// Gets the local position on the game screen.
    /// </summary>
    /// <value>The local position on the game screen.</value>
    /// <remarks>The rectangle position minus the parent screen position.</remarks>
    Rectangle LocalPosition { get; }

    /// <summary>
    /// Gets or sets the scale.
    /// </summary>
    /// <value>The scale.</value>
    float Scale { get; set; }
}

So the problem is that I need access to the global position or the Rectangle for checking whether the mouse is over it.

if (component.Rectangle.Contains(mouse.Rectangle))
{
    do somthing.
}

But when I am just accessing to moving it around i want to just be accessing the local position so that if i have a game screen at position 50, 50 then i can set the local position to 0, 0 and it will be at the top left corner of the parent game screen. I guess the real problem is that I don't want to be able to accidentally set the Rectangle by saying

component.Rectangle = new Rectangle(component.Rectangle.X + 5, component.Rectangle.Y + 5, component.Rectangle.Width, component.Rectangle.Height);

This is more or less an accessor design issue than anything and I want to make it look right, but I am having trouble doing so.

Update:

What if i changed changed it so that the I2D component only had Bounds and scale and then had a function in GameScreen to get the current "Global" position of the component.

Update:

These 2D objects are all objects that are draw as a hud or a gui object not in 3D space. just a clareification.

Update:

I am thinking about doing it recursively now but im not sure how i would go about doing this.

+1  A: 

If you're concerned about how these Rectangles will be used, why expose them at all? Your component could just have its own GetGlobalX, GetGlobalY, GetLocalX, GetLocalY, plus maybe a IsInside(X, Y) for checking mouseover. Then it can use Rectangles internally, or not, as it likes.

Encapsulation, yo. It's not just for breakfast any more.

chaos
+3  A: 

I would only ever worry about modifying the local position of your game object, never it's screen position, because that is Dependant on the location and view port of your camera.

There are two ways you can tackle finding the position of something on screen. The first is with an accessor method that takes the Camera, and the other is by using the camera as a translation method in the first place. I prefer the later, and rewrite thusly:

public interface I2DComponent
{
    /// <summary>
    /// Gets or sets the local (world) bounds of the object.
    /// </summary>
    /// <value>The rectangle.</value>
    Rectangle Bounds{ get; }

    /// <summary>
    /// Gets or sets the scale.
    /// </summary>
    /// <value>The scale.</value>
    float Scale { get; set; }
}

Then in camera have two methods

public class Camera
{
    public Rectangle WorldToScreen(Rectangle rect);
    public Rectangle ScreenToWorld(Rectangle point);
}

This way you keep an encapsulated Object which doesn't care at all about the screen, and a Camera that handles all of the translation.

Jeff
This brings a good point. Although why would it be part of the Camera object for 2D objects?
Chris Watts
You can further abstract the screen from the camera itself too. Since a camera just represents a view transform ideally you'd have a WorldToCamera() and represent the screen as a class Screen. Then you could have a Screen.CameraToScreen to support a wider variety of transformations.
Ron Warholic
+1  A: 

Your original question is a bit vague as to what these 2D objects are, and the best answer depends on a more rigorous definition.

If these are objects in your game world, (eg. names above heads, billboards), then they need a world position, and you translate that to a screen position typically via a camera class, as described in another answer.

If however these are GUI objects, such as menus, forms, heads-up displays, etc, then that's quite different. You only need to store a position relative to a parent element in that case (ie. the local position), and drawing them can be done recursively. Each element's position is basically its parent's global position plus its own local position, and that goes up the tree to any top-level parents whose local positions are also global positions.

EDIT: example given to clarify. Python-style untested pseudocode (apologies, as I don't know C#)

def draw(element, parentScreenX, parentScreenY):
    # Draw this element relative to its parent
    screenX = parentScreenX + element.localX
    screenY = parentScreenY + element.localY    
    render(element, screenX, screenY)
    # Draw each child relative to this one
    for childElement in element.children:
        draw(childElement, screenX, screenY)

# Normal usage (root element should be at 0,0, 
# and typically just an invisible container.)
draw(rootElement, 0, 0)


# Slightly different mathematics for the mouseover test,
# but that's just personal preference
def elementAtPos(element, posX, posY):
    # Translate that space to element local space
    posX -= element.localX
    posY -= element.localY
    # Compare against this element's dimensions
    if (posX, posY) is within (0, 0, element.width, element.height):
        # it's within this element - now check children
        for childElement in element.children:
            # Do this same check on the child element (and its children, etc)
            targetedChildElement = elementAtPos(childElement, posX, posY) 
            if targetedChildElement is not None:
                return targetedChildElement
        # it wasn't within any of our descendants so return this one
        return element 
    # It wasn't within our bounds at all, so return None
    return None

#Normal usage
targetedElement = elementAtPos(rootElement, mouseScreenX, mouseScreenY)

You may want 2 separate functions: one that gets the exact targeted element (as above) and one that merely returns if the mouse is anywhere over an element, which may return true for several elements at the same time, eg. a button, the panel the button is on, the form the panel is on, etc. There are several ways you could implement that, but probably the easiest is to implement a recursive call to allow any element to get its screen position:

def screen_position(element):
    if element.parent is not None:
        (parentLocalX, parentLocalY) = screen_position(element.parent)
    else:
        (parentLocalX, parentLocalY) = (0,0)
    return (element.localX + parentLocalX, element.localY + parentLocalY)

Watch out for the circular reference now between parent and children now.

These all require a root element at 0,0 - this facilitates the transformation between screen space and local space for the root element, because they are effectively the same. Then the rest of the algorithm can work in local space.

Kylotan
The second part is correct, sorry I am talking about GUI 2D objects. Drawing them is one thing but i also need to be able to reference the global position when accessing it for when the mouse is over the object.
Chris Watts
Generally you don't need the global position for that: just check each element recursively, passing the mouse position in. Each call translates the mouse coordinates to local coordinates for that GUI element.If you really do want the global position of an element for some reason, that is easy enough to calculate recursively, but if you're checking for mouse hits it's easier and more efficient just to have a recursive 'IsMouseOver(localX, localY)' routine.
Kylotan
Alright, i think im following. Im not sure how i would implement the recursive call for the gui items though. could you show an example? As well as the IsMouseOver. Because right now the components are being updated and drawn by overriden moethods that get updated every frame. so are you saying when ever i draw i would call draw at local position or somthing and have that part be recursive all the way up to the most parent screen?
Chris Watts
Have edited the original post with some Python pseudocode to hopefully explain further. Apologies for it not being in C# but I am not too familiar with that language.
Kylotan