My History of Game Programming (Part 9 – Unity/C#)

This project was a very quick prototype that was worked on for about a week. The idea was to put together the basic elements of a grid based FPS RPG which came about after my friend was enjoying the game Starcrawlers. One cool thing about Unity is how easy it is to throw together a prototype to get the feel for a game idea you have in your head that you wish to experience.

The combat uses time units to choose the turn order. The initial turn order is determined by a units initiative value and different attacks have different time units assigned, so a fast attack will put you ahead of an enemy using a slower attack when determining next turn order placement after the attack is made.

One cool thing I learned in this project is the use of a shader on 2d sprites which makes the lighting effect the sprites in different ways. For some reason in my past 2d work I never got into lighting 2d images. I discovered you can achieve really cool visuals using shaders with 2d sprites though.

Below is a gif of the prototype game play using the original Doom game sprites and some free assets from the Unity© Asset Store.

GIF

I noticed that I have gotten rather good at modifying the default FPSController.cs code to fit the needs of different projects. In this I made the movement advance a grid space per movement direction inputted and also a quick turn which rotates the camera 90 degrees in either left or right direction when a hotkey is pressed. This is a very common control in this game type.

Below is the part of the FPS Controller I wrote to do 90 degree turns. It needs a little fine tuning but works well enough for a prototype.


	UpdateCursorLock();

    if (INVERTED)
    {
        if (YSensitivity > 0)
            YSensitivity = -YSensitivity;
    }
    else
    {
        YSensitivity = Math.Abs(YSensitivity);
    }
    float yRot = CrossPlatformInputManager.GetAxis("Mouse X") * XSensitivity /** Time.deltaTime*/;
    float xRot = CrossPlatformInputManager.GetAxis("Mouse Y") * YSensitivity /** Time.deltaTime*/;
    if (Input.GetButtonDown("Rotate Left") && !rotating)
    {
        rotating = true;
        targetAngle = transform.eulerAngles.y - 90;
        target = new Quaternion(0, 0, 0, 0);
        target.eulerAngles = new Vector3(transform.eulerAngles.x, targetAngle, transform.eulerAngles.z);
    }
    if (Input.GetButtonDown("Rotate Right") && !rotating)
    {
        rotating = true;
        targetAngle = transform.eulerAngles.y + 90;
        target = new Quaternion(0, 0, 0, 0);
        target.eulerAngles = new Vector3(transform.eulerAngles.x, targetAngle, transform.eulerAngles.z);
    }

    if (!rotating)
    {
        transform.Rotate(0, yRot, 0);
        transform.GetChild(0).Rotate(-xRot, 0, 0);
    }
    else if (rotating)
    {
        if (Math.Round(transform.eulerAngles.y) != Math.Round(target.eulerAngles.y))
        {
            transform.rotation = Quaternion.Slerp(transform.rotation, target, Time.deltaTime * 10f);
        }
        else
        {
            rotating = false;
        }
    }

The prototype also has saving and loading using the BinaryFormatter which made it quick and easy to implement. Below is a few methods which performs the saving and loading.

    public void SaveData()
    {
        if (!Directory.Exists("Saves"))
            Directory.CreateDirectory("Saves");

        BinaryFormatter formatter = new BinaryFormatter();
        FileStream saveFile = File.Create("Saves/save.binary");

        formatter.Serialize(saveFile, saveDataClass);

        saveFile.Close();
    }

    public void LoadData()
    {
        BinaryFormatter formatter = new BinaryFormatter();
        FileStream saveFile = File.Open("Saves/save.binary", FileMode.Open);

        saveDataClass = (SaveDataClass)formatter.Deserialize(saveFile);

        saveFile.Close();

        SetData();
    }

    public void SetData()
    {
        playerPosition.x = saveDataClass.posX;
        playerPosition.y = saveDataClass.posY;
        playerPosition.z = saveDataClass.posZ;
        lookX = saveDataClass.lookX;
        Instance.lookY = saveDataClass.lookY;
        hp = saveDataClass.hp;
        maxhp = saveDataClass.maxhp;
        mp = saveDataClass.mp;
        initiative = saveDataClass.initiative;
        xp = saveDataClass.xp;
        lvl = saveDataClass.lvl;
        charClass = saveDataClass.charClass;
        attackTU = saveDataClass.attackTU;
        attackDMG = saveDataClass.attackDMG;
        //also can LoadSceneMode.Additive which loads it into current scene
        SceneManager.LoadScene(saveDataClass.sceneName, LoadSceneMode.Single); 
        isSceneBeingLoaded = true;
        //save the current state of the enemies on the level
        if (saveDataClass.enemies != null)
        {
            enemies = saveDataClass.enemies;
        }
    }

    [System.Serializable]
    public class SaveDataClass
    {
        public int hp = 300;
        public int maxhp = 0;
        public int mp = 0;
        public int xp = 0;
        public int lvl = 1;
        public string charClass = "default";
        public int attackTU = 200;
        public int attackDMG = 50;
        public int initiative = 2;
        public string sceneName = "level1";
        public float posX, posY, posZ;
        public float lookX, lookY;
        public bool[] enemies;
    }