Roy Triesscheijn’s Weblog

My programming world

Archive for February, 2010

SpaceAce progress report

Posted by Roy Triesscheijn on 26th February 2010

It’s been a while since I’ve blogged about the space rts game (working title SpaceAce) I’m making for Windows/XBLIG. People who follow me on twitter might have seen that I worked on a model for a space frigate. Although it’s more of a placeholder than anything fancy I find it much easier to see what I’m doing when it’s a space frigate flying, and not a silly sphere or cube.

Here’s my (shortened) done/todolist.

Done
-Scene Manager
-Sophisticated input manager with keymapping functionality
-Planets moving realistically (even eccentricity and inclination)
-Demo’d a gui (I’ve tried to make an immediate gui in XNA, and I really liked it, to bad my art skills suck so hard, else I would show it off, might make it into a tutorial someday).
-Ship following mouse, evading obstacles. (Still needs more testing).
-Random galaxy generation.

Todo
-Selecting objects
-Waypoints for ships (you wouldn’t want them to just follow the mouse, would you ;-) ).
-Building structures inside the planet space
-Some research tree
-Some way to build objects
-Objects belonging to ai or other players

Those are the items with high priority, that need implementation first. Of course the actual todo list is much longer (and only inside my head). I hope I will have some time to implement all this stuff, but from what I’ve heard this semester is very demanding.

See you later alligator!

Tags: , , , ,
Posted in Blog, General Gamedesign, XNA | No Comments »

Code snippet: XNA Simple ArcBallCamera (Updated)

Posted by Roy Triesscheijn on 21st February 2010

Update 23-2-2010: Today I’ve updated the source code of the arcball camera, I made some small changes and added two helper methods for moving the camera right and forward as seen from the camera’s perspective. I’ve also changed the Position prop to be read only and show the real position of the camera instead of the relative position. I’ve also added a zoom property which can now be used instead of setting position, making this a truer ArcBallCamera and fixing a few small glitches when having a lookAt not at Vector3.Zero. (The LookAt is now truly the center of attention, changing the lookAt will also change the position of the camera.) Enjoy!

Today I was struggling with all different sorts of ArcBall/Rotating/Quaternion cameras to create a camera that could simple rotate around an object. I found a sample on xnawiki, but that didn’t seem to work (I still dont know why). In the end I ended up creating my own class.

Note that you never need an Update method for a camera, with one or two booleans you can track if something changed and only then recalculate your matrices the next time they are accessed. This saves you a lot of cpu cycles, because instead of every frame (60x per second) you only recalculate these matrices when you move the camera or change another camera property. In a lot of games this will be a lot less then 60x per second.

Special thanks to Gorion for the winning tip, when I got a bit stuck (my rotation order was flawed), sony` for spotting that the Quaternion was unneeded, and X-Tatic for helping me with the MoveCameraRight method.

Anyway the code:

public class ArcBallCamera
    {

        public ArcBallCamera(float aspectRation, Vector3 lookAt)
            : this(aspectRation, MathHelper.PiOver4, lookAt, Vector3.Up, 0.1f, float.MaxValue) { }

        public ArcBallCamera(float aspectRatio, float fieldOfView, Vector3 lookAt, Vector3 up, float nearPlane, float farPlane)
        {
            this.aspectRatio = aspectRatio;
            this.fieldOfView = fieldOfView;
            this.lookAt = lookAt;
            this.nearPlane = nearPlane;
            this.farPlane = farPlane;
        }

        /// <summary>
        /// Recreates our view matrix, then signals that the view matrix
        /// is clean.
        /// </summary>
        private void ReCreateViewMatrix()
        {
            //Calculate the relative position of the camera
            position = Vector3.Transform(Vector3.Backward, Matrix.CreateFromYawPitchRoll(yaw, pitch, 0));
            //Convert the relative position to the absolute position
            position *= zoom;
            position += lookAt;

            //Calculate a new viewmatrix
            viewMatrix = Matrix.CreateLookAt(position, lookAt, Vector3.Up);
            viewMatrixDirty = false;
        }

        /// <summary>
        /// Recreates our projection matrix, then signals that the projection
        /// matrix is clean.
        /// </summary>
        private void ReCreateProjectionMatrix()
        {
            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, AspectRatio, nearPlane , farPlane);
            projectionMatrixDirty = false;
        }

        #region HelperMethods

        /// <summary>
        /// Moves the camera and lookAt at to the right,
        /// as seen from the camera, while keeping the same height
        /// </summary>
        public void MoveCameraRight(float amount)
        {
            Vector3 right = Vector3.Normalize(LookAt - Position); //calculate forward
            right = Vector3.Cross(right, Vector3.Up); //calculate the real right
            right.Y = 0;
            right.Normalize();
            LookAt += right * amount;
        }

        /// <summary>
        /// Moves the camera and lookAt forward,
        /// as seen from the camera, while keeping the same height
        /// </summary>
        public void MoveCameraForward(float amount)
        {
            Vector3 forward = Vector3.Normalize(LookAt - Position);
            forward.Y = 0;
            forward.Normalize();
            LookAt += forward * amount;
        }

        #endregion

        #region FieldsAndProperties
        //We don't need an update method because the camera only needs updating
        //when we change one of it's parameters.
        //We keep track if one of our matrices is dirty
        //and reacalculate that matrix when it is accesed.
        private bool viewMatrixDirty = true;
        private bool projectionMatrixDirty = true;

        public float MinPitch = -MathHelper.PiOver2 + 0.3f;
        public float MaxPitch = MathHelper.PiOver2 - 0.3f;
        private float pitch;
        public float Pitch
        {
            get { return pitch; }
            set
            {
                viewMatrixDirty = true;
                pitch = MathHelper.Clamp(value, MinPitch, MaxPitch);
            }
        }

        private float yaw;
        public float Yaw
        {
            get { return yaw; }
            set
            {
                viewMatrixDirty = true;
                yaw = value;
            }
        }

        private float fieldOfView;
        public float FieldOfView
        {
            get { return fieldOfView; }
            set
            {
                projectionMatrixDirty = true;
                fieldOfView = value;
            }
        }

        private float aspectRatio;
        public float AspectRatio
        {
            get { return aspectRatio; }
            set
            {
                projectionMatrixDirty = true;
                aspectRatio = value;
            }
        }

        private float nearPlane;
        public float NearPlane
        {
            get { return nearPlane; }
            set
            {
                projectionMatrixDirty = true;
                nearPlane = value;
            }
        }

        private float farPlane;
        public float FarPlane
        {
            get { return farPlane; }
            set
            {
                projectionMatrixDirty = true;
                farPlane = value;
            }
        }

        public float MinZoom = 1;
        public float MaxZoom = float.MaxValue;
        private float zoom = 1;
        public float Zoom
        {
            get { return zoom; }
            set
            {
                viewMatrixDirty = true;
                zoom = MathHelper.Clamp(value, MinZoom, MaxZoom);
            }
        }

        private Vector3 position;
        public Vector3 Position
        {
            get
            {
                if (viewMatrixDirty)
                {
                    ReCreateViewMatrix();
                }
                return position;
            }
        }

        private Vector3 lookAt;
        public Vector3 LookAt
        {
            get { return lookAt; }
            set
            {
                viewMatrixDirty = true;
                lookAt = value;
            }
        }
        #endregion

        #region ICamera Members
        public Matrix ViewProjectionMatrix
        {
            get {return ViewMatrix * ProjectionMatrix; }
        }

        private Matrix viewMatrix;
        public Matrix ViewMatrix
        {
            get
            {
                if (viewMatrixDirty)
                {
                    ReCreateViewMatrix();
                }
                return viewMatrix;
            }
        }

        private Matrix projectionMatrix;
        public Matrix ProjectionMatrix
        {
            get
            {
                if (projectionMatrixDirty)
                {
                    ReCreateProjectionMatrix();
                }
                return projectionMatrix;
            }
        }
        #endregion
    }

Tags: , , , , , , , ,
Posted in Blog, General Gamedesign, XNA | 3 Comments »

Tutorial: Using the XNA Content Pipeline for localization: Part 2, Assets

Posted by Roy Triesscheijn on 20th February 2010

(read about part 1 here)

SgtConker has uploaded the second part of my tutorial on localization. This time we automatically localize assets and the code is going to make a lot more sense. (The way strings where localized might look a bit odd without seeing part 2).

Anyway without further introduction, you can find part 2 at sgtconker.com here.

First screen

kick it on GameDevKicks.com

Tags: , , , , ,
Posted in Blog, General Gamedesign, XNA | No Comments »

Twitter!?

Posted by Roy Triesscheijn on 17th February 2010

Oh no, I don’t know how this happened. But last night I signed up for Twitter. Who knows what will appear there? (Probably some boring stuff about what I did last week, and some things like “hey I found this out”.)

Anyway, if you’d like you can follow me at http://twitter.com/roytries (@roytries).

For now I don’t have plans to auto-link this blog, but if anything interesting should come up here, I’m sure to let you know.

Oh and before I forgot, the second part of the localization tutorial, should be on http://www.sgtconker.com in a few days. I spoke to the sergeant himself last weekend, and he said he was busy formatting a lot of articles, but he hoped to get around to it soon.

Tags: , ,
Posted in Blog | No Comments »

Half-Life 2 Tribute video

Posted by Roy Triesscheijn on 13th February 2010

I just had to share this video. It’s a beautiful tribute how digital art, immersiveness, and music can really let you feel emotions. Altough everything that happens is not real.

I hope to bring this same feeling to people by programming immersive, emotive video games in a couple of years. Ah heck, aren’t we privileged with emotion and fantasy?

Anyway here’s the beautiful video:

Tags: , ,
Posted in Blog, General Gamedesign | No Comments »

Code snippet: converting keyboard input to text in XNA

Posted by Roy Triesscheijn on 11th February 2010

In the standard XNA libraries, there is no method that deals with converting keyboard input to text. There are several methods on the Internet, but most of them are pretty incomplete or extremely slow. I found one solution, which was pretty complete, but it used a gigantic “if-then-else if…” to check every key to see if it was pressed. This makes for an extremely slow process (Still O(c) where c is constant), but having the computer look trough 60 if-statements every frame is a bit of a waste.

After I couldn’t find anything else to my liking I decided to create my own method. I used GetPressedKeys() to get the pressed keys. If keys are pressed I take the first one (if a users presses multiple keys at once, except modifier keys like shift, it’s rubbish anyway). And throw that one in a switch, which branches to the correct character immediately (in contrast to an if-then-else-if construction). I’ve tried modeled the code the same as TryParse, so a bool is returned to show that we either converted a character, or not. And via the out keyword, we pass the found character.

(Note: some of the ‘oem’-keys might not register when pressing shift, see here for a bit more in depth info about this known bug in XNA’s Keyboard api).

 /// <summary>
        /// Tries to convert keyboard input to characters and prevents repeatedly returning the
        /// same character if a key was pressed last frame, but not yet unpressed this frame.
        /// </summary>
        /// <param name="keyboard">The current KeyboardState</param>
        /// <param name="oldKeyboard">The KeyboardState of the previous frame</param>
        /// <param name="key">When this method returns, contains the correct character if conversion succeeded.
        /// Else contains the null, (000), character.</param>
        /// <returns>True if conversion was successful</returns>
        public static bool TryConvertKeyboardInput(KeyboardState keyboard, KeyboardState oldKeyboard, out char key)
        {
            Keys[] keys = keyboard.GetPressedKeys();
            bool shift = keyboard.IsKeyDown(Keys.LeftShift) || keyboard.IsKeyDown(Keys.RightShift);            

            if(keys.Length > 0 && !oldKeyboard.IsKeyDown(keys[0]))
            {
                switch (keys[0])
                {
                    //Alphabet keys
                    case Keys.A: if (shift) { key = 'A'; } else { key = 'a'; } return true;
                    case Keys.B: if (shift) { key = 'B'; } else { key = 'b'; } return true;
                    case Keys.C: if (shift) { key = 'C'; } else { key = 'c'; } return true;
                    case Keys.D: if (shift) { key = 'D'; } else { key = 'd'; } return true;
                    case Keys.E: if (shift) { key = 'E'; } else { key = 'e'; } return true;
                    case Keys.F: if (shift) { key = 'F'; } else { key = 'f'; } return true;
                    case Keys.G: if (shift) { key = 'G'; } else { key = 'g'; } return true;
                    case Keys.H: if (shift) { key = 'H'; } else { key = 'h'; } return true;
                    case Keys.I: if (shift) { key = 'I'; } else { key = 'i'; } return true;
                    case Keys.J: if (shift) { key = 'J'; } else { key = 'j'; } return true;
                    case Keys.K: if (shift) { key = 'K'; } else { key = 'k'; } return true;
                    case Keys.L: if (shift) { key = 'L'; } else { key = 'l'; } return true;
                    case Keys.M: if (shift) { key = 'M'; } else { key = 'm'; } return true;
                    case Keys.N: if (shift) { key = 'N'; } else { key = 'n'; } return true;
                    case Keys.O: if (shift) { key = 'O'; } else { key = 'o'; } return true;
                    case Keys.P: if (shift) { key = 'P'; } else { key = 'p'; } return true;
                    case Keys.Q: if (shift) { key = 'Q'; } else { key = 'q'; } return true;
                    case Keys.R: if (shift) { key = 'R'; } else { key = 'r'; } return true;
                    case Keys.S: if (shift) { key = 'S'; } else { key = 's'; } return true;
                    case Keys.T: if (shift) { key = 'T'; } else { key = 't'; } return true;
                    case Keys.U: if (shift) { key = 'U'; } else { key = 'u'; } return true;
                    case Keys.V: if (shift) { key = 'V'; } else { key = 'v'; } return true;
                    case Keys.W: if (shift) { key = 'W'; } else { key = 'w'; } return true;
                    case Keys.X: if (shift) { key = 'X'; } else { key = 'x'; } return true;
                    case Keys.Y: if (shift) { key = 'Y'; } else { key = 'y'; } return true;
                    case Keys.Z: if (shift) { key = 'Z'; } else { key = 'z'; } return true;

                    //Decimal keys
                    case Keys.D0: if (shift) { key = ')'; } else { key = '0'; } return true;
                    case Keys.D1: if (shift) { key = '!'; } else { key = '1'; } return true;
                    case Keys.D2: if (shift) { key = '@'; } else { key = '2'; } return true;
                    case Keys.D3: if (shift) { key = '#'; } else { key = '3'; } return true;
                    case Keys.D4: if (shift) { key = '$'; } else { key = '4'; } return true;
                    case Keys.D5: if (shift) { key = '%'; } else { key = '5'; } return true;
                    case Keys.D6: if (shift) { key = '^'; } else { key = '6'; } return true;
                    case Keys.D7: if (shift) { key = '&'; } else { key = '7'; } return true;
                    case Keys.D8: if (shift) { key = '*'; } else { key = '8'; } return true;
                    case Keys.D9: if (shift) { key = '('; } else { key = '9'; } return true;

                    //Decimal numpad keys
                    case Keys.NumPad0: key = '0'; return true;
                    case Keys.NumPad1: key = '1'; return true;
                    case Keys.NumPad2: key = '2'; return true;
                    case Keys.NumPad3: key = '3'; return true;
                    case Keys.NumPad4: key = '4'; return true;
                    case Keys.NumPad5: key = '5'; return true;
                    case Keys.NumPad6: key = '6'; return true;
                    case Keys.NumPad7: key = '7'; return true;
                    case Keys.NumPad8: key = '8'; return true;
                    case Keys.NumPad9: key = '9'; return true;

                    //Special keys
                    case Keys.OemTilde: if (shift) { key = '~'; } else { key = '`'; } return true;
                    case Keys.OemSemicolon: if (shift) { key = ':'; } else { key = ';'; } return true;
                    case Keys.OemQuotes: if (shift) { key = '"'; } else { key = '\''; } return true;
                    case Keys.OemQuestion: if (shift) { key = '?'; } else { key = '/'; } return true;
                    case Keys.OemPlus: if (shift) { key = '+'; } else { key = '='; } return true;
                    case Keys.OemPipe: if (shift) { key = '|'; } else { key = '\\'; } return true;
                    case Keys.OemPeriod: if (shift) { key = '>'; } else { key = '.'; } return true;
                    case Keys.OemOpenBrackets: if (shift) { key = '{'; } else { key = '['; } return true;
                    case Keys.OemCloseBrackets: if (shift) { key = '}'; } else { key = ']'; } return true;
                    case Keys.OemMinus: if (shift) { key = '_'; } else { key = '-'; } return true;
                    case Keys.OemComma: if (shift) { key = '<'; } else { key = ','; } return true;
                    case Keys.Space: key = ' '; return true;
                }
            }

            key = (char)0;
            return false;
        }

I hope you enjoy this code, feel free to use it, but if you do please post a thank you message here or something like that. As usual tips, tricks, comments and improvements are welcome!

Tags: , , , , , ,
Posted in Blog, XNA | 4 Comments »