Let’s Code Rogue-Like in Unity … Part 5

Today’s theme is static GUI (UI in modern parlance).

Unity works with Canvas Object that has a number of UI objects (or other GameObjects). Various objects are presented within Canvas. This can be done in two ways:
With fixed values set against origo – This uses Pos X, Pos Y, Pos Z as well as object’s Width and Height. The main advantage of this system is that it allows setting precisely position of each GraphicObject in relation to each other. Unfortunately it does not scale when game’s screen resolution is changed. Thus object’s size is same even if screen would be smaller. This quickly crowds out screen if graphics settings are poorer.
With anchors – This sets graphical anchors as Maximum and Mininmum values vis a vis X- and Y- axis coordinates. The main advantage of this method is that it sets the anchoring as proportion (percentage of parent GraphicObject shown as value between 0 to 1). When anchors are set to match borders of GraphicObject in RectTransform (Left, Top, Right and Bottom values are set as 0) the graphic object scales automatically in X- and Y-axis of parent object (typically Canvas).

I decided to use anchors to make final graphics of my UI. The layout itself is conventional with a semitransparent panel as background and various setting elements shown as dropdown boxes. Audio will use Slider because it allows flexible value adjusting.

The Dropdown box is built from a button which will hold a GameObject with a VerticalLayout. This layout will then hold a number of Button elements.
I looked inspiration from Lena Florian. See:

https://www.youtube.com/channel/UCyCUUkurQ2RieUI8lmg-ScQ

The rest of the work was simply adding necessary object handling for storing and loading data and changing displays to show it. This can be done by sing simple OnClicked() events to fire to feed new values for properties which are then used to store new values:

    int framerate;
    public int newFramerate
    {
        set
        {
            framerate = value;
        }
    }

Correctly set values can then be immediately stored as PlayerPrefs values.

        PlayerPrefs.SetInt("ScreenRefreshRate", framerate);

Values shown for each Button element is best done at Start() (when Scene is launched). I decided to use RefreshEverything() so I can refresh the display whenever necessary. This is needed when default values are set and button values are consequently refreshed.

public class FrameRateButtonText : MonoBehaviour
{

    // Use this for initialization
    void Start()
    {
        this.RefreshEverything();
    }

    public void RefreshEverything()
    {
        switch (PlayerPrefs.GetInt("ScreenRefreshRate"))
        {
            case 30:
                this.GetComponent<Text>().text = "30 Hz";
                break;
            case 60:
                this.GetComponent<Text>().text = "60 Hz";
                break;
            default:
                break;
        }
    }
}

With Settings scene working adding additional settings becomes really a matter of additional GUI elements and associated dropdown boxes.

Next thing is to add ore elements with persistent storage such as high scores and strategy advice as well as company news…

Let’s Code Rogue-Like in Unity … Part 4

I decided that next phase in coding is to set persistent information for the game. This kind of information typically include audio and video settings such as frame rate and screen resolution. Depending on game there should also be information concerning player avatar’s advancement and status if necessary.

Persistent information can be stored in different ways. First technique is to use PlayerPrefs dictionary which functions very much like archiving/unarchiving in iOS. It is important to remeber that PlayerPrefs stored data is not encrypted so it should not be used for anything else than game settings. Thus I decided to use it to store some of my game’s audio and visual settings.

Following information is typically set within game
1) Audio settings – Game audio has typically two important settings: music and audio effects. Exact setup of audio is typically left to computer’s settings and game only deals with volume. Minimum audio set-up should have one audio level to music and another to audio effects. Master volume control is typically added as a convenience for player to allow her kill off sound when necessary.

I decide to create gameobject as a AudioManager which will handle all these three issues.

2) Video settings – Game video settings deal with game graphics and their rendering during game play. Graphics rendering is typically carried out by GPU (Graphics Processing Unit) where better graphics generally mean more computing overhead taken away from game execution. Video settings need to changed by individual player and game maker has to provide support for that.

Following issues should be considered:
Resolution – This value is described in X and Y coordinates. It describes the number of pixel columns and rows shown in display (or static image). The picture quality is better the more pixels are shown (image is both sharper and smoother) but this is very taxing to GPU. Running game on too high resolution leads to stuttering as GPU cannot render pixels fast enough. Typically gamers reduce the resolution to next lower rating compared to computer’s ideal resolution to reduce stuttering.

Vertical Synchronization (V-Sync) – This value shows display’s framerate. It describes the number of time dispay is refreshed in a second. Framerates vary from several hundreds (specialized sports broadcasts) to low dozens depending on technology. Computer video screens are typically 60 and most hand-held mobile phones use 30. Computer games have variable framerate due unpredictable rate of their game loop cycle while screen hardware has a set framerate.

If game loop and video screen framerates differ wildly the gamer will see following effects. First, if game’s framerate is higher than screen’s framerate, the actual framerate rendered will not increase above hardware’s framerate. If game’s framerate is considerably lower than screen hardware’s framerate, the viewer will experience lag and screen tear (display shows parts of multiple frames simultaneously). Changing Vertical Synchronization to lower level makes game less responsive to players actions but it also reduces (even eliminates) screen tear.

Anti-Aliasing – This value shows how much processing power is used to smooth edges of jagged objects to be rendered. Pixels are square elements while many objects to be shown have jagged lines or textures. During anti-aliasing color of edge pixels is blended with pixels in both sides of edge. This results is more realistic looking image (due smoother transition between clours in edges). Actual anti-aliasing technique used differ from one GPU setting to another. Use of antialiasing techniques increases increases workload of GPU. Typically game should not deal with actual GPU antialiasing technique settings but it should determine how much computing power is dedicated to it.

Texture Quality – This value sets resolution given to textures used with 3D model surfaces. More detailed textures look sharper and have more details. High Texture quality makes surfaces look more realistic and players see difference between shown materials (like plastic and leather look and feel). Texture quality does not directly affect video rendering performance (it does not use GPU’s processing power) but more detailed textures use more VRAM (Video RAM). VRAM holds these elements (and many others such as light) in video buffer that GPU uses to render them. When GPU runs out of VRAM it will compensate this by moving some elements to DRAM. This drastically drops frame rate. Game cannot really change VRAM (this depends on GPU hardware setup) but game should have changeable texture quality values so player can prevent VRAM from coming overwhelmed.

Shadow Quality – This value sets quality of shadows game renders to screen according to light sources and objects on screen. Shadows are calculated in real time for rendering and they are processing power intensive. The higher settings create more realistic shadows while lower settings make shadows come across less realistic or even remove them alltogether. Player should be able to change Shadow Quality to accept that some effects are more realistic than others.

Motion Blur – This value determines how rapid camera pan is depicted on display. Motion blur is an effect where movement is so fast that human eye cannot follow it leading to very short blur during movement. This effect is typically used to describe effect of player’s avatar falling suddenly. Blur effect is processing power intensive and some players feel motion sickness when they see it. Game should have option to set motion blur on and off to protect player health whenever necessary.

Camera Filtering (Lense Flare and Bloom) – This value determines post-processing effects added to already-rendered images. Filtering make video appear deeper and screen to have more three-dimensional aspect. These effects are usually used in fantasy games where otherworldly imagery is achieved via use of camera filtering techniques. Turning these effects off make game screen appear flatter. Game should have option to set on and off these effects as they use processing power of GPU.

I studied what Unity immediately supports from these issues and I noticed that free version of Unity had support to basic video settings such as screen size, framerate, antialiasing value, texture quality and vertical synchronization. From audio settings standpoint there is a master sound control.

Logical next step was inserting these items to code. I decided that settings must be used in two places: At Start-up – This is a time when game must check what (if any) settings has been made previously. If none has been set, the game must proceed with default values. Otherwise it should choose player’s settings.
At Settings Page – This is main settings set-up place. In this page there must be support for changing (and thus storing) new settings as well as a way to return to system defaults.

For start-up I decided to employ script in my launching Scene with Awake method of MonoBehaviour. This method is triggered once after GameObject is instantiated so it effectively fires when game is started. Existence of potential player settings must be tested next. I used HasKey method of PlayerPrefs for this. In essense this boolean method checks if a key-value pair already exists.

    void Awake()
    {
        if (!PlayerPrefs.HasKey("ScreenSizeWidth"))
        {
            SetDefaultSettings();
        }
        LoadSettings();
    }

Second place where I needed to change things was settings page. Here I made a placeholder code that simply replicated the code above. This allowed me to test software to make sure game logic worked correctly.

Then it was time to start testing. Protip: PlyerPrefs values are stored to HKEY_CURRENT_USER values of Windows PC’s registers (use regedit to find them).

Next step is making sure game’s UI supports changing these settings…

Let’s Code Rogue-Like in Unity … Part 3

Today’s theme is to determine necessary layout for game’s basic infrastructure. This infrastructure should allow determining game settings and support to game playing as easy as possible. Basic principles are the same for different kinds of video games.

At least following functions are necessary:
1) Exit – Game must be able to be stopped and user has to be able to return to normally use of her computer.
2) Scores – Player typically wants have a record of her game progress. This typically shows top 10 scores achieved.
3) Statistics – Some players want to follow their long term achievements. This can include information that has no direct relevance to game play itself. For instance, a role-playing game could record total of heroine’s allies killed in all different played games.
4) Settings – Players can have wide array of hardware in use. Thus game should be able to support different kinds of hardware by changing audio and video settings of game to match player’s computer.
5) Game – Game must be able to start the actual game play. Many games have support for continuing previous game play. This is important if game cannot be played through in a single time. Typically continuing function is necessary for any game where game play extends over few minutes.
6) Expansions – Many games extend their life cycle by adding specialized missions, expansions or added game-play elements. Many of these can be integrated directly to game play but some might have to be started from a separate menu. It is advisable to also have settings for activation/inactivation of these expansions in same place.

I support these functions by having following layout structure in my game:

UnityAdventureGame001

I also make each of these modules a separate Scene.

I also make a decision to create a common GameObject to every scene where my UI element scripts will be housed. Effectively this GameObject will hold UI named script that deals with all UI related events and their handling. This is effectively same as to use a code back programming. I use it as I prefer to have a common point for all UI codes to start. It also cuts down number of individual C# scripts floating around.

The current code supports thus change of scenes and exiting application. It still has no actual game and it does not have any kind of persistent information. This is something that has to be added in future…

Let’s Code Rogue-Like in Unity … Part 2

This day’s theme is to make interactive UI GameObject in Unity. In plain english it means making a Button which loads a new Scene when pressed.

Button can be added to Canvas from GameObject->UI->Button. If Button does not appear under Canvas it must be moved there next. Protip: Canvas displays its UI GameObjects in order of their appearance in Hierarchy Window so putting Button above Image means button never shows. Putting it below Image shows both items correctly.

UI GameObjects must be set to correct place in display. In practice this means seeing them first in Scene Viewer as wireframe and then in Game Viewer as how they are shown in game. Protip: Sometimes Scene Viewer not seem to show wiring correctly. Either press ”F” in viewer or double click all UI GameObjects in Hierarchy Window.

Correct positioning in of UI GameObjects is
determined with Rect Transform of the object. Canvas typically covers area of entire view so setting origo and anchoring values in Rect Transform allows positioning of UI GameObjects very neatly.

Two important concepts in Rect Transform are origo and anchoring. Origo is set with window showing objects origo within borders of its parent UI GameObject (or Canvas). Typically iOS, Android and Windows Phone set origo as upper left corner but in my case I set Origo of Button as lower right corner. Pos X and Pos Y now show starting point of drawing of the object. Width and Height values are width and height of the UI GameObject (Button) in question. Note that starting point of the GameObject depends also its Pivot Point values. Pivot Point is typically in the middle of object (values are 0.5 and 0.5 respectively) so iving them values 0 and 0 might be best for static UI elements for beginning coders.

Why Unity needs all these different Origos and Pivots? The simple answer is that they make game makers life easier. For instance Maya Setting Pivot to centrepoint of allows making turning easier (less calculations) than in edge. Setting origo freely allows making game objects any position within display quite easily.

Finally UI Button needs somekind of scripting to function. Major difference between MVC pattern (Model-View-Controller) using mobile phones and Unity is that Unity’s UI GameObjects do not have dedicated code behind files. Instead the UI BUtton’s On Click() event is tied to specific method that can be any public method with signature of one argument and no return value (void).

Thus I create for a temporary use a GameObject that holds a C# language script. This scipt has method StartGameHub which works as following:

using UnityEngine;
using System.Collections;
using UnityEngine.SceneManagement;

public class StartGameButton : MonoBehaviour {

    public void StartGameHub()
    {
        SceneManager.LoadSceneAsync("GameHubScene");
    }
}

Then I just need a empty Scene (named GameHubScene) which would be launched (it will look blue as it holds nothing to show or act on).

Finally my game is ready for testing. Protip: Unity programs do not find (and thus cannot load) Scenes which are not part of the building process. It is good to look at the list of Scenes in the Scenes to Build by selecting File -> Build Settings… and looking at it. If this is not the case, then easiest solution is simply to open each Scene and then selecting again File -> Build Settings… and adding necessary Scenes.

Next time it is time to add information that moves between Scenes by adding a GameManager object to control this…

Let’s Code Rogue-Like in Unity … Part 1

I just got myself a free afternoon in office so I decided to learn some Unity coding on the side.

I want to create basis for a game which takes into consideration all kinds of boring but necessary things like high score tables and screen resolution. I am also intrigued about writing actual Rogue-like game in Unity so doing it will be second step of the project. Thus my first step in Unity studies is to start a new project and make it ”2D” style game. I do it because basic Rogue-likes use 2D graphics.

Unity itself uses a Scene which will hold a number of GameObjects. Scene is effectively a single logical entity which can be loaded and changed as necessary. A game can have multiple Scenes. Number of Scenes does not affect game speed but there is always going to be some inefficiency due time of loading a new scene. furthermore, all previous GameObjects are purged from memory whenever Scene is changed. I chose to have a single Scene for every ”window” of the game to separate and minimize unnecessary code and memory used to do household functions of game from actual game itself.

I code in C# so using it as a language was a complete no brainer. Furthermore, I hope to be able to reuse some of my C# code from previous console-based game. It is probably a vain hope but you never know.

Thus this day’s real meat in Unity coding is to create a LaunchScene that will need to have a background image (to let players know what kind of game they are going to play) and a button that starts the actual main hub of the necessary household functions of game.

Background image is a simple GameObject->UI->Image object. However, fitting it to game requires some work on proper positioning of the camera of the Scene.

First: Set Image (Script) to use ”Set Native Size” and see the vales set as Height and Width of Image GameObject. This is ntive size of image. For instance, a 720p image will be 1280×720.

Any UI objects have a Canvas object. it is effectively a screen where child objects (UI GameObjects) are set. It also has a Canvas Scaler (Script) which is used to scale objects in Canvas according to screen size.

Second: Typically scaling should be ”Scale With Screen Size” to make game support different screen sizes. I also use it. The image’s native size is then used as a Reference Resolution value. Match should be 1 and tied to either Height or Width. In first there seems to be no difference but it is important if picture is not ”standard sized”. The Match should be enough to make cover the screen to be displayed.

Finally I need to consider camera settings. Camera in each Scene is watcher’s eye which looks at the scene rendered to her. Camera’s proper distance is the main question here. It is set in Camera GameObject. Typically Scene has one Main Camera (can be renamed) and distance to viewer’s eye is in Camera script and Size property.

Third: Proper viewing distance depends on size of the screen to be displayed. For a fized image this is calculated as size of the image divided by Reference Pizels per Unit (found in Canvas Scaler script). This can be changed later in code but I scale it according to my background image. Since my background image is 720p (1280×720) the correct value will be 1280/100 = 12.8 and it goes to size.

The screen is now picture perfect. 🙂

2016-03-05_360

 

Let’s Build Rogue-Like … Part 7

This project starts from the basics following Rogue Basin’s advice on doing a suitable program. The step 7 is a basic interaction between objects. In plain English this means making sure the game has some sort of AI to move monsters and a combat system that allows player to kill monsters.

My own solution was to try out few technologies. First I implemented A* algorithm to use for route finding. Then I added triggers for advancement in escalation ladder from harmless wandering to deadly serious combat. Finally I added a very simple combat system. And yes, it works. Now I can kill monsters and thus ”win” the game.

However, the other side of the coin is generally shoddy implementation. This is very common when one works without any kind of game planning or necessary documentation attached to it.

Thus I have been thinking of doing the same exercise again by refactoring the code into more logical architecture and giving some thought on how code should be actually working.

Food for thought for different kinds of things to consider can be found in here:

    class Program
    {
        static void Main(string[] args)
        {
            // Window Settings
            Console.Title = "My Rogue-Like Game v0.1";
            Console.WindowWidth = 80;
            Console.BufferWidth = 80;
            Console.WindowHeight = 25;
            Console.BufferHeight = 25;

            RunShowStartScreen();

            // Create a game object
            GameObject MyGame = new GameObject();
            if (MyGame != null)
            {
                MyGame.RunGameInitializer();
            }

            do
            {
                MyGame.RunGameLoop();
            } while (MyGame.GameFinished == 0);

            RunShowEndScreen(MyGame.GameFinished);
        }

        static void RunShowStartScreen()
        {
            Console.Clear();
            Console.SetCursorPosition(15, 10);
            Console.WriteLine("This is a Rogue-Like Game Called \"My Rogue-Like Game\".");
            Console.SetCursorPosition(15, 15);
            Console.WriteLine("(c) Janne Kemppi, 2016");
            Console.SetCursorPosition(15, 20);
            Console.WriteLine("Press Any Key to Continue...");
            Console.ReadKey();
        }


        static void RunShowEndScreen(int reason)
        {
            Console.Clear();
            Console.SetCursorPosition(20, 10);
            switch (reason)
            {
                case 1:
                    Console.WriteLine("You Died for Hunger...");
                    break;

                case 2:
                    Console.WriteLine("You Died for External Violence...");
                    break;

                case 3:
                    Console.WriteLine("You Stopped Playing via Exit...");
                    break;

                default:
                    Console.WriteLine("You Died for General Error...");
                    break;
            }
            Console.WriteLine("Press Any Key to Continue...");
            Console.ReadKey();
        }
    }
    public class AStar
    {
        public class AStarMapNode
        {
            public int X;
            public int Y;
            public Boolean Traversable;
            public double costfromstart;
            public double costtogoal;
            public double totalcost;
            public AStarMapNode Parent;
            public AStarMapNode() { }
            public AStarMapNode(int newX, int newY, Boolean newTraversable)
            {
                X = newX;
                Y = newY;
                Traversable = newTraversable;
            }

        }

        internal AStarMapNode[,] Map;


        internal List<AStarMapNode> openList;
        internal List<AStarMapNode> closedList;
        internal AStarMapNode endingNode;
        public void AStarSetMap(String[] newmap)
        {
            Map = new AStarMapNode[24, 80];
            Console.Clear();
            for (int i = 0; i < 24; i++)
            {
                for (int j = 0; j < 80; j++)
                {
                    Map[i, j] = new AStarMapNode();
                    Map[i, j].X = j;
                    Map[i, j].Y = i;
                    if (newmap[i][j] == '|' || newmap[i][j] == '-')
                    {
                        Map[i, j].Traversable = false;
                    }
                    else
                    {
                        Map[i, j].Traversable = true;
                    }
                }
            }
        }

        public List<CharacterPosition> AStarSearchPath(CharacterPosition startPosition, CharacterPosition endPosition)
        {
            List<CharacterPosition> result = new List<CharacterPosition>();
            // This A* algorithm has been constructed following pseudocode
            // published in Game Programming Gems. Article is:
            // Bryan Stout: The Basics of A* for Path Planning

            //initialize the open list
            openList = new List<AStarMapNode>();
            openList.Clear();

            //initialize the closed list
            closedList = new List<AStarMapNode>();
            closedList.Clear();

            //determine ending node
            endingNode = new AStarMapNode();
            endingNode.X = endPosition.X;
            endingNode.Y = endPosition.Y;

            //put the starting node on the open list (you can leave its f at zero)
            AStarMapNode startingNode = new AStarMapNode();
            startingNode.X = startPosition.X;
            startingNode.Y = startPosition.Y;
            startingNode.costfromstart = 0;
            startingNode.costtogoal = AStarCalculateMovementCost(startingNode,endingNode);
            startingNode.Parent = null;
            openList.Add(startingNode);

            //while the open list is not empty
            while (openList.Count > 0)
            {
                AStarMapNode Node = new AStarMapNode();
                // LINQ based ascending order sort
                openList = openList.OrderBy(o => o.totalcost).ToList();
                Node = openList.First<AStarMapNode>();
                openList.Remove(Node);
                closedList.Add(Node);

                if (Node.X == endingNode.X && Node.Y == endingNode.Y )
                {
                    while (Node.Parent != null)
                    {
                        CharacterPosition dummy;
                        dummy.X = Node.X;
                        dummy.Y = Node.Y;
                        result.Add(dummy);
                        Node = Node.Parent;
                    }
                    result.Reverse();
                    return result;
                }
                else
                {
                    AStarCheckSuccessorNewNodes(Node);
                }
                closedList.Add(Node);
            }
            return null;
        }

        Boolean AStarCheckSuccessorNewNodes(AStarMapNode Node)
        {
            //generate node's 8 successors and set their parents to q
            // then go through the list
            List<AStarMapNode> successors = AStarGenerateSuccessorNewNodes(Node);

            foreach (AStarMapNode newNode in successors)
            {
                if (closedList.Contains(newNode))
                    continue;

                if (!newNode.Traversable)
                    continue;

                double newmovementcost = Node.costfromstart + AStarCalculateMovementCost(Node, newNode);

                if (openList.Contains(newNode) && newNode.costfromstart <= newmovementcost)
                {
                    continue;
                }

                newNode.Parent = Node;
                newNode.costfromstart = newmovementcost;
                newNode.costtogoal = AStarCalculateMovementCost(newNode, endingNode);
                newNode.totalcost = newNode.costfromstart + newNode.costtogoal;

                if (!openList.Contains(newNode))
                    openList.Add(newNode);

                openList = openList.OrderBy(o => o.totalcost).ToList();
                
            }
            return false;
        }

        List<AStarMapNode> AStarGenerateSuccessorNewNodes(AStarMapNode parentNode)
        {
            List<AStarMapNode> successorList = new List<AStarMapNode>();
            successorList.Clear();

            if (parentNode.X > 0  && parentNode.X < 79 && parentNode.Y > 0 && parentNode.Y < 23)
            {
                if (Map[parentNode.Y - 1, parentNode.X - 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X - 1, parentNode.Y - 1, Map[parentNode.Y - 1, parentNode.X - 1].Traversable));
                if (Map[parentNode.Y, parentNode.X - 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X - 1, parentNode.Y, Map[parentNode.Y, parentNode.X - 1].Traversable));
                if (Map[parentNode.Y + 1, parentNode.X - 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X - 1, parentNode.Y + 1, Map[parentNode.Y + 1, parentNode.X - 1].Traversable));
                if (Map[parentNode.Y + 1, parentNode.X].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X, parentNode.Y + 1, Map[parentNode.Y + 1, parentNode.X].Traversable));
                if (Map[parentNode.Y + 1, parentNode.X + 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X + 1, parentNode.Y + 1, Map[parentNode.Y + 1, parentNode.X + 1].Traversable));
                if (Map[parentNode.Y, parentNode.X + 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X + 1, parentNode.Y, Map[parentNode.Y, parentNode.X + 1].Traversable));
                if (Map[parentNode.Y - 1, parentNode.X + 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X + 1, parentNode.Y - 1, Map[parentNode.Y - 1, parentNode.X + 1].Traversable));
                if (Map[parentNode.Y - 1, parentNode.X].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X, parentNode.Y - 1, Map[parentNode.Y - 1, parentNode.X].Traversable));
            }
            else if (parentNode.X == 0 && parentNode.Y > 0 && parentNode.Y < 23)
            {
                if (Map[parentNode.Y + 1, parentNode.X].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X, parentNode.Y + 1, Map[parentNode.Y + 1, parentNode.X].Traversable));
                if (Map[parentNode.Y + 1, parentNode.X + 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X + 1, parentNode.Y + 1, Map[parentNode.Y + 1, parentNode.X + 1].Traversable));
                if (Map[parentNode.Y, parentNode.X + 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X + 1, parentNode.Y, Map[parentNode.Y, parentNode.X + 1].Traversable));
                if (Map[parentNode.Y - 1, parentNode.X + 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X + 1, parentNode.Y - 1, Map[parentNode.Y - 1, parentNode.X + 1].Traversable));
                if (Map[parentNode.Y - 1, parentNode.X].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X, parentNode.Y - 1, Map[parentNode.Y - 1, parentNode.X].Traversable));
            }
            else if (parentNode.X == 79 && parentNode.Y > 0 && parentNode.Y < 23)
            {
                if (Map[parentNode.Y - 1, parentNode.X - 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X - 1, parentNode.Y - 1, Map[parentNode.Y - 1, parentNode.X - 1].Traversable));
                if (Map[parentNode.Y, parentNode.X - 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X - 1, parentNode.Y, Map[parentNode.Y, parentNode.X - 1].Traversable));
                if (Map[parentNode.Y + 1, parentNode.X - 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X - 1, parentNode.Y + 1, Map[parentNode.Y + 1, parentNode.X - 1].Traversable));
                if (Map[parentNode.Y + 1, parentNode.X].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X, parentNode.Y + 1, Map[parentNode.Y + 1, parentNode.X].Traversable));
                if (Map[parentNode.Y - 1, parentNode.X].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X, parentNode.Y - 1, Map[parentNode.Y - 1, parentNode.X].Traversable));
            }
            else if (parentNode.X > 0 && parentNode.X < 79 && parentNode.Y == 0)
            {
                if (Map[parentNode.Y, parentNode.X - 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X - 1, parentNode.Y, Map[parentNode.Y, parentNode.X - 1].Traversable));
                if (Map[parentNode.Y + 1, parentNode.X - 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X - 1, parentNode.Y + 1, Map[parentNode.Y + 1, parentNode.X - 1].Traversable));
                if (Map[parentNode.Y + 1, parentNode.X].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X, parentNode.Y + 1, Map[parentNode.Y + 1, parentNode.X].Traversable));
                if (Map[parentNode.Y + 1, parentNode.X + 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X + 1, parentNode.Y + 1, Map[parentNode.Y + 1, parentNode.X + 1].Traversable));
                if (Map[parentNode.Y, parentNode.X + 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X + 1, parentNode.Y, Map[parentNode.Y, parentNode.X + 1].Traversable));
            }
            else if (parentNode.X > 0 && parentNode.X < 79 && parentNode.Y == 23)
            {
                if (Map[parentNode.Y - 1, parentNode.X - 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X - 1, parentNode.Y - 1, Map[parentNode.Y - 1, parentNode.X - 1].Traversable));
                if (Map[parentNode.Y, parentNode.X - 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X - 1, parentNode.Y, Map[parentNode.Y, parentNode.X - 1].Traversable));
                if (Map[parentNode.Y, parentNode.X + 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X + 1, parentNode.Y, Map[parentNode.Y, parentNode.X + 1].Traversable));
                if (Map[parentNode.Y - 1, parentNode.X + 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X + 1, parentNode.Y - 1, Map[parentNode.Y - 1, parentNode.X + 1].Traversable));
                if (Map[parentNode.Y - 1, parentNode.X].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X, parentNode.Y - 1, Map[parentNode.Y - 1, parentNode.X].Traversable));
            }
            else if (parentNode.X == 0 && parentNode.Y == 0)
            {
                if (Map[parentNode.Y + 1, parentNode.X].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X, parentNode.Y + 1, Map[parentNode.Y + 1, parentNode.X].Traversable));
                if (Map[parentNode.Y + 1, parentNode.X + 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X + 1, parentNode.Y + 1, Map[parentNode.Y + 1, parentNode.X + 1].Traversable));
                if (Map[parentNode.Y, parentNode.X + 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X + 1, parentNode.Y, Map[parentNode.Y, parentNode.X + 1].Traversable));
            }
            else if (parentNode.X == 0 && parentNode.Y == 23)
            {
                if (Map[parentNode.Y, parentNode.X + 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X + 1, parentNode.Y, Map[parentNode.Y, parentNode.X + 1].Traversable));
                if (Map[parentNode.Y - 1, parentNode.X + 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X + 1, parentNode.Y - 1, Map[parentNode.Y - 1, parentNode.X + 1].Traversable));
                if (Map[parentNode.Y - 1, parentNode.X].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X, parentNode.Y - 1, Map[parentNode.Y - 1, parentNode.X].Traversable));
            }
            else if (parentNode.X == 79 && parentNode.Y == 0)
            {
                if (Map[parentNode.Y, parentNode.X - 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X - 1, parentNode.Y, Map[parentNode.Y, parentNode.X - 1].Traversable));
                if (Map[parentNode.Y + 1, parentNode.X - 1].Traversable)
                successorList.Add(new AStarMapNode(parentNode.X - 1, parentNode.Y + 1, Map[parentNode.Y + 1, parentNode.X - 1].Traversable));
                if (Map[parentNode.Y + 1, parentNode.X].Traversable)
                successorList.Add(new AStarMapNode(parentNode.X, parentNode.Y + 1, Map[parentNode.Y + 1, parentNode.X].Traversable));
            }
            else if (parentNode.X == 79 && parentNode.Y == 23)
            {
                if (Map[parentNode.Y - 1, parentNode.X - 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X - 1, parentNode.Y - 1, Map[parentNode.Y - 1, parentNode.X - 1].Traversable));
                if (Map[parentNode.Y, parentNode.X - 1].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X - 1, parentNode.Y, Map[parentNode.Y, parentNode.X - 1].Traversable));
                if (Map[parentNode.Y - 1, parentNode.X].Traversable)
                    successorList.Add(new AStarMapNode(parentNode.X, parentNode.Y - 1, Map[parentNode.Y - 1, parentNode.X].Traversable));
            }

            return successorList;
        }

        double AStarCalculateMovementCost(AStarMapNode node, AStarMapNode newNode)
        {
            // number of different distance calculations 
            // Manhattan distance
            //return Math.Abs(newNode.X - node.X) + Math.Abs(newNode.Y - node.Y);
            // Euclidean distance
            return Math.Sqrt(Math.Pow((newNode.X - node.X), 2) + Math.Pow((newNode.Y - node.Y), 2));
        }
    }

    public struct CharacterPosition
    {
        internal int X;
        internal int Y;
    }

    public class Character
    {
        internal enum NPCStrategy
        {
            Wander,
            Sleep,
            Patrol,
            Fight
        }

        internal Character() { } 
        internal Character(string newType, int newX, int newY, NPCStrategy newStrategy)
        {
            MonsterType = newType;
            MonsterStatus = newType;
            Position.X = newX;
            Position.Y = newY;
            Strategy = newStrategy;
        }
        internal string MonsterType = "";
        internal string MonsterStatus = "";
        
        public CharacterPosition Position;
        internal int HitPointsLeft = 1;

        internal NPCStrategy Strategy = NPCStrategy.Sleep;
        public List<CharacterPosition> Routeselection;

        public Character Enemy;
        public int AttackScore = 1;
        public int DefendScore = 1;
    }

    public class CharacterHero : Character
    {
        internal int FoodLeft = 0;
        internal string ActivityMessage = "";
    }
    class GameObject
    {
        internal const int SCREEN_WIDTH = 80;
        internal const int SCREEN_HEIGHT = 24;

        CharacterHero MyHero;
        internal int GameFinished = 0;

        String[] GameMapCurrent;

        List<String[]> GameMapList = new List<string[]>();

        List<Character> MonsterList = new List<Character>();

        Random random = new Random();

        AStar AStar = new AStar();

        internal void RunGameInitializer()
        {
            int selection = 0;
            do
            {
                Console.WriteLine("Game Initializer");
                Console.WriteLine("1: New Game");
                Console.WriteLine("2: Continue");
                Console.WriteLine("3: Exit Game");
                selection = int.Parse(Console.ReadLine());
            } while (selection != 1 && selection != 2 && selection != 3);

            switch (selection)
            {
                case 1:
                    RunCreateHeroCharacter();
                    RunCreateGameMap();
                    RunDrawGameWindowLayout();
                    Console.CursorVisible = false;
                    GameFinished = 0;
                    break;
                case 2:
                    RunLoadGameData();
                    RunDrawGameWindowLayout();
                    Console.CursorVisible = false;
                    GameFinished = 0;
                    break;
                case 3:
                    GameFinished = 3;
                    break;
                default:
                    break;
            }
        }

        internal void RunGameLoop()
        {
            ConsoleKey PlayerInput = RunGetPlayerInput();

            RunGameLogic(PlayerInput);

            RunMonsterLogic();

            RunDrawGameWindowLayout();

            if (GameFinished == 0)
            {
                GameFinished = RunDetermineEndCondition();
            }

            RunKillDeadOnsters();
        }

        void RunKillDeadOnsters()
        {
            MonsterList.Remove(MonsterList.Find(x => x.HitPointsLeft == 0));
        }

        void RunMonsterLogic()
        {
            foreach (Character monster in MonsterList)
            {
                double range = 0.0;
                monster.Routeselection = new List<CharacterPosition>();
                switch (monster.Strategy)
                {
                    case Character.NPCStrategy.Wander:
                        monster.MonsterStatus = "!";
                        switch (random.Next(8))
                        {
                            case 0:
                                RunCharacterTryToMove(monster, -1, 0);
                                break;
                            case 1:
                                RunCharacterTryToMove(monster, 1, 0);
                                break;
                            case 2:
                                RunCharacterTryToMove(monster, 0, -1);
                                break;
                            case 3:
                                RunCharacterTryToMove(monster, 0, 1);
                                break;
                            default:
                                break;
                        }
                        range = Math.Sqrt(Math.Pow((MyHero.Position.X - monster.Position.X), 2) + Math.Pow((MyHero.Position.Y - monster.Position.Y), 2));
                        if (range <= 2)
                            monster.Strategy = Character.NPCStrategy.Fight;
                        break;
                    case Character.NPCStrategy.Sleep:
                        monster.MonsterStatus = "?";
                        range = Math.Sqrt(Math.Pow((MyHero.Position.X - monster.Position.X),2) + Math.Pow((MyHero.Position.Y - monster.Position.Y),2));
                        if (range <= 2)
                            monster.Strategy = Character.NPCStrategy.Fight;
                        break;
                    case Character.NPCStrategy.Patrol:
                        range = Math.Sqrt(Math.Pow((MyHero.Position.X - monster.Position.X), 2) + Math.Pow((MyHero.Position.Y - monster.Position.Y), 2));
                        if (range <= 4)
                        {
                            monster.MonsterStatus = monster.MonsterType;
                            monster.Routeselection = AStar.AStarSearchPath(monster.Position, MyHero.Position);
                            if (monster.Routeselection.Count > 0)
                            {
                                monster.Position = monster.Routeselection[0];
                                RunCharacterTryToMove(monster, 0, 0);
                            }
                            range = Math.Sqrt(Math.Abs(MyHero.Position.X - monster.Position.X) + Math.Abs(MyHero.Position.Y - monster.Position.Y));
                            if (range <= 2)
                                monster.Strategy = Character.NPCStrategy.Fight;
                        }
                        break;
                    case Character.NPCStrategy.Fight:
                        monster.Enemy = MyHero;
                        MyHero.Enemy = monster;
                        monster.MonsterStatus = monster.MonsterType;
                        monster.Routeselection = new List<CharacterPosition>();
                        monster.Routeselection = AStar.AStarSearchPath(monster.Position, MyHero.Position);
                        if (monster.Routeselection.Count > 0)
                        {
                            monster.Position = monster.Routeselection[0];
                            RunCharacterTryToMove(monster);
                        }
                        range = Math.Sqrt(Math.Abs(MyHero.Position.X - monster.Position.X) + Math.Abs(MyHero.Position.Y - monster.Position.Y));
                        if (range <= 1)
                        {
                            RunAttackRoutine(MyHero);
                            RunAttackRoutine(monster);
                        }
                        break;
                    default:
                        break;
                }
            }
        }

        void RunAttackRoutine(Character attacker)
        {
            int score = attacker.AttackScore - attacker.Enemy.DefendScore + random.Next(5);
            if (score >= 0)
                attacker.Enemy.HitPointsLeft--;
        }

        Boolean RunCharacterTryToMove(Character thisChar, int toX = 0, int toY = 0)
        {
            if ( ((thisChar.Position.X + toX) < 0) || 
                ((thisChar.Position.X + toX) > (SCREEN_WIDTH-1)) ||
                ((thisChar.Position.Y + toY) < 0) ||
                ((thisChar.Position.Y + toY) > (SCREEN_HEIGHT-1)))
            {
                return false;
            }

            if (RunWallMovementTest(GameMapCurrent[thisChar.Position.Y + toY][thisChar.Position.X + toX]) == true)
            {
                thisChar.Position.X += toX;
                thisChar.Position.Y += toY;
                return true;
            }
            return false;
        }

        void RunCreateHeroCharacter()
        {
            MyHero = new CharacterHero();
            MyHero.FoodLeft = 100;
            MyHero.HitPointsLeft = 5;
            MyHero.Position.X = 1;  //18 ja 11 22
            MyHero.Position.Y = 1; //12
            MyHero.AttackScore = 3;
            MyHero.DefendScore = 3;
            MyHero.ActivityMessage = "Hero Created!";
        }

        ConsoleKey RunGetPlayerInput()
        {
            return Console.ReadKey(true).Key;
        }

        void RunGameLogic(ConsoleKey playerinput)
        {
            switch (playerinput)
            {
                case ConsoleKey.A:
                case ConsoleKey.LeftArrow:
                    if (RunCharacterTryToMove(MyHero,-1,0) == true)
                    {
                        MyHero.ActivityMessage = "Hero Goes West!";
                    }
                    else
                    {
                        MyHero.ActivityMessage = "Hero Tries to Go West!";
                    }
                    break;
                case ConsoleKey.D:
                case ConsoleKey.RightArrow:
                    if (RunCharacterTryToMove(MyHero, 1, 0) == true)
                    {
                        MyHero.ActivityMessage = "Hero Goes East!";
                    }
                    else
                    {
                        MyHero.ActivityMessage = "Hero Tries to Go East!";
                    }
                    break;
                case ConsoleKey.W:
                case ConsoleKey.UpArrow:
                    if (RunCharacterTryToMove(MyHero, 0, -1) == true)
                    {
                        MyHero.ActivityMessage = "Hero Goes North!";
                    }
                    else
                    {
                        MyHero.ActivityMessage = "Hero Tries to Go North!";
                    }
                    break;
                case ConsoleKey.S:
                case ConsoleKey.DownArrow:
                    if (RunCharacterTryToMove(MyHero, 0, 1) == true)
                    {
                        MyHero.ActivityMessage = "Hero Goes South!";
                    }
                    else
                    {
                        MyHero.ActivityMessage = "Hero Tries to Go South!";
                    }
                    break;
                case ConsoleKey.PageDown:
                    if ( RunStairsMovementTest(GameMapCurrent[MyHero.Position.Y][MyHero.Position.X]) == true )
                    {
                        MyHero.ActivityMessage = "Going Down to Next Level!";
                    }
                    break;
                case ConsoleKey.PageUp:
                    if (RunStairsMovementTest(GameMapCurrent[MyHero.Position.Y][MyHero.Position.X]) == true)
                    {
                        MyHero.ActivityMessage = "Going Up to Previous Level!";
                    }
                    break;
                case ConsoleKey.Q:
                    RunSaveGameData();
                    GameFinished = 3;
                    break;
                case ConsoleKey.Spacebar:
                    break;
                default:
                    break;
            }
            MyHero.FoodLeft--;
        }

        void RunLoadGameData()
        {
            List<String> loaddata = new List<string>();
            String line = "";

            StreamReader file = new StreamReader(Environment.CurrentDirectory + @"\AdventureGame001.txt");
            while ((line = file.ReadLine()) != null)
            {
                loaddata.Add(line);
            }
            file.Close();

            int dummy = 0;
            int numberofmaps = int.Parse(loaddata[dummy]);
            for (int i=0; i < numberofmaps; i++)
            {
                String[] dummymap = new String[24];
                for (int j=0; j < 24; j++)
                {
                    dummy++;
                    dummymap[j] = loaddata[dummy];
                }
                GameMapList.Add(dummymap);
            }
            GameMapCurrent = GameMapList.Last<string[]>();
            AStar.AStarSetMap(GameMapCurrent);

            MyHero = new CharacterHero();
            dummy++;
            MyHero.FoodLeft = int.Parse(loaddata[dummy]);
            dummy++;
            MyHero.HitPointsLeft = int.Parse(loaddata[dummy]);
            dummy++;
            MyHero.Position.X = int.Parse(loaddata[dummy]);
            dummy++;
            MyHero.Position.Y = int.Parse(loaddata[dummy]);

            MyHero.ActivityMessage = "Hero Loaded!";

        }

        void RunSaveGameData()
        {
            List<String> savedata = new List<string>();
            savedata.Add(GameMapList.Count.ToString());
            foreach(string[] map in GameMapList)
            {
                foreach(string iter in map)
                {
                    savedata.Add(iter);
                }
            }
            savedata.Add(MyHero.FoodLeft.ToString());
            savedata.Add(MyHero.HitPointsLeft.ToString());
            savedata.Add(MyHero.Position.X.ToString());
            savedata.Add(MyHero.Position.Y.ToString());

            using (StreamWriter outputFile = new StreamWriter(Environment.CurrentDirectory + @"\AdventureGame001.txt"))
            {
                foreach (string line in savedata)
                    outputFile.WriteLine(line);
            }
        }


        void RunCreateGameMap()
        {
            String[] ZeroLevel = new String[]
                {
                "                                                                                ",
                "   --------------              --------------------           -------------     ",
                "   |            |              |        %         |           |           |     ",
                "   |            |              |     C A V E      |           |           |     ",
                "   |            |              |                  |           |           |     ",
                "   --------------              ---------+----------           -------------     ",
                "                                                                                ",
                "                                                                                ",
                "                                                                                ",
                "                                                             ----------         ",
                "       ------                     ------+-------             |        |         ",
                "       |    |                     |            |             |        |         ",
                "       |    |                     |            |             |        |         ",
                "       |    |                     |  S H O P   |             ----------         ",
                "       ------                     |            |                                ",
                "                                  --------------                                ",
                "                                                                                ",
                "                                                                                ",
                "       -----                       ---------                  --------          ",
                "       |   |                       |       |                  |      |          ",
                "       |   |                       |       |                  |      |          ",
                "       |   |                       |       |                  |      |          ",
                "       -----                       ---------                  --------          ",
                "                                                                                "
            };

            GameMapCurrent = ZeroLevel;
            GameMapList.Add(GameMapCurrent);
            AStar.AStarSetMap(GameMapCurrent);

            Character TestMonster1 = new Character("B", 10, 15, Character.NPCStrategy.Patrol);
            MonsterList.Add(TestMonster1);
            Character TestMonster2 = new Character("B", 20, 15, Character.NPCStrategy.Wander);
            MonsterList.Add(TestMonster2);
        }


        void RunDrawGameWindowLayout()
        {
            Console.Clear();
            for (int i=0; i < SCREEN_HEIGHT; i++)
            {
                for (int j=0; j < SCREEN_WIDTH; j++)
                {
                    Console.Write(GameMapCurrent[i][j]);
                }
            }

            Console.SetCursorPosition(0, 24);
            Console.Write("Food Left: {0}", MyHero.FoodLeft);

            Console.SetCursorPosition(40, 24);
            Console.Write(MyHero.ActivityMessage);

            foreach (Character monster in MonsterList)
            {
                Console.SetCursorPosition(monster.Position.X, monster.Position.Y);
                Console.Write(monster.MonsterStatus);
            }

            Console.SetCursorPosition(MyHero.Position.X, MyHero.Position.Y);
            Console.Write("@");
        }

        Boolean RunWallMovementTest(char testedlocation)
        {
            if (testedlocation == '-' || testedlocation == '|')
            {
                MyHero.ActivityMessage = "Hero Hit wall";
                return false;
            }
            return true;
        }

        Boolean RunStairsMovementTest(char testedlocation)
        {
            if (testedlocation == '%')
            {
                return true;
            }
            return false;
        }

        int RunDetermineEndCondition()
        {
            if (MyHero.FoodLeft <= 0)
            {
                return 1;
            }

            if (MyHero.HitPointsLeft <= 0)
            {
                return 2;
            }

            return 0;
        }

    }