2D map, drawing objects C# / XNA

Started by
11 comments, last by MadHaTr 16 years, 3 months ago
Hello, I'm fairly new to all this game programming. I am working with XNA. What I need/want is objects on my map. Examples bases/ships Now my problem. I have a world which is larger than my viewing window(which is what I want) so I had to implement up,down,left,right scrolling. This works great. The problem I have is I have no idea how to add objects to the map and draw them only when they enter the view window. Also have them stay on the right spot on the map. here is the code.

#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
#endregion

namespace WindowsGame3
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        ContentManager content;
        SpriteBatch spriteBatch;
        SpriteFont TypoDermic;
        Texture2D worldMap;
        KeyboardState ksKeyboardState;
        Rectangle CamLoc = new Rectangle();
        
        int iCamLocX = 0, iCamLocY = 0, speed =10;
        string CameraLocationX, CameraLocationY;
        


        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            content = new ContentManager(Services);
        }


        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            ksKeyboardState = new KeyboardState();
            CamLoc.Width = 1024;
            CamLoc.Height = 768;
            CameraLocationX = "";
            CameraLocationY = "";
            base.Initialize();
        }


        /// <summary>
        /// Load your graphics content.  If loadAllContent is true, you should
        /// load content from both ResourceManagementMode pools.  Otherwise, just
        /// load ResourceManagementMode.Manual content.
        /// </summary>
        /// <param name="loadAllContent">Which type of content to load.</param>
        protected override void LoadGraphicsContent(bool loadAllContent)
        {
            if (loadAllContent)
            {
                // TODO: Load any ResourceManagementMode.Automatic content
                // Before TypoDermic works the properties of the image must be adjusted to Sprite Font Texture
                
                spriteBatch = new SpriteBatch(this.graphics.GraphicsDevice);
                TypoDermic = content.Load<SpriteFont>(@"TypodermicLarge");
                worldMap = content.Load<Texture2D>(@"hdworld");
                
            }

            // TODO: Load any ResourceManagementMode.Manual content
        }


        /// <summary>
        /// Unload your graphics content.  If unloadAllContent is true, you should
        /// unload content from both ResourceManagementMode pools.  Otherwise, just
        /// unload ResourceManagementMode.Manual content.  Manual content will get
        /// Disposed by the GraphicsDevice during a Reset.
        /// </summary>
        /// <param name="unloadAllContent">Which type of content to unload.</param>
        protected override void UnloadGraphicsContent(bool unloadAllContent)
        {
            if (unloadAllContent)
            {
                // TODO: Unload any ResourceManagementMode.Automatic content
                content.Unload();
            }

            // TODO: Unload any ResourceManagementMode.Manual content
        }


        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // TODO: Add your update logic here
            ksKeyboardState = Keyboard.GetState();
            input();
            

            base.Update(gameTime);
        }


        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here
            spriteBatch.Begin();
            spriteBatch.Draw(worldMap, new Rectangle(0, 0, 800, 600), CamLoc , Color.White);
            spriteBatch.DrawString(TypoDermic, CameraLocationX, new Vector2(20, 0), Color.Red, 0, new Vector2(0, 0), .4f, SpriteEffects.None, 0);
            spriteBatch.DrawString(TypoDermic, CameraLocationY, new Vector2(401, 0), Color.Red, 0, new Vector2(0, 0), .4f, SpriteEffects.None, 0);
            spriteBatch.End();

            base.Draw(gameTime);
        }

        #region Custom Methods
        
        void input()
        {
            
            if (ksKeyboardState.IsKeyDown(Keys.Up))
            { CamLoc.Y = CamLoc.Y - (1 * speed); }

            if (ksKeyboardState.IsKeyDown(Keys.Down))
            { CamLoc.Y = CamLoc.Y + (1 * speed); }

            if (ksKeyboardState.IsKeyDown(Keys.Left))
            { CamLoc.X = CamLoc.X - (1 * speed); }

            if (ksKeyboardState.IsKeyDown(Keys.Right))
            { CamLoc.X = CamLoc.X + (1 * speed); }

            CameraLocationX = "Camera Location X: " + (CamLoc.X+(CamLoc.Width/2));
            CameraLocationY = "Camera Location Y: " + (CamLoc.Y+(CamLoc.Height/2));
        }
        
        #endregion
    }
}



As you see I've made a variable that tracks the centre of the view window, which I think is important to drawing where the bases are. I guess for finding relational coordinates here is the code which makes my window form which i look at the world map spriteBatch.Draw(worldMap, new Rectangle(0, 0, 800, 600), CamLoc , Color.White); (so you see the window is 800x600, my map(worldMap) is something like 6000x4000 so im only looking at a section(CamLoc) of the map at any given time. So when i scroll over an area that has a base in it i need it drawn and moved in the propers directions until it is no longer in the screen ) I've heard mention of makeing some kinda class and what not but it all goes over my head. So if anyone has Ideas on how to do this please give some example code, pseudo im sure would be fine, or maybe some pseudo+ :) edit: PS this is not tilebased [Edited by - MadHaTr on December 4, 2007 11:37:51 AM]
Advertisement
The method I use is as follows:

-Create a rectangle object with the position of 0.0f (the top left corner of the screen) and the width and height of your current resolution.

-Each frame, loop through all of your game objects (I have them in arrays, personally). Each loop, create a rectangle with the width and height of that game object's texture and the position of the object's top left corner. (By default, a sprite is drawn with its origin at this position. If you centered the origin, you'll have to calculate the top left corner.)

-Now test to see if this rectangle intersects with the screen rectangle. Here's a basic rectangle intersection testing function:

  protected bool RectIntersectsRect(Rect one, Rect two)        {            if (one.Bottom <= two.Top || one.Top >= two.Bottom || one.Right <= two.Left || one.Left >= two.Right)                return false;            return true;        }


-If it does, you have several options. Personally, I found it simplest to just place all intersecting objects in a smaller array, which I then loop through to draw each of their graphics in the Render function. This uses more system memory (an extra array) but less processor cycles (it has to loop less times each frame). You could also flag each object as "drawable" and loop through the main array, or sort the array so they're on top, or some other method.
Okay this kinda went over my head. I understand the intersecting rects to decide if its time to draw my object. What I don't understand or see here is the movement of the obect while I move my screen,

Example. I am moving my screen rightwards across my map, and now an object appears(say a base) on the right hand side.. As i keep moving rightwards the base needs to move left as I pass over it, yes?

Sorry if this does do that as well. like I said I am still learning.

Thanks
BUMP

Okay there must be other ways to go about this. Has anyone here made a program with a free moving camera over a 2d map. I need to be able to map objects and draw them to the screen, when the camera flys over there.

My only other option at this point is drawing on my actual 2d map texture, which makes it really hard to move the objects around.

The objects will be airports on my map if anyone is wondering.
I'm building a 2D RTS, and I use the same system:
1) Draw map
2) Draw buildings
3) Draw ground units
4) Draw bullets
5) Draw air units

When I render, I traverse over my list of units, I check if each of them is within the rectangle of the camera, and if so, I draw the object.

My objects are all stored in 1 ArrayList and are sorted by type(Building, groundunit, bullet, aircraft, etc). The sorting is done by a custom Comparator and makes adding new types pretty simple.

However, if you do not cut up your 'list' of gameobjects into partitions, there is no way around having to compare EACH and EVERY single object with the camera view.

This is my render code:
        public void Render()        {            foreach (GameObject obj in objects)            {                if (IsWithinViewPort(obj))                    obj.Render(engine);            }        }


And the IsWithinViewPort(GameObject obj) looks like this:
        private bool IsWithinViewPort(GameObject obj)        {            // Is this object within the X boundaries of the viewport?            if (obj.Position.X + (obj.Size.Width / 2) > map.ViewPort.X * Tile.Size && obj.Position.X - (obj.Size.Width / 2) < (map.ViewPort.X + map.ViewPortSize.Width) * Tile.Size)            {                // Is this object within the Y boundaries of the viewport?                if (obj.Position.Y + (obj.Size.Width / 2) > map.ViewPort.Y * Tile.Size && obj.Position.Y - (obj.Size.Width / 2) < (map.ViewPort.Y + map.ViewPortSize.Height) * Tile.Size)                {                    return true;                }            }            return false;        }


Toolmaker

Okay both what you Toolmaker and OremLK makes perfect sense. took a bit to sink in. If either of you could also possible show me how you are storing you're objects into the array, I assume there some kinda class with attributes.


Also for the rectangle detection, when you draw do you not need some kinda of translation, won't the object just show up in the center of the viewport?
I use a standard List<GameObject> to store all my objects. However, I make a difference between the current, new and deleted objects, because I use a foreach() to run over them. Adding something to the list inside a foreach() will invalidate the iterator.

private List<GameObject> objects = new List<GameObject>();private List<GameObject> deletedObjects = new List<GameObject>();private List<GameObject> newObjects = new List<GameObject>();


When I need to delete an object, I place it into the deletedObjects list, and at the end of my update loop(Same with new objects, but I place those in the newObjects list), I do:
objects.addRange(newObjects);
objects.remove(deletedObjects);

For rendering:
Each GameObject calls CalculateScreenPosition() before rendering itself. It takes it's own world coordinates and the viewport coordinates and then translates itself to the correct position:
        protected Vector3 CalculateScreenPosition(Vector3 position)        {            renderPosition.X = (float)Math.Round(position.X - map.ViewPort.X * Tile.Size);            renderPosition.Y = (float)Math.Round(position.Y - map.ViewPort.Y * Tile.Size);            renderPosition.Z = position.Z;            return renderPosition;        }


I then use that position to render the object to.

Toolmaker

Okay thanks a lot, this is great.


if you would please continue to bare with me. I made some basic games before using only a simple class to store info on the main player. Also the games never left the main area of the scree, and also some were text based.

When you say a simple list its not something I think I am familiar with. Is it something all ready defined for me like Draw? If you could give an example. I'll also probably go read up on it, I'd just search Simple list, right?


Sorry if I keep asking and asking, I usually do this stuff in the morning before I have to go to work so I ask as much as possible for when I come back tomorrow morning.

Thank you again.
MadHaTr
Quote:Original post by MadHaTr
When you say a simple list its not something I think I am familiar with. Is it something all ready defined for me like Draw? If you could give an example. I'll also probably go read up on it, I'd just search Simple list, right?


He said 'standard List', not 'simple list'. :-) A List is a container provided as part of the .NET Framework Class Library which is essentially the standard library (ie included as part of the language specification) for any .NET language (of which C# is one).

It functions as essentially the 'default' container that many people will reach for (as a sort of resizeable array). There are, however, many other containers which are more, or less, appropriate for use in a variety of situations (depending on efficiency of searching, insertion and removal and so on) primarily located in the System.Collections namespace.

It's worth noting that the .NET List is not a linked list—this functionality is provided by the LinkedList generic class.

The 'generic' just means that it can contain a variety of types: List<int> is a List of ints but List<string> is a List of strings.
[TheUnbeliever]
Main Source

#region Using Statementsusing System;using System.Collections.Generic;using Microsoft.Xna.Framework;using Microsoft.Xna.Framework.Audio;using Microsoft.Xna.Framework.Content;using Microsoft.Xna.Framework.Graphics;using Microsoft.Xna.Framework.Input;using Microsoft.Xna.Framework.Storage;#endregionnamespace WindowsGame3{    /// <summary>    /// This is the main type for your game    /// </summary>    public class Game1 : Microsoft.Xna.Framework.Game    {        GraphicsDeviceManager graphics;        ContentManager content;        SpriteBatch spriteBatch;        SpriteFont TypoDermic;        Texture2D worldMap,airport;        KeyboardState ksKeyboardState;        Rectangle CamLoc = new Rectangle();        Rectangle viewSize = new Rectangle();        Rectangle mapSize = new Rectangle();        gameObject.Airport toronto = new gameObject.Airport();                        int speed = 8, x=0, y=0;        string CameraLocationX, CameraLocationY;        bool draw = true;        //List<gameObject> gameObject = new List<gameObject>();                public Game1()        {            graphics = new GraphicsDeviceManager(this);            content = new ContentManager(Services);        }        /// <summary>        /// Allows the game to perform any initialization it needs to before starting to run.        /// This is where it can query for any required services and load any non-graphic        /// related content.  Calling base.Initialize will enumerate through any components        /// and initialize them as well.        /// </summary>        protected override void Initialize()        {            // TODO: Add your initialization logic here            ksKeyboardState = new KeyboardState();            CamLoc.Width = 800; CamLoc.X = 0;            CamLoc.Height = 600; CamLoc.Y = 0;            viewSize.X = 0; viewSize.Y = 0; viewSize.Width = 800; viewSize.Height = 600;            mapSize.X = 0; mapSize.Y = 0; mapSize.Width = 1600; mapSize.Height = 1200;                        toronto.gates = 1;            toronto.economy = 500;            toronto.xy.x = 1000;            toronto.xy.y = 100;                                base.Initialize();        }        /// <summary>        /// Load your graphics content.  If loadAllContent is true, you should        /// load content from both ResourceManagementMode pools.  Otherwise, just        /// load ResourceManagementMode.Manual content.        /// </summary>        /// <param name="loadAllContent">Which type of content to load.</param>        protected override void LoadGraphicsContent(bool loadAllContent)        {            if (loadAllContent)            {                // TODO: Load any ResourceManagementMode.Automatic content                // Before TypoDermic works the properties of the image must be adjusted to Sprite Font Texture                                spriteBatch = new SpriteBatch(this.graphics.GraphicsDevice);                TypoDermic = content.Load<SpriteFont>(@"TypodermicLarge");                worldMap = content.Load<Texture2D>(@"1600x1200");                airport = content.Load<Texture2D>(@"tower");                            }            // TODO: Load any ResourceManagementMode.Manual content        }        /// <summary>        /// Unload your graphics content.  If unloadAllContent is true, you should        /// unload content from both ResourceManagementMode pools.  Otherwise, just        /// unload ResourceManagementMode.Manual content.  Manual content will get        /// Disposed by the GraphicsDevice during a Reset.        /// </summary>        /// <param name="unloadAllContent">Which type of content to unload.</param>        protected override void UnloadGraphicsContent(bool unloadAllContent)        {            if (unloadAllContent)            {                // TODO: Unload any ResourceManagementMode.Automatic content                content.Unload();            }            // TODO: Unload any ResourceManagementMode.Manual content        }        /// <summary>        /// Allows the game to run logic such as updating the world,        /// checking for collisions, gathering input and playing audio.        /// </summary>        /// <param name="gameTime">Provides a snapshot of timing values.</param>        protected override void Update(GameTime gameTime)        {            // Allows the game to exit            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)                this.Exit();            // TODO: Add your update logic here            ksKeyboardState = Keyboard.GetState();            if (ksKeyboardState.IsKeyDown(Keys.Escape))            {                this.Exit();            }                        input();                        base.Update(gameTime);        }        /// <summary>        /// This is called when the game should draw itself.        /// </summary>        /// <param name="gameTime">Provides a snapshot of timing values.</param>        protected override void Draw(GameTime gameTime)        {            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);            // TODO: Add your drawing code here            spriteBatch.Begin();            spriteBatch.Draw(worldMap, viewSize , CamLoc , Color.White);                        drawAirports();                       spriteBatch.DrawString(TypoDermic, CameraLocationX, new Vector2(20, 0), Color.Red, 0, new Vector2(0, 0), .4f, SpriteEffects.None, 0);            spriteBatch.DrawString(TypoDermic, CameraLocationY, new Vector2(401, 0), Color.Red, 0, new Vector2(0, 0), .4f, SpriteEffects.None, 0);                        spriteBatch.End();            base.Draw(gameTime);        }        #region Custom Methods                void input()        {                        if (ksKeyboardState.IsKeyDown(Keys.Up))            { if (CamLoc.Y <= viewSize.Y) CamLoc.Y = 0;                else CamLoc.Y = CamLoc.Y - (1 * speed); }            if (ksKeyboardState.IsKeyDown(Keys.Down))            { if (CamLoc.Y >= (mapSize.Height - CamLoc.Height)) CamLoc.Y = (mapSize.Height - CamLoc.Height);                else CamLoc.Y = CamLoc.Y + (1 * speed); }            if (ksKeyboardState.IsKeyDown(Keys.Left))            { if (CamLoc.X <= viewSize.X) CamLoc.X = 0;                 else CamLoc.X = CamLoc.X - (1 * speed); }            if (ksKeyboardState.IsKeyDown(Keys.Right))            {                if (CamLoc.X >= (mapSize.Width - CamLoc.Width)) CamLoc.X = (mapSize.Width - CamLoc.Width);                 else CamLoc.X = CamLoc.X + (1 * speed); }            //CameraLocationX = "Camera Location X: " + (CamLoc.X+(CamLoc.Width/2));            //CameraLocationY = "Camera Location Y: " + (CamLoc.Y+(CamLoc.Height/2));            CameraLocationX = "Camera Location X: " + (CamLoc.X);            CameraLocationY = "Camera Location Y: " + (CamLoc.Y);        }        void drawAirports()        {            if (toronto.xy.x > CamLoc.X && toronto.xy.x < (CamLoc.Width+CamLoc.X) &&                 toronto.xy.y > CamLoc.Y && toronto.xy.y < (CamLoc.Height+CamLoc.Y))            { x = toronto.xy.x - CamLoc.X; y = toronto.xy.y - CamLoc.Y; draw = true; }            else draw = false;                if (draw)                {                    spriteBatch.Draw(airport, new Vector2(x, y), Color.White);                }                    }                #endregion    }}


gameObject CLASS
using System;using System.Collections.Generic;using System.Text;namespace WindowsGame3{    public class gameObject    {        public class Airport        {            public int gates;            public int economy;            public coord xy = new coord();            public string name;            public int population;            public float growth;            public int AirportNumber;        }        public class Attractions        {            public string name;            public float mutator;                    }        public class City        {        }        public class Airplane        {        }    }    public class coord    {        public int x;        public int y;    }}


Okay so I've made some changeds. I now have code which can draw my bases(airports) to the map and move the acordingly when I move the screen around.

I also have an object class which holds info on the airports(population, economy ect.) Keep in mind I'm new to this and even newer to classes. Suggestions move than welcome on my class. Also keep in mind its a very basic class to test and get me used to these things.

Here is my code for finding the base and determining if it needs to be draw. I need some help on making it more flexible. Right now i'd have to write this code out for every airport name. How do I fix this? In the class?


void drawAirports()        {            if (toronto.xy.x > CamLoc.X && toronto.xy.x < (CamLoc.Width+CamLoc.X) &&                 toronto.xy.y > CamLoc.Y && toronto.xy.y < (CamLoc.Height+CamLoc.Y))            { x = toronto.xy.x - CamLoc.X; y = toronto.xy.y - CamLoc.Y; draw = true; }            else draw = false;                if (draw)                {                    spriteBatch.Draw(airport, new Vector2(x, y), Color.White);                }                    }


Thanks

[Edited by - MadHaTr on January 6, 2008 12:01:04 AM]

This topic is closed to new replies.

Advertisement