Running on Empty

The few things I know, I like to share.

XNA Framework GameEngine Development. (Part 11, Static Meshes)

Introduction

Welcome to Part 11 of the XNA Framework GameEngine Development series.  This article will be a huge update to the engine and will introduce some of the first static meshes to the game engine as well as movement within the game.

Part 11

Release

I finally put together a release of the code up until now.  You will see the sample game, the engine, the shader generator and test cases.  Hope you enjoy.  Link to Part 11.

Add Content

For this article you will need to add content items from the XNA team blog.

  • Ship.fbx (from several team site examples)
  • ShipDiffuse.tga (from several team site examples)

Changes to SceneGraphManager GameComponent

I had some issues with the way the SceneGraphManager worked when it was a DrawableGameComponent.  I had very little control over when the SceneGraphManager actually began the draw cycle.

So as a minor improvement I made it a GameComponent instead.  This means, I will have to call Draw, LoadContent and UnloadContent on my own rather than allow the game loop to do so.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using RoeEngine2.SceneObject.SceneGraph;
using RoeEngine2.SceneObject;

namespace RoeEngine2.Managers
{
    public class SceneGraphManager : GameComponent
    {
        private static Node _root;
        /// <summary>
        /// The root of the scene graph
        /// </summary>
        public static Node Root
        {
            get { return _root; }
        }
    
        /// <summary>
        /// Create the scenegraph Managers.
        /// </summary>
        /// <param name="game"></param>
        public SceneGraphManager(Game game)
            : base(game)
        {
            _root = new Node();
        }

        /// <summary>
        /// Draw objects
        /// </summary>
        /// <param name="gameTime"></param>
        public static void Draw(GameTime gameTime)
        {
            _root.Draw(gameTime);
        }

        /// <summary>
        /// Load the content of all the objects in the scenegraph
        /// </summary>
        public static void LoadContent()
        {
            _root.LoadContent();
        }

        /// <summary>
        /// Unload the content of all the objects in the scenegraph
        /// </summary>
        public static void UnloadContent()
        {
            _root.UnloadContent();
        }

        /// <summary>
        /// Add an object to the scenegraph.
        /// </summary>
        /// <param name="newObject"></param>
        public static void AddObject(RoeSceneObject newObject)
        {
            SceneObjectNode node = new SceneObjectNode(newObject);
            _root.AddNode(node);
        }
    }
}

Material.cs

Material will be used to contain information about the shader used by an object and a list of all the textures used by that object.  I will use this later.

using System;
using System.Collections.Generic;
using System.Text;

namespace RoeEngine2.SceneObject
{
    public class Material
    {
        private string _shader;
        public string Shader
        {
            get { return _shader; }
            set { _shader = value; }
        }

        private List<string> _textureList;
        public List<string> TextureList
        {
            get { return _textureList; }
            set { _textureList = value; }
        }
    }
}

RoeSceneObject.cs

Some updates to this class from last time.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using RoeEngine2.Interfaces;
using RoeEngine2.Managers;
using Microsoft.Xna.Framework.Graphics;

namespace RoeEngine2.SceneObject
{
    public class RoeSceneObject : IRoeSceneObject
    {
        private bool _readyToRender = false;
        /// <summary>
        /// Is this object ready to render?
        /// </summary>
        public bool ReadyToRender
        {
            get { return _readyToRender; }
            set { _readyToRender = value; }
        }

        private BoundingBox _boundingBox;
        /// <summary>
        /// The bounding box of this object, used for culling.
        /// </summary>
        public BoundingBox BoundingBox
        {
            get { return _boundingBox; }
            set { _boundingBox = value; }
        }

        private Material _material;
        public Material Material
        {
            get { return _material; }
            set { _material = value; }
        }

        private string _modelName;
        public string ModelName
        {
            get { return _modelName; }
            set { _modelName = value; }
        }    

        private Vector3 _position = Vector3.Zero;
        /// <summary>
        /// The position of this object in 3d space.
        /// </summary>
        public Vector3 Position
        {
            get { return _position; }
            set { _position = value; }
        }

        private Vector3 _scale = Vector3.One;
        /// <summary>
        /// Scale of the object.
        /// </summary>
        public Vector3 Scale
        {
            get { return _scale; }
            set { _scale = value; }
        }

        private Quaternion _rotation = Quaternion.Identity;
        /// <summary>
        /// Yaw, pitch and roll of the object.
        /// </summary>
        public Quaternion Rotation
        {
            get { return _rotation; }
            set { _rotation = value; }
        }

        public virtual void Draw(GameTime gameTime)
        {
            IRoeModel model = ModelManager.GetModel(_modelName);
            if (model != null && model.ReadyToRender)
            {
                Matrix[] transforms = new Matrix[model.BaseModel.Bones.Count];
                model.BaseModel.CopyAbsoluteBoneTransformsTo(transforms);

                foreach (ModelMesh mesh in model.BaseModel.Meshes)
                {
                    foreach (BasicEffect effect in mesh.Effects)
                    {
                        effect.EnableDefaultLighting();
                        effect.PreferPerPixelLighting = true;
                        effect.World = transforms[mesh.ParentBone.Index] * Matrix.Identity;

                        effect.View = CameraManager.ActiveCamera.View;
                        effect.Projection = CameraManager.ActiveCamera.Projection;
                    }
                    mesh.Draw();
                }
            }           
        }
    }  
}

LoadingScreen.cs

using System;
using System.Collections.Generic;
using System.Text;
using RoeEngine2.GameComponents;
using RoeEngine2.Managers;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Kynskavion.GameScreens
{
    class LoadingScreen : GameScreen
    {
        bool _loadingIsSlow;
        bool _otherScreensAreGone;

        GameScreen[] _screensToLoad;

        private LoadingScreen(bool loadingIsSlow, GameScreen[] screensToLoad)
        {
            _loadingIsSlow = loadingIsSlow;
            _screensToLoad = screensToLoad;

            TransitionOnTime = TimeSpan.FromSeconds(0.5);
        }

        public static void Load(bool loadingIsSlow, params GameScreen[] screensToLoad)
        {
            foreach (GameScreen screen in ScreenManager.GetScreens())
            {
                screen.ExitScreen();
            }

            LoadingScreen loadingScreen = new LoadingScreen(loadingIsSlow, screensToLoad);

            ScreenManager.AddScreen(loadingScreen);
        }

        public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)
        {
            base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);

            if (_otherScreensAreGone)
            {
                ScreenManager.RemoveScreen(this);

                foreach (GameScreen screen in _screensToLoad)
                {
                    if (screen != null)
                    {
                        ScreenManager.AddScreen(screen);
                    }
                }

                EngineManager.Game.ResetElapsedTime();
            }
        }

        public override void Draw(GameTime gameTime)
        {
            if ((ScreenState == ScreenState.Active) &&
                (ScreenManager.GetScreens().Length == 1))
            {
                _otherScreensAreGone = true;
            }

            if (_loadingIsSlow)
            {
                const string message = "Loading...";

                // Center the text in the viewport.
                Viewport viewport = EngineManager.Device.Viewport;
                Vector2 viewportSize = new Vector2(viewport.Width, viewport.Height);
                Vector2 textSize = ScreenManager.Font.MeasureString(message);
                Vector2 textPosition = (viewportSize - textSize) / 2;

                Color color = new Color(255, 255, 255, TransitionAlpha);

                ScreenManager.SpriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Deferred,
                                                SaveStateMode.SaveState);
                ScreenManager.SpriteBatch.DrawString(ScreenManager.Font, message, textPosition, color);
                ScreenManager.SpriteBatch.End();
            }
        }
    }
}

PauseMenuScreen.cs

using System;
using System.Collections.Generic;
using System.Text;
using RoeEngine2.Managers;
using Microsoft.Xna.Framework;

namespace Kynskavion.GameScreens
{
    class PauseMenuScreen : MenuScreen
    {
        /// <summary>
        /// Constructor.
        /// </summary>
        public PauseMenuScreen()
            : base("Paused")
        {
            // Flag that there is no need for the game to transition
            // off when the pause menu is on top of it.
            IsPopup = true;

            // Create our menu entries.
            MenuEntry resumeGameMenuEntry = new MenuEntry("Resume Game");
            MenuEntry quitGameMenuEntry = new MenuEntry("Quit Game");

            // Hook up menu event handlers.
            resumeGameMenuEntry.Selected += OnCancel;
            quitGameMenuEntry.Selected += QuitGameMenuEntrySelected;

            // Add entries to the menu.
            MenuEntries.Add(resumeGameMenuEntry);
            MenuEntries.Add(quitGameMenuEntry);
        }

        /// <summary>
        /// Event handler for when the Quit Game menu entry is selected.
        /// </summary>
        void QuitGameMenuEntrySelected(object sender, EventArgs e)
        {
            const string message = "Are you sure you want to quit this game?";

            MessageBoxScreen confirmQuitMessageBox = new MessageBoxScreen(message);

            confirmQuitMessageBox.Accepted += ConfirmQuitMessageBoxAccepted;

            ScreenManager.AddScreen(confirmQuitMessageBox);
        }

        /// <summary>
        /// Event handler for when the user selects ok on the "are you sure
        /// you want to quit" message box. This uses the loading screen to
        /// transition from the game back to the main menu screen.
        /// </summary>
        void ConfirmQuitMessageBoxAccepted(object sender, EventArgs e)
        {
            LoadingScreen.Load(false, new BackgroundScreen(),
                                      new MainMenuScreen());
        }

        /// <summary>
        /// Draws the pause menu screen. This darkens down the gameplay screen
        /// that is underneath us, and then chains to the base MenuScreen.Draw.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            ScreenManager.FadeBackBufferToBlack(TransitionAlpha * 2 / 3);

            base.Draw(gameTime);
        }
    }
}

GameplayScreen.cs

This is where all the magic happens.  In this class we will load all of the game content, Update cameras, draw game content, and handle input from the user.

using System;
using System.Collections.Generic;
using System.Text;
using RoeEngine2.GameComponents;
using RoeEngine2.Managers;
using Microsoft.Xna.Framework;
using System.Threading;
using RoeEngine2.Models;
using RoeEngine2.SceneObject;
using Microsoft.Xna.Framework.Input;

namespace Kynskavion.GameScreens
{
    class GameplayScreen : GameScreen
    {
        private static double delta = 0.0;

        public GameplayScreen()
        {
            TransitionOnTime = TimeSpan.FromSeconds(1.5);
            TransitionOffTime = TimeSpan.FromSeconds(0.5);

            CameraManager.ActiveCamera.CameraType = Camera.CameraEnumType.FirstPerson;
            CameraManager.ActiveCamera.NearPlane = 10.0f;
            CameraManager.ActiveCamera.FarPlane = 100000.0f;
            CameraManager.ActiveCamera.SetPosition(new Vector3(0, 0, -10000));
            CameraManager.SetAllCamerasProjectionMatrix((float)EngineManager.Device.Viewport.Width / EngineManager.Device.Viewport.Height); 
        }

        /// <summary>
        /// Load graphics content for the game.
        /// </summary>
        public override void LoadContent()
        {
            RoeModel model = new RoeModel("Content/Models/Ship");

            ModelManager.AddModel(model, "ship");
            RoeSceneObject obj = new RoeSceneObject();
            obj.ModelName = "ship";
            SceneGraphManager.AddObject(obj);

            SceneGraphManager.LoadContent();

            // once the load has finished, we use ResetElapsedTime to tell the game's
            // timing mechanism that we have just finished a very long frame, and that
            // it should not try to catch up.
            EngineManager.Game.ResetElapsedTime();
        }

        /// <summary>
        /// Unload graphics content used by the game.
        /// </summary>
        public override void UnloadContent()
        {
            SceneGraphManager.UnloadContent();
        }

        public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)
        {
            base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
            delta = gameTime.ElapsedGameTime.TotalSeconds;
        }

        public override void HandleInput(Input input)
        {           
            if (input.PauseGame)
            {
                ScreenManager.AddScreen(new PauseMenuScreen());
            }
            if (input.CurrentKeyboardState.IsKeyDown(Keys.Q))
            {
                CameraManager.ActiveCamera.Translate(new Vector3(0, 3000f * (float)delta, 0.0f));
            }
            if (input.CurrentKeyboardState.IsKeyDown(Keys.Z))
            {
                CameraManager.ActiveCamera.Translate(new Vector3(0, -3000f * (float)delta, 0.0f));
            }
            if (input.CurrentKeyboardState.IsKeyDown(Keys.W))
            {
                CameraManager.ActiveCamera.Translate(new Vector3(0, 0, 1000f * (float)delta));
            }
            if (input.CurrentKeyboardState.IsKeyDown(Keys.S))
            {
                CameraManager.ActiveCamera.Translate(new Vector3(0, 0, -1000f * (float)delta));
            }
            if (input.CurrentKeyboardState.IsKeyDown(Keys.D))
            {
                CameraManager.ActiveCamera.Translate(new Vector3(-1000f * (float)delta, 0, 0));
            }
            if (input.CurrentKeyboardState.IsKeyDown(Keys.A))
            {
                CameraManager.ActiveCamera.Translate(new Vector3(1000f * (float)delta, 0, 0));
            }
            if (input.CurrentMouseState.RightButton == ButtonState.Pressed)
            {
                CameraManager.ActiveCamera.RotateX(input.MouseMoved.Y);
                CameraManager.ActiveCamera.RotateY(input.MouseMoved.X);
            }
        }

        public override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);

            SceneGraphManager.Draw(gameTime);
        }

        public override void PostUIDraw(GameTime gameTime)
        {
            base.PostUIDraw(gameTime);
        }
    }
}

Conclusion

In this article, I finally demonstrated some objects in the engine using the basic effect and model loading.  I also included a link to the first release of the engine code.  I hope you enjoy.

I am very interested in your thoughts and suggestions, your feedback is very important.

Advertisements

February 12, 2008 - Posted by | C#, GameComponent, TDD, XBOX360, XNA

5 Comments »

  1. Finally got a chance to get caught up, everything is looking great, I’m going to start expanding on this with some of my own idea soon, and hopefully I’ll have something worth sharing. Keep up the great work.

    Comment by KaBaL | February 12, 2008 | Reply

  2. KaBaL,
    Thank you for your comments, I am very interested in your suggestions and improvements. Looking forward to your contribution.

    Comment by roecode | February 13, 2008 | Reply

  3. […] to GameComponentsPart IX: SceneGraphManager GameComponentPart X: CameraManager GameComponentPart XI: Static meshesPart XII: Culling and chase cameraPart XIII: Oclussion & Frustum […]

    Pingback by Getting up with XNA GS 2.0 [UPDATED] - Kartones Blog | February 19, 2008 | Reply

  4. I know there is the source code as reference, but i think you missed the model class and the modelManager in the tutorial…
    Anyway, goog work, keep on going!!!

    J.

    Comment by Gazzell | October 6, 2009 | Reply

  5. i do not understand the interface principle used here. Where does it determine, wich interface type it is? (IRoeDrawable, IRoeLoadable, IRoeUpdateable) – all I can see is that there are made some IRoeSceneObject nodes. Can you please explain that to me? Is the type defined by adding the proper functions from the interface type/s used? I really do not understand this…

    Comment by ben0bi | December 8, 2010 | Reply


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: