Jump to content
  • Advertisement
Sign in to follow this  
MatsK

C# Clicking text after it's been scrolled

Recommended Posts

I'm creating a textbox for my UI, and I found a way to place the cursor where the user clicks on the text.

Here is all of my code:

using System;
using System.Collections.Generic;
using System.Timers;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace TextEditorTest
{
    public class Cursor
    {
        public int CharacterIndex = 0;
        public string Symbol = "|";
        public Vector2 Position = new Vector2(0, 0);
        public int LineIndex = 0;
        public bool Visible = true;
    }

    /// <summary>
    /// This is the main type for your game.
    /// </summary>
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        private GapBuffer<string> m_Text = new GapBuffer<string>();

        //private List<Vector2> m_CharacterPositions = new List<Vector2>();
        private Dictionary<int, Vector2> m_CharacterPositions = new Dictionary<int, Vector2>();
        private List<Rectangle> m_HitBoxes = new List<Rectangle>();
        private bool m_UpdateCharPositions = true;

        private const int NUM_CHARS_IN_LINE = 10;
        private Vector2 m_TextEditPosition = new Vector2(50, 0), m_TextEditSize = new Vector2(250, 320);
        private SpriteFont m_Font;
        private Timer m_CursorVisibilityTimer = new Timer();
        private Cursor m_Cursor = new Cursor();
        private InputHelper m_Input = new InputHelper();
        private int m_NumLinesInText = 1;

        private bool m_HasFocus = true;
        private bool m_MultiLine = true;
        private bool m_CapitalLetters = false;

        //For how long has a key been presseed?
        private DateTime m_DownSince = DateTime.Now;
        private float m_TimeUntilRepInMillis = 100f;
        private int m_RepsPerSec = 15;
        private DateTime m_LastRep = DateTime.Now;
        private Keys? m_RepChar; //A character currently being pressed (repeated).

        private Vector2 m_TextPosition = new Vector2(0, 0); //Coordinate for anchoring the text.

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            m_Cursor.Position = m_TextEditPosition;
            m_CursorVisibilityTimer = new Timer(100);
            m_CursorVisibilityTimer.Enabled = true;
            m_CursorVisibilityTimer.Elapsed += CursorVisibilityTimer_Elapsed;
            m_CursorVisibilityTimer.Start();

            IsMouseVisible = true;

            Window.TextInput += Window_TextInput;

            m_TextPosition = m_TextEditPosition;
        }

        private void CursorVisibilityTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            if (m_HasFocus)
            {
                if (m_Cursor.Visible)
                    m_Cursor.Visible = false;
                else
                    m_Cursor.Visible = true;
            }
        }

        /// <summary>
        /// The text in this TextEditor instance, containing "\n".
        /// </summary>
        private string TextWithLBreaks
        {
            get
            {
                string Text = "";

                foreach (string Str in m_Text)
                    Text += Str;

                return Text;
            }
        }

        /// <summary>
        /// Returns a line of text.
        /// </summary>
        /// <param name="LineIndex">The index of the line of text to get.</param>
        /// <returns>The line of text, as indicated by the line index.</returns>
        private string GetLine(int LineIndex)
        {
            try
            {
                if (TextWithLBreaks.Contains("\n"))
                    return TextWithLBreaks.Split("\n".ToCharArray())[LineIndex];
            }
            catch(Exception)
            {
                return "";
            }

            return TextWithLBreaks;
        }

        /// <summary>
        /// Make sure cursor's index is valid and in range.
        /// </summary>
        private void FixCursorIndex()
        {
            if (m_Cursor.CharacterIndex < 0)
                m_Cursor.CharacterIndex = 0;
            if (m_Cursor.CharacterIndex == m_Text.Count)
                m_Cursor.CharacterIndex = m_Text.Count - 1;
        }

        /// <summary>
        /// Make sure cursor's position is valid and in range.
        /// </summary>
        private void FixCursorPosition()
        {
            if (m_Cursor.Position.X < m_TextEditPosition.X)
                m_Cursor.Position.X = m_TextEditPosition.X;

            m_UpdateCharPositions = true;
            UpdateCharacterPositions();
            UpdateHitboxes();

            //Find the curor's real character index.
            int RealCharIndex = m_Cursor.CharacterIndex;

            RealCharIndex = (RealCharIndex < m_CharacterPositions.Count) ?  //Make sure it doesn't overflow.
                RealCharIndex : m_CharacterPositions.Count - 1;

            if (RealCharIndex < 0)
                RealCharIndex = 0; //Make sure it doesn't underflow.

            //Adjust the character's position based on the real character index.
            if (m_Text.Count > 0)
            {
                Vector2 IndexPosition = m_CharacterPositions[(RealCharIndex > 0) ? RealCharIndex : 0];

                if (m_Cursor.Position.X < IndexPosition.X)
                    m_Cursor.Position.X = IndexPosition.X;
                if (m_Cursor.Position.Y != IndexPosition.Y)
                    m_Cursor.Position.Y = IndexPosition.Y;
            }
        }

        /// <summary>
        /// The text in this TextEditor instance, without \n 
        /// (except for those explicitly added by pressing backspace).
        /// </summary>
        public string Text
        {
            get
            {
                string Text = "";

                foreach (string Str in m_Text)
                    Text += Str;

                return Text.Replace("\n", "");
            }
        }

        /// <summary>
        /// Returns the width of a character in this font.
        /// </summary>
        /// <returns>The width of the character in floating point numbers.</returns>
        private float CharacterWidth
        {
            get { return m_Font.MeasureString("a").X; }
        }

        /// <summary>
        /// Returns the width of a capitalized character in this font.
        /// </summary>
        /// <returns>The width of the capitalized character in floating point numbers.</returns>
        private float CapitalCharacterWidth
        {
            get { return m_Font.MeasureString("A").X; }
        }

        /// <summary>
        /// Returns the height of a character in this font.
        /// </summary>
        /// <returns>The height of the character in floating point numbers.</returns>
        private float CharacterHeight
        {
            get { return m_Font.MeasureString("a").Y; }
        }

        /// <summary>
        /// Returns the height of a capitalized character in this font.
        /// </summary>
        /// <returns>The height of the capitalized character in floating point numbers.</returns>
        private float CapitalCharacterHeight
        {
            get { return m_Font.MeasureString("A").Y; }
        }

        /// <summary>
        /// Returns the last line of text in the gap buffer.
        /// </summary>
        /// <returns></returns>
        private string CurrentLine
        {
            get
            {
                if (m_Text.Count > 1)
                {
                    if (TextWithLBreaks.Contains("\n"))
                    {
                        string[] Lines = TextWithLBreaks.Split("\n".ToCharArray());

                        return Lines[Lines.Length - 1];
                    }
                    else
                        return TextWithLBreaks;
                }

                if (m_Text.Count > 0)
                    return m_Text[0];
                else
                    return "";
            }
        }

        /// <summary>
        /// The control received text input.
        /// </summary>
        private void Window_TextInput(object sender, TextInputEventArgs e)
        {
            if (e.Character != (char)Keys.Back)
            {
                int Index = TextWithLBreaks.LastIndexOf("\n", m_Cursor.CharacterIndex);
                if (Index == -1) //No occurence was found!!
                {
                    if (Text.Length <= NUM_CHARS_IN_LINE)
                    {
                        AddText((m_CapitalLetters == true) ? e.Character.ToString().ToUpper() :
                            e.Character.ToString());
                        m_CapitalLetters = false;
                        m_UpdateCharPositions = true;
                        return;
                    }
                    else
                    {
                        if (m_MultiLine)
                        {
                            AddNewline();
                            return;
                        }
                    }
                }

                if ((m_Cursor.CharacterIndex - Index) <= NUM_CHARS_IN_LINE)
                {
                    //If the cursor has moved away from the end of the text...
                    if (m_Cursor.CharacterIndex < (m_Text.Count - (1 + m_NumLinesInText)))
                    {
                        //... insert it at the cursor's position.
                        m_Text.Insert(m_Cursor.CharacterIndex, (m_CapitalLetters == true) ? e.Character.ToString().ToUpper() : 
                            e.Character.ToString());
                        m_CapitalLetters = false;
                        m_UpdateCharPositions = true;
                    }
                    else
                    {
                        AddText((m_CapitalLetters == true) ? e.Character.ToString().ToUpper() : 
                            e.Character.ToString()); //... just add the text as usual.
                        m_CapitalLetters = false;
                        m_UpdateCharPositions = true;
                    }
                }
                else
                {
                    if(m_MultiLine)
                        AddNewline();
                }
            }
        }

        /// <summary>
        /// Adds a string to m_Text, and updates the cursor.
        /// </summary>
        /// <param name="Text">The string to add.</param>
        private void AddText(string Text)
        {
            m_Text.Add(Text);
            m_Cursor.CharacterIndex++;
            m_Cursor.Position.X += CharacterWidth;
        }

        //Can the cursor move further down or has it reached the end of the textbox?
        private bool m_CanMoveCursorDown = true;

        /// <summary>
        /// Adds a newline to m_Text, and updates the cursor.
        /// </summary>
        private void AddNewline()
        {
            m_Text.Add("\n");
            m_Cursor.CharacterIndex++;
            m_Cursor.Position.X = m_TextEditPosition.X;

            m_Cursor.LineIndex++;

            //Scroll the text up if it's gone beyond the borders of the control.
            if ((m_TextEditPosition.Y - TextSize().Y) < (m_TextEditPosition.Y - m_TextEditSize.Y))
            {
                m_TextPosition.Y -= CapitalCharacterHeight;
                m_CanMoveCursorDown = false;
                m_UpdateCharPositions = true;
            }

            if (m_CanMoveCursorDown)
                m_Cursor.Position.Y += CapitalCharacterHeight;

            m_NumLinesInText++;
        }

        /// <summary>
        /// Removes text from m_Text.
        /// </summary>
        private void RemoveText()
        {
            FixCursorIndex();
            FixCursorPosition();

            if (m_Cursor.Position.X > m_TextEditPosition.X)
            {
                m_Text.RemoveAt(m_Cursor.CharacterIndex);
                m_Cursor.CharacterIndex--;
                m_Cursor.Position.X -= CharacterWidth;
            }

            if (m_Cursor.Position.X <= m_TextEditPosition.X)
            {
                if (m_Cursor.LineIndex != 0)
                {
                    m_Cursor.Position.X = m_TextEditPosition.X +
                        m_Font.MeasureString(GetLine(m_Cursor.LineIndex - 1)).X;

                    if (m_MultiLine)
                    {
                        m_Cursor.Position.Y -= CapitalCharacterHeight;
                        m_Cursor.LineIndex--;

                        m_NumLinesInText--;

                        if (m_TextPosition.Y < m_TextEditPosition.Y)
                            m_TextPosition.Y += m_Font.LineSpacing;
                    }
                }
            }
        }

        /// <summary>
        /// Moves m_Cursor left.
        /// </summary>
        private void MoveCursorLeft()
        {
            if (m_Cursor.Position.X > m_TextEditPosition.X)
            {
                m_Cursor.CharacterIndex -= (((NUM_CHARS_IN_LINE + 1) -
                    GetLine(m_Cursor.LineIndex).Length) + GetLine(m_Cursor.LineIndex).Length);

                m_Cursor.Position.X -= CapitalCharacterHeight;
            }

            //Scroll the text right if the cursor is at the beginning of the control.
            if (m_Cursor.Position.X == m_TextEditPosition.X)
            {
                if (m_TextPosition.X > m_TextEditPosition.X)
                    m_TextPosition.X -= m_Font.LineSpacing;
            }
        }


        /// <summary>
        /// Moves m_Cursor right.
        /// </summary>
        private void MoveCursorRight()
        {
            if (m_Cursor.Position.X < (m_TextEditPosition.X + m_TextEditSize.X))
            {
                m_Cursor.CharacterIndex += (((NUM_CHARS_IN_LINE + 1) -
                    GetLine(m_Cursor.LineIndex).Length) + GetLine(m_Cursor.LineIndex).Length);

                m_Cursor.Position.X += CapitalCharacterHeight;
            }

            //Scroll the text right if the cursor is at the beginning of the control.
            if (m_Cursor.Position.X == m_TextEditPosition.X)
            {
                if (m_TextPosition.X < m_TextEditPosition.X)
                    m_TextPosition.X += m_Font.LineSpacing;
            }
        }

        /// <summary>
        /// Moves m_Cursor up.
        /// </summary>
        private void MoveCursorUp()
        {
            if (m_Cursor.Position.Y > m_TextEditPosition.Y)
            {
                m_Cursor.LineIndex--;
                m_Cursor.CharacterIndex -= (((NUM_CHARS_IN_LINE + 1) -
                    GetLine(m_Cursor.LineIndex).Length) + GetLine(m_Cursor.LineIndex).Length);

                m_Cursor.Position.Y -= CapitalCharacterHeight;

                m_CanMoveCursorDown = true;
            }

            //Scroll the text down if the cursor is at the top of the control.
            if (m_Cursor.Position.Y == m_TextEditPosition.Y)
            {
                if (m_TextPosition.Y < m_TextEditPosition.Y)
                    m_TextPosition.Y += m_Font.LineSpacing;
            }
        }

        /// <summary>
        /// Moves m_Cursor down.
        /// </summary>
        private void MoveCursorDown()
        {
            if (m_Cursor.Position.Y < (m_TextEditPosition.Y + m_TextEditSize.Y))
            {
                    m_Cursor.LineIndex++;
                    m_Cursor.CharacterIndex += (((NUM_CHARS_IN_LINE + 1) -
                        GetLine(m_Cursor.LineIndex).Length) + GetLine(m_Cursor.LineIndex).Length);

                    m_Cursor.Position.Y += CapitalCharacterHeight;
            }
            else //Scroll the text up if the cursor is at the bottom of the control.
            {
                if ((m_TextPosition.Y + TextSize().Y) > (m_TextEditPosition.Y + m_TextEditSize.Y))
                    m_TextPosition.Y -= m_Font.LineSpacing;
            }
        }

        /// <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

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
            m_Font = Content.Load<SpriteFont>("ProjectDollhouse_11px");
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// game-specific content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Calculates the size of all the text in the textbox.
        /// </summary>
        /// <returns>A Vector2 containing the width and height of the text.</returns>
        private Vector2 TextSize()
        {
            float Width = 0.0f, Height = 0.0f;

            foreach (string Str in TextWithLBreaks.Split("\n".ToCharArray()))
            {
                Vector2 Size = m_Font.MeasureString(Str);
                Width = Size.X;
                Height += Size.Y;
            }

            return new Vector2(Width, Height);
        }

        /// <summary>
        /// Update the hitboxes for the characters in the textbox.
        /// The hitboxes are used to detect collision(s) with the mouse cursor.
        /// </summary>
        private void UpdateHitboxes()
        {
            if (m_UpdateCharPositions)
            {
                int Height = 0;

                m_HitBoxes.Clear();

                //Make sure it doesn't go out of bounds...
                if (m_Text.Count >= 1)
                {
                    for (int i = 0; i < m_CharacterPositions.Count; i++)
                    {
                        //Make sure it doesn't go out of bounds...
                        Height = (int)m_Font.MeasureString(m_Text[i < m_Text.Count ? i : m_Text.Count - 1]).Y;

                        //Create a hitbox for each character if the character isn't the last one.
                        if (i != m_CharacterPositions.Count - 1)
                        {
                            Rectangle Hitbox = new Rectangle((int)m_CharacterPositions[i].X, (int)m_CharacterPositions[i].Y,
                                (int)(m_CharacterPositions[i + 1].X - m_CharacterPositions[i].X), Height);
                            m_HitBoxes.Add(Hitbox);
                        }
                    }
                }

                m_UpdateCharPositions = false;
            }
        }

        /// <summary>
        /// Updates the positions of the characters.
        /// Called when a character is added or deleted from the textbox.
        /// </summary>
        private void UpdateCharacterPositions()
        {
            Vector2 Position = m_TextEditPosition;
            float XPosition = 0, YPosition = 0;
            if (m_UpdateCharPositions)
            {
                m_CharacterPositions.Clear();

                int CharIndex = 0;

                foreach (string Str in TextWithLBreaks.Split("\n".ToCharArray()))
                {
                    XPosition = 0;

                    for (int i = 0; i < Str.Length; i++)
                    {
                        float CharWidth = m_Font.MeasureString(Str.Substring(i, 1)).X;
                        XPosition += CharWidth;

                        m_CharacterPositions.Add(CharIndex, new Vector2(XPosition + m_TextEditPosition.X, Position.Y + m_TextEditPosition.Y));
                        CharIndex++;
                    }

                    YPosition += CapitalCharacterHeight;
                    Position.Y = YPosition;
                }

                ///This shouldn't be set here, because it is set in UpdateHitboxes();
                //m_UpdateCharPositions = false;
            }
        }

        /// <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)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            m_Input.Update();
            UpdateCharacterPositions();
            UpdateHitboxes();

            foreach (Rectangle Hitbox in m_HitBoxes)
            {
                if (Hitbox.Contains(new Vector2(m_Input.MousePosition.X, m_Input.MousePosition.Y)) &&
                    m_Input.IsNewPress(MouseButtons.LeftButton))
                {
                    m_Cursor.Position = new Vector2(Hitbox.X, Hitbox.Y);
                    
                    int CharIndex = m_CharacterPositions.FirstOrDefault(x => x.Value == m_Cursor.Position).Key;
                    if (CharIndex != -1)
                        m_Cursor.CharacterIndex = CharIndex;
                }
            }

            foreach (Keys Key in (Keys[])Enum.GetValues(typeof(Keys)))
            {
                if (m_Input.IsNewPress(Key))
                {
                    m_DownSince = DateTime.Now;
                    m_RepChar = Key;
                }
                else if (m_Input.IsOldPress(Key))
                {
                    if (m_RepChar == Key)
                        m_RepChar = null;
                }

                if (m_RepChar != null && m_RepChar == Key && m_Input.CurrentKeyboardState.IsKeyDown(Key))
                {
                    DateTime Now = DateTime.Now;
                    TimeSpan DownFor = Now.Subtract(m_DownSince);
                    if (DownFor.CompareTo(TimeSpan.FromMilliseconds(m_TimeUntilRepInMillis)) > 0)
                    {
                        // Should repeat since the wait time is over now.
                        TimeSpan repeatSince = Now.Subtract(m_LastRep);
                        if (repeatSince.CompareTo(TimeSpan.FromMilliseconds(1000f / m_RepsPerSec)) > 0)
                            // Time for another key-stroke.
                            m_LastRep = Now;
                    }
                }
            }

            Keys[] PressedKeys = m_Input.CurrentKeyboardState.GetPressedKeys();

            //Are these keys being held down since the last update?
            if (m_RepChar == Keys.Back && m_LastRep == DateTime.Now)
                RemoveText();
            if (m_RepChar == Keys.Up && m_LastRep == DateTime.Now)
            {
                if(m_MultiLine)
                    MoveCursorUp();
            }
            if (m_RepChar == Keys.Down && m_LastRep == DateTime.Now)
            {
                if(m_MultiLine)
                    MoveCursorDown();
            }

            foreach (Keys K in PressedKeys)
            {
                if (m_Input.IsNewPress(K))
                {
                    switch(K)
                    {
                        case Keys.Up:
                            if (m_RepChar != Keys.Up || m_LastRep != DateTime.Now)
                            {
                                if(m_MultiLine)
                                    MoveCursorUp();
                            }
                        break;
                        case Keys.Down:
                            if (m_RepChar != Keys.Down || m_LastRep != DateTime.Now)
                            {
                                if (m_MultiLine)
                                    MoveCursorDown();
                            }
                            break;
                        case Keys.Left:
                            if (!m_MultiLine)
                                MoveCursorLeft();
                            break;
                        case Keys.Right:
                            if (!m_MultiLine)
                                MoveCursorRight();
                            break;
                        case Keys.Back:
                            if (m_RepChar != Keys.Back || m_LastRep != DateTime.Now)
                                RemoveText();
                            break;
                        case Keys.LeftShift:
                            m_CapitalLetters = true;
                            break;
                        case Keys.RightShift:
                            m_CapitalLetters = true;
                            break;
                        case Keys.Enter:
                            AddNewline();
                            break;
                    }
                }
            }

            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)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.GraphicsDevice.ScissorRectangle = new Rectangle((int)m_TextEditPosition.X, 
                (int)m_TextEditPosition.Y, (int)m_TextEditSize.X, (int)m_TextEditSize.Y);

            Vector2 Position = m_TextPosition;

            spriteBatch.Begin(SpriteSortMode.BackToFront);

            if(m_Cursor.Visible)
                spriteBatch.DrawString(m_Font, m_Cursor.Symbol, m_Cursor.Position, Color.White);

            // TODO: Add your drawing code here
            foreach (string Str in TextWithLBreaks.Split("\n".ToCharArray()))
            {
                spriteBatch.DrawString(m_Font, Str, Position, Color.White);
                Position.Y += CapitalCharacterHeight;
            }

            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

UpdateCharacterPositions() and UpdateHitboxes() seems to work perfectly. That is, UNTIL the text has been scrolled up:

        /// <summary>
        /// Adds a newline to m_Text, and updates the cursor.
        /// </summary>
        private void AddNewline()
        {
            m_Text.Add("\n");
            m_Cursor.CharacterIndex++;
            m_Cursor.Position.X = m_TextEditPosition.X;

            m_Cursor.LineIndex++;

            //Scroll the text up if it's gone beyond the borders of the control.
            if ((m_TextEditPosition.Y - TextSize().Y) < (m_TextEditPosition.Y - m_TextEditSize.Y))
            {
                m_TextPosition.Y -= CapitalCharacterHeight;
                m_CanMoveCursorDown = false;
                m_UpdateCharPositions = true;
            }

            if (m_CanMoveCursorDown)
                m_Cursor.Position.Y += CapitalCharacterHeight;

            m_NumLinesInText++;
        }

I'm guessing that I need to take m_TextPosition.Y into consideration here:

            foreach (Rectangle Hitbox in m_HitBoxes)
            {
                if (Hitbox.Contains(new Vector2(m_Input.MousePosition.X, m_Input.MousePosition.Y)) &&
                    m_Input.IsNewPress(MouseButtons.LeftButton))
                {
                    m_Cursor.Position = new Vector2(Hitbox.X, Hitbox.Y);
                    
                    int CharIndex = m_CharacterPositions.FirstOrDefault(x => x.Value == m_Cursor.Position).Key;
                    if (CharIndex != -1)
                        m_Cursor.CharacterIndex = CharIndex;
                }
            }

But I'm not entirely sure what to do. I tried both multiplying and adding m_TextPosition.Y to m_Input.MousePosition.Y and Hitbox.Y, but none of them seemed to work.

Please help!

Share this post


Link to post
Share on other sites
Advertisement

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!