Legeria

- from legere "to gather, collect, pick out, choose"

XNA Run-Time Asset Loading

When working on a project you'll want to have as little downtime as possible. In XNA, whenever you edit a resource, you'll have to first shutdown your game, rebuild the resources and then run the game again. It's a lot of wasted time if you're working on textures and need to see how it looks in-game.

I found a way around this, by making a small custom content manager that allows you to load assets like you would normally, but also features reloading assets, with the optional feature of loading from non-compiled files as well.

The first thing I made was a Texture2DReference type, which is basically a Texture2D I can update remotely using the reference, which means I don't have to update all the use-sites as well.

    class Texture2DReference
    {
        public Texture2D Texture { get; set; }

        public String AssetName { get; set; }

        public Texture2DReference(String assetName)
        {
            this.AssetName = assetName;
        }

        // Implicit conversion from TextureReference to Texture2D 
        public static implicit operator Texture2D(Texture2DReference d)
        {
            return d.Texture;
        }
    }

 
 

It's pretty straightforward, and has the feature that it converts to Texture2D implicitly, which makes integration even easier. To make it fit the XNA service/component model, I made an interface for the service.

    interface IContentManagerService
    {
        T Get<T>(String name) where T : class;

        Boolean TryGet<T>(String name, out T asset) where T : class;

        void Reload();
    }

With the interface in place, I made the content manager itself.

    class ContentManagerComponent : GameComponent, IContentManagerService
    {
        public ContentManagerComponent(Game game)
            : base(game)
        {
            this.textures = new Dictionary<String, Texture2DReference>();
        }

        private Dictionary<String, Texture2DReference> textures { get; set; }

        public override void Initialize()
        {
            this.Game.Content.RootDirectory = "Content";

            base.Initialize();
        }

        public T Get<T>(String assetName) where T : class
        {
            if (typeof(T) == typeof(Texture2DReference))
            {
                Texture2DReference textureReference;
                if (!this.textures.TryGetValue(assetName, out textureReference))
                {
                    textureReference = new Texture2DReference(assetName);
                    textureReference.Texture = this.LoadTexture2D(assetName);
                    this.textures.Add(assetName, textureReference);
                }
                return (T)(Object)textureReference;
            }

            return (T)this.Game.Content.Load<T>(assetName);
        }

        public Boolean TryGet<T>(String assetName, out T asset) where T : class
        {
            try
            {
                asset = this.Get<T>(assetName);
                return true;
            }
            catch (Exception e)
            {
                Trace.WriteLine("ContentManagerComponent:TryGet:Exception: " + e.Message);
                asset = null;
                return false;
            }
        }

        private Texture2D LoadTexture2D(String assetName)
        {
            var filename = Path.Combine(Environment.CurrentDirectory, Path.Combine(this.Game.Content.RootDirectory, assetName + ".png"));
            if (File.Exists(filename))
            {
                using (var stream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    Trace.WriteLine("ContentManager: Loading (custom) " + assetName);
                    return Texture2D.FromStream(this.Game.GraphicsDevice, stream);
                }
            }
            else
            {
                Trace.WriteLine("ContentManager: Loading (normal) " + assetName);
                return this.Game.Content.Load<Texture2D>(assetName);
            }
        }

        public void Reload()
        {
            Trace.WriteLine("ContentManager: Reloading");
            foreach (var texture in this.textures.Values)
            {
                texture.Texture = this.LoadTexture2D(texture.AssetName);
            }
        }
    }

And that's all there is to it. So far it only supports Texture2DReference, but it can easily be extended to support more. It also supports all the regular types, but they're backed by the builtin XNA content manager.

Here's how you'd use it in an XNA game.

    class TheClient : Game
    {
        public TheClient(String[] args)
        {
            this.graphics = new GraphicsDeviceManager(this);

            var contentManagerComponent = new ContentManagerComponent(this);
            this.Services.AddService(typeof(IContentManagerService), contentManagerComponent);
            this.Components.Add(contentManagerComponent);
        }

        private GraphicsDeviceManager graphics { get; set; }

        private SpriteBatch spriteBatch { get; set; }

        private IContentManagerService contentManager { get; set; }

        private Texture2DReference someTexture { get; set; }

        protected override void Initialize()
        {
            this.spriteBatch = new SpriteBatch(this.GraphicsDevice);

            // Initialize components so they're ready to use.
            base.Initialize();

            this.contentManager = (IContentManagerService)this.Services.GetService(typeof(IContentManagerService));
        }

        protected override void LoadContent()
        {
            this.someTexture = this.contentManager.Get<Texture2DReference>("SomeTexture");
            
            base.LoadContent();
        }

        protected override void Update(GameTime gameTime)
        {
            if (this.IsActive)
            {
                if (Keyboard.GetState().IsKeyDown(Keys.F5))
                    this.contentManager.Reload();
            }

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            this.GraphicsDevice.SetRenderTarget(null);
            this.GraphicsDevice.Clear(Color.CornflowerBlue);

            this.spriteBatch.Begin();
            this.spriteBatch.Draw(this.someTexture, Vector2.Zero, Color.White);
            this.spriteBatch.End();
			
            base.Draw(gameTime);
        }
    }

It'll draw an asset named SomeTexture, and optionally, if the file SomeTexture.png exists in the content directory, it'll favor loading that instead, allowing you to modify it at runtime, and then reload it by hitting F5.

Project: Productivity

Procrastination

Unless you've stumbled upon this blog by accident, there's a good chance you've dabbled in the procrastinacious way of living. Whether it's putting off tasks with other tasks or finding excuses for not doing a particular task today, the results are the same. You want to get something done, but nothing happens, and in the end you have this sinking feeling that lets you know you've let yourself down.

The type of procrastinating I do is one where I always wait for the right moment to do something. I see other people make the same mistake. They put off things, like weight loss, making a game or eating healthier, because the time isn't right just yet. Perhaps they want to do it at the beginning of next month, but then the date passes, they realize and then put it off another month. Months pass and suddenly, it's been half a year, and the task hasn't even started yet. To put that in perspective, that's six months of their life, where they've not done something to make themselves healthier or happier. Just for the sake of finding the right moment. And personally, I think that's a waste of life.

Productivity

That's why I've decided to start a project. The project is a summer project that will span 8 weeks of my vacation, four hours per day, five days a week. The first thing I've decided to do, is write down the different pitfalls in my own productivity.

  • Distractions (online communities, games, Facebook, etc.)
  • Perfectionism (redoing the same piece of code over and over)
  • Overthinking (not doing anything but stare at a task)
  • Lack of motivation (tired, uninspired, bored)

These pitfalls have halted me over and over in previous projects. Distractions mean constant breaks in concentration, resetting your train of thought, and slowing down progress. To address this, I've decided to strictly manage my time. Each day I'll work for one hour, take a fifteen minute break, and repeat until I've worked at least four hours. In the hours I work, I'll close down online social interactions, and only use the internet to solve problems. Outside the four strict hours, I am still allowed to work on the project, but without the same rules. If I start to burn out due to this, I'll stop working, and stick to writing down the ideas I get instead.

Dealing with perfectionism can be tough. Obviously I want to create something awesome. Problem is, it leads to the next pitfall which is overthinking it. You end up in a closed loop of redoing code and staring at code, not progressing at all. To address this, I've decided to force myself to solve specific tasks. Using Trello, I've written up all my ideas, and once an idea is implemented, I'll move on, only touching it if there are important changes to be made.

Lack of motivation is a tough one as well. It's often caused by lack of energy, which means you'll have a hard time focusing on a task, which also kills creativity. The way I’m going to address this, is by getting at least half an hour of exercise every day. This should help increase my energy, and gives me time to think about ideas without distractions.

With the pitfalls and solutions in mind, I'll be able to take a look at this blog post whenever I start to slow down or begin falling into my old habits, and hopefully get back on track again. I'll try to write a few blogs along the way, to keep track of how this is going. This way, I should be able to look back on this next time I need to be productive, and see what was good and what was bad.

A blogging good start...

Here it is. My first attempt at writing a blog. I guess I should start out by introducing myself.

I'm a 26 year old student doing the equivalent to the 3rd year of high school. Lets just let that sink in for a bit... Now I know what you're thinking, but I'd like to explain.

After I finished the last year of middle school (equivalent to 1st year of high school, confusing eh?), I didn't really know what I wanted to do. I knew I liked creating/inventing things, but I wasn't really sure in what area that should happen. This first lead me to try taking a year of pre high school to figure it out, but with the lack of a goal I lacked focus, and after a few months I dropped out. That repeated for longer than I'd like to admit, but after a while I decided I'd rather wait, and take my time. I started driving a cab and found out that I loved it. Meeting new people, talking to inspirational people, like executive leads on various small and large companies and even a few CEO's as well, not to mention, the money. Some time before I started driving the cab I had begun programming. Turned out driving a cab was the perfect job to match that hobby. I had plenty of night hours with no customers where I could sit at home, programming and learning. Then, a year ago, I decided it was time to move on. I still loved my cab job, but I couldn't see myself doing it for the rest of my life. Then I went back to high school to give it another try. This time, with great success, I'm getting top grades, I'm focused and I'm loving it.

Now, there's no doubt that I've wasted a lot of time. But I've enjoyed the time I've wasted. And that's what matters to me. Feeling good about what you do, no matter if it's being the CEO of a large company, the lead developer at ArenaNET or driving a cab while programming on the side.