Crux Gaming
Zao
Explore a new world in Zao!

General Details

Stationed on a distant barren planet, your goal is to explore and unlock new areas, gather valuable crystals and purchase and upgrade units to help you in your task.
Combining elements from incremental, clicker and memory game genres with unique units and a vast terrain.

Technical Details

- Developed in Unity and programmed in C#.
- Features a very large detailed terrain and units, optimized for mobile.
- Low-poly 3d models with normal/specular and diffuse maps transferred from high-poly models.
- Custom hlsl shaders used on some elements for mobile optimization.
- Features multiple .apk deployments containing optimized texture compression for different hardware, taking advantage of Google Play's ability to distribute the correct version.
- Unity Ads for in-game ads.
- Published on Google Play.


                        /**
                        * GM.cs
                        * A Game Manager script that creates a single point to access commonly
                        * used public data and functions throughout Zao.
                        * NicholusHuber@gmail.com
                        */
                        using System.Collections;
                        using System.Collections.Generic;
                        using UnityEngine;
                        using System.Globalization;
                        using JetBrains.Annotations;

                        public class GM : MonoBehaviour
                        {
                            public static AchievementSystem AchievementSystem { get; private set; }
                            public static Ads               Ads               { get; private set; }
                            public static Search            Search            { get; private set; }
                            public static CamMain           CamMain           { get; private set; }
                            public static CamUnit           CamUnit           { get; private set; }
                            public static CamMap            CamMap            { get; private set; }
                            public static SaveLoad          SaveLoad          { get; private set; }
                            public static SoundFX           SoundFX           { get; private set; }
                            public static UIInfo            UIInfo            { get; private set; }
                            public static UIScores          UIScores          { get; private set; }
                            public static UIMenu            UIMenu            { get; private set; }
                            public static UIUnits           UIUnits           { get; private set; }
                            public static UISearchButton    UISearchButton    { get; private set; }
                            public static UIHexTooltip      UIHexTooltip      { get; private set; }
                            public static Data              Data              { get; private set; }
                            public static TimeData          TimeData          { get; private set; }
                            public static CultureInfo       CultureInfo       { get; private set; }
                            public static Camera            MapCamera         { get; private set; }
                            public static Camera            SearchCamera      { get; private set; }
                            public static Camera            UiCamera          { get; private set; }
                            public static GameObject        UIRootGameObject  { get; private set; }
                            public static FPS               Fps               { get; private set; }
                            public static Tutorial          Tutorial          { get; private set; }
                            public static GameState         PrevGameState     { get; private set; }
                            public static GameState         GameState
                            {
                                get { return _gameState; }
                                set
                                {
                                    if (_gameState.IsIn(UIMenu.IdleGameStates)) PrevGameState = _gameState;
                                    _gameState = value;
                                }
                            }

                            private static GameState  _gameState;
                            private static TweenAlpha _cameraAlpha;
                            private static readonly Dictionary<string, BoxCollider> MainButtonColliders = new Dictionary<string, BoxCollider>();

                            [UsedImplicitly]
                            private void Awake()
                            {
                                AchievementSystem = FindObjectOfType<AchievementSystem>();
                                Ads               = FindObjectOfType<Ads>();
                                Search            = FindObjectOfType<Search>();
                                CamMain           = FindObjectOfType<CamMain>();
                                CamUnit           = FindObjectOfType<CamUnit>();
                                CamMap            = FindObjectOfType<CamMap>();
                                SaveLoad          = FindObjectOfType<SaveLoad>();
                                SoundFX           = FindObjectOfType<SoundFX>();
                                UIInfo            = FindObjectOfType<UIInfo>();
                                UIScores          = FindObjectOfType<UIScores>();
                                UIMenu            = FindObjectOfType<UIMenu>();
                                UIUnits           = FindObjectOfType<UIUnits>();
                                UISearchButton    = FindObjectOfType<UISearchButton>();
                                UIHexTooltip      = FindObjectOfType<UIHexTooltip>();
                                MapCamera         = GameObject.Find("2dMapCamera").GetComponent<Camera>();
                                SearchCamera      = GameObject.Find("_camMain").GetComponent<Camera>();
                                UiCamera          = GameObject.Find("UI Root").GetComponentInChildren<Camera>();
                                UIRootGameObject  = GameObject.Find("UI Root");
                                _cameraAlpha      = GameObject.Find("CameraAlpha").GetComponent<TweenAlpha>();
                                Fps               = GameObject.Find("FPS Counter").GetComponent<FPS>();
                                Tutorial          = GameObject.Find("Panel - Tutorial").GetComponent<Tutorial>();

                                Data.NewData();
                                Data        = Data.Instance;
                                TimeData    = TimeData.Instance;
                                CultureInfo = new CultureInfo("en-us");
                                GameState   = GameState.INTRO;
                                MainButtonColliders.Add("info" , GameObject.Find("UnitInfoButton").GetComponent<BoxCollider>());
                                MainButtonColliders.Add("map"  , GameObject.Find("MapButton").GetComponent<BoxCollider>());
                                MainButtonColliders.Add("menu" , GameObject.Find("MenuButton").GetComponent<BoxCollider>());
                                MainButtonColliders.Add("bonus", GameObject.Find("BonusButton").GetComponent<BoxCollider>());
                            }

                            [UsedImplicitly]
                            private void Update()
                            {
                                for (var i = 0; i < Data.Units.Length; i++)
                                {
                                    Data.Units[i].UnitUpdate(Time.deltaTime);
                                }
                                if (Data.VideoRewardTimer > 0) Data.VideoRewardTimer -= Time.deltaTime;
                                else if (UIScores.BonusLabel.isActiveAndEnabled)
                                {
                                    Data.GlobalMult = Data.GlobalMult;        // update the value
                                    UIScores.BonusLabel.enabled = false;
                                }
                            }


                            /// <summary>
                            /// Displays a message at the top of the screen for x seconds.
                            /// </summary>
                            /// <param name="txt">the message to display</param>
                            /// <param name="time">(opt) time to display</param>
                            public static void SysMsg(string txt, float time = 2.5f)
                            {
                                UIMenu.SysMsg(txt, time);
                            }

                            /// <summary>
                            /// Fade the screen to or from black.
                            /// </summary>
                            /// <param name="fadeToBlack">True to fade to black, false to fade to transparent.</param>
                            public static IEnumerator FadeCam(bool fadeToBlack)
                            {
                                if(fadeToBlack) _cameraAlpha.PlayForward();
                                else _cameraAlpha.PlayReverse();
                                yield return new WaitForSeconds(_cameraAlpha.duration);
                            }

                            /// <summary>
                            /// Enable or disable the 4 'main' UI Buttons. (Menu, Info, Map and Bonus)
                            /// </summary>
                            /// <param name="active"></param>
                            public static void MainButtonsSetAllActive(bool active)
                            {
                                foreach (var entry in MainButtonColliders)
                                {
                                    entry.Value.enabled = active;
                                }

                            }

                            /// <summary>
                            /// Enable or disable one of the 'main' UI Buttons. (info, map, menu or bonus)
                            /// </summary>
                            /// <param name="button">info, map, menu, bonus</param>
                            /// <param name="active">enable/disable</param>
                            public static void MainButtonsSetActive(string button, bool active)
                            {
                                MainButtonColliders[button].enabled = active;
                            }
                        }


                    

                        /**
                        * Miner.cs
                        * Script for the Miner units in Zao. Derives from the abstract 'Unit' base class, providing
                        * a way to create arrays of base units .
                        * NicholusHuber@gmail.com
                        */
                        using System.Collections.ObjectModel;
                        using UnityEngine;

                        [System.Serializable]
                        public class Miner : Unit
                        {
                            /**
                             * Private Constants
                             * Used in calculations for the Miner class
                             */
                            private const ushort BaseMaxCarryAmount = 2;
                            private const ushort BaseMorale         = 1;
                            /**
                             * Public Get Private Set Properties
                             * Used for data that is unique to the Miner class.
                             * Can be accessed in the generic Unit array by casting to a Miner class.
                             */
                            public decimal CurrentMorale { get; protected set; }
                            /**
                             * Public Sealed Override ReadOnlyCollections with auto get accessor. These are assigned to in the derived class constructors and are immutable afterwards. Sealed to prevent errors.
                             * Used for publicly read-only indexed data that will not change during runtime.
                             */
                            public sealed override ReadOnlyCollection<string>  NameStrings               { get; }
                            public sealed override ReadOnlyCollection<decimal> UpgradeAmountDecimals     { get; }
                            public sealed override ReadOnlyCollection<decimal> BaseCostDecimals          { get; }
                            public sealed override ReadOnlyCollection<decimal> CostScalingDecimals1      { get; }
                            public sealed override ReadOnlyCollection<decimal> CostScalingDecimals2      { get; }
                            public sealed override ReadOnlyCollection<string>  UpgradeDescriptionStrings { get; }
                            /// <summary>
                            /// Used in the unit info window stat strings.
                            /// </summary>
                            //


                            public Miner()
                            {
                                NameStrings               = new ReadOnlyCollection<string> (new[] { "Miner", "Lightweight Backpack", "Improved Workboots", "Workers' Morale", "Search Bonus 1" });
                                UpgradeAmountDecimals     = new ReadOnlyCollection<decimal>(new[] {  1m,      1m,                     0.0228m,              0.01m,             1m              });
                                BaseCostDecimals          = new ReadOnlyCollection<decimal>(new[] {  20m,     10m,                    10m,                  10m,               10m             });
                                CostScalingDecimals1      = new ReadOnlyCollection<decimal>(new[] {  0m,      0m,                     0m,                   0m,                0m              });
                                CostScalingDecimals2      = new ReadOnlyCollection<decimal>(new[] {  0.05m,   0.1m,                   1m,                   0.75m,             0.4m            });
                                UpgradeDescriptionStrings = new ReadOnlyCollection<string> (new[]
                                {
                                    "\n\n\n",
                                    string.Format("[b]+{0}[/b] max. carry"         , UpgradeAmountDecimals[1].ToString("N1")),
                                    string.Format("[b]+{0}[/b] gather speed"       , UpgradeAmountDecimals[2].ToString("P1")),
                                    string.Format("[b]+{0}[/b] avg. gather."       , UpgradeAmountDecimals[3].ToString("P1")),
                                    string.Format("[b]+{0} x[/b] Search Bonus (1).", UpgradeAmountDecimals[4].ToString("N1", GM.CultureInfo))
                                });
                                CurrentMaxCarryAmount = BaseMaxCarryAmount;
                                CurrentMorale         = BaseMorale;
                                MaxUpgradeRanks[2]   = CalcMaxWorkbootsRank();
                                MaxUpgradeRanks[3]   = (int) (BaseMorale/UpgradeAmountDecimals[3]);
                            }

                            public override bool Purchase(int upgrade)
                            {
                                if (!CheckIfValidPurchase(upgrade)) return false;
                                IncrementCurrentNumberOwnedInts(upgrade);
                                GM.UIScores.SubtractCurrency(GetCost(upgrade));
                                if (upgrade != 4) TotalSpent += GetCost(upgrade);
                                UpdateCost(upgrade);
                                UpdateStats();
                                UpdateCps();
                                return true;
                            }
                            /// <summary>
                            /// Returns a formatted string for the 'upgrade' - used in the unit info window.
                            /// </summary>
                            /// <param name="upgrade">The upgrade index</param>
                            /// <returns></returns>
                            public override string GetUpgradeStats(int upgrade)
                            {
                                if (upgrade < 1 || upgrade > 3) return "";

                                var showGem   = upgrade == 1;
                                var isPercent = upgrade != 1;
                                var digits    = upgrade == 1 ? 0 : 1;
                                var post      = upgrade == 1 ? "max gather" : upgrade == 2 ? "gather speed" : "avg. gather";

                                var a = "[b](Rank "+ GetCurrentNumberOwnedInts(upgrade) +")[/b] ";
                                var b = showGem ? ":$  " : " ";
                                var c = "+[b]" + (GetCurrentNumberOwnedInts(upgrade)*UpgradeAmountDecimals[upgrade]).ToString(isPercent ? "P" + digits : "N" + digits, GM.CultureInfo) + "[/b] ";
                                var d = "[sup]" + post + "[/sup]";

                                return a + b + c + d;
                            }
                            /// <summary>
                            /// Returns a long description for each unit/upgrade.
                            /// </summary>
                            /// <param name="upgrade">The upgrade index</param>
                            /// <returns></returns>
                            public override string GetUpgradeInfoDesc(int upgrade)
                            {
                                var desc = "";
                                switch (upgrade)
                                {
                                    case 0:
                                        desc = "Miners are relatively low-cost units. However, with sufficient numbers," +
                                               " proper gear and kept happy, they can generate large amounts of crystals.";
                                        break;
                                    case 1:
                                        desc = "Using increasingly advanced materials and designs, each rank provides your miners " +
                                               "with better backpacks, allowing them to carry larger numbers of crystals on each gathering trip.";
                                        break;
                                    case 2:
                                        desc = "Proper footwear is essential in such treacherous terrain. Each rank provides more advanced boots, " +
                                               "decreasing discomfort, fatigue, and injury, and as a result decreasing overall gather times.";
                                        break;
                                    case 3:
                                        desc = "Keeping your miners happy will lead to increased efficiency. " +
                                               "Higher morale increases the likelihood of gathering closer to maximum amounts.";
                                        break;
                                    case 4:
                                        desc = "Each time the number (1) is used in manual search, the miners are called to assist and this bonus is applied.";
                                        break;
                                }

                                return "[i]'" + desc + "'[/i]";
                            }
                            /// <summary>
                            /// Returns dynamic details for each unit/upgrade.
                            /// </summary>
                            /// <param name="upgrade">The upgrade index</param>
                            /// <returns></returns>
                            public override string GetUpgradeInfoDetails(int upgrade)
                            {
                                var details = "";
                                switch (upgrade)
                                {
                                    case 0:
                                        details = string.Format(GM.CultureInfo, "Each miner gathers :$ 0.0 to :$ [E7B126]{0:N1}[-] (:$ [E7B126]{1:N1}[-] avg. with morale) " +
                                                                                "every [E7B126]{2:N1}[-] seconds. Upgrades purchased modify these stats.", CurrentMaxCarryAmount,
                                                                                GetAvgGatherPerUnit(), CurrentGatherLength);
                                        break;
                                    case 1:
                                        details = string.Format(GM.CultureInfo,":$ [b]+{0:N1}[/b] to each miner's maximum carry amount per rank.\n[sub][i]Base max. carry: {1:N1}[/i][/sub]",
                                            UpgradeAmountDecimals[upgrade], BaseMaxCarryAmount);
                                        break;
                                    case 2:
                                        details = string.Format(GM.CultureInfo, "[b]{0:P1}[/b] reduction to the current gather time per rank.\n[sub][i]Base gather time: {1:N1} sec[/i][/sub]",
                                            UpgradeAmountDecimals[upgrade], BaseGatherLength);
                                        break;
                                    case 3:
                                        details = string.Format(GM.CultureInfo, "[b]+{0:P1}[/b] increased chance to gather closer to the maximum amount per rank." +
                                                                                "\n[sub][i]Base skew amount: 0 %[/i][/sub]", UpgradeAmountDecimals[upgrade]);
                                        break;
                                    case 4:
                                        details = string.Format(GM.CultureInfo, "[b]+{0:P1}[/b] bonus to the gather amount when (1) is used in manual searches per rank." +
                                                                                "\n[sub][i]Base manual search amount: 100 %[/i][/sub]", UpgradeAmountDecimals[upgrade]);
                                        break;
                                }
                                return details;
                            }
                            /// <summary>
                            /// Returns the base gather range for miner as a vector 2.
                            /// </summary>
                            /// <returns></returns>
                            public override Vector2 GetBaseGatherRange()
                            {
                                return new Vector2(0, BaseMaxCarryAmount);
                            }

                            /// <summary>
                            /// (Re)calculates the current value of upgradeable stats.
                            /// </summary>
                            protected override void UpdateStats()
                            {
                                CurrentMaxCarryAmount = BaseMaxCarryAmount + UpgradeAmountDecimals[1] * GetCurrentNumberOwnedInts(1);
                                CurrentGatherLength   = CalcGatherTime();
                                CurrentMorale         = BaseMorale - UpgradeAmountDecimals[3] * GetCurrentNumberOwnedInts(3);
                                CurrentSearchBonus    = CalcSearchBonus();
                            }
                            /// <summary>
                            /// Uses the Miner gather formula to return a value.
                            /// MinerGather(false, false, false, false, false) returns the round gather amount.
                            /// MinerGather(true, false, false, false, false) returns the current unit CPS.
                            /// MinerGather(true, true, false, false, false) returns an upgrade purchase CPS gain.
                            /// </summary>
                            /// <param name="returnAvgCps"></param>
                            /// <param name="upgradeUnit"></param>
                            /// <param name="upgrade1Max"></param>
                            /// <param name="upgrade2Speed"></param>
                            /// <param name="upgrade3Morale"></param>
                            /// <returns></returns>
                            protected override decimal CurrencyGeneration(bool returnAvgCps, bool upgradeUnit, bool upgrade1Max, bool upgrade2Speed, bool upgrade3Morale)
                            {
                                var m = upgrade3Morale ? CurrentMorale - UpgradeAmountDecimals[3]                             : CurrentMorale;
                                var t = upgrade2Speed  ? CurrentGatherLength - CurrentGatherLength * UpgradeAmountDecimals[2] : CurrentGatherLength;

                                var a = returnAvgCps   ? (decimal) Mathf.Pow(0.5f, (float) m)                                 : (decimal) Mathf.Pow(Random.value, (float) m);
                                var b = upgradeUnit    ? GetCurrentNumberOwnedInts(0) + 1                                     : GetCurrentNumberOwnedInts(0);
                                var c = upgrade1Max    ? CurrentMaxCarryAmount + UpgradeAmountDecimals[1]                     : CurrentMaxCarryAmount;
                                var d = (decimal) GM.AchievementSystem._afUnitRewardBonus[0];
                                var e = (decimal) GM.Data.GlobalMult;
                                var f = returnAvgCps   ? t                                                                    : 1;

                                return a*b*c*d*e/f;
                            }

                            /// <summary>
                            /// Returns the averaged round gather amount.
                            /// </summary>
                            /// <returns></returns>
                            private float GetAvgGatherPerUnit()
                            {
                                return (float) (GetCurrentNumberOwnedInts(0) > 0 ? CurrencyGeneration(true, false, false, false, false)*CurrentGatherLength/GetCurrentNumberOwnedInts(0) : (0 + BaseMaxCarryAmount) / 2m);
                            }
                            /// <summary>
                            /// Calculates and returns the current gather time.
                            /// </summary>
                            /// <returns></returns>
                            private decimal CalcGatherTime()
                            {
                                decimal time = BaseGatherLength;
                                for (var i = 0; i < GetCurrentNumberOwnedInts(2); i++)
                                    time -= time * UpgradeAmountDecimals[2];
                                return time;
                            }
                            /// <summary>
                            /// Calculates the maximum purchasable rank for the Workboots upgrade.
                            /// </summary>
                            /// <returns></returns>
                            private int CalcMaxWorkbootsRank()
                            {
                                decimal time = BaseGatherLength;
                                var rank = 0;
                                while (time > MinimumGatherTime)
                                {
                                    time -= time * UpgradeAmountDecimals[2];
                                    rank++;
                                }
                                return rank;
                            }
                        }

                    

                        /**
                        * Water.shader
                        * A custom water shader for Zao.
                        * NicholusHuber@gmail.com
                        */
                        Shader "NH/Water"
                        {
                            Properties
                            {
                                _Color("Color Tint", Color) = (1.0,1.0,1.0,1.0)
                                //The main texture file.
                                _MainTex("Diffuse Texture", 2D) = "white" {}
                                //The normal map file.
                                _BumpMap("Normal Texture", 2D) = "bump" {}
                                //A slider to adjust the normal map intensity.
                                _BumpDepth("Bump Depth", Range(-2.0,2.0)) = 1.0
                                //A color picker, used to change the color of the specular highlights.
                                _SpecColor("Spec Color", Color) = (1.0,1.0,1.0,1.0)
                                //A float value, used to adjust the "sharpness" of the specular highlights.
                                _Shininess("Shininess", Float) = 10
                                //A float value, used to control the brightness of the specular highlights.
                                _SpecIntensity("Spec Intensity", Float) = 1
                                //The cubemap that is being used for reflections or refractions.
                                _Cube("Cube Map", Cube) = "" {}
                                //A slider to adjust the normal map intensity.
                                _CubemapMult("Cubemap Mult", Range(0.0, 10.0)) = 1.0

                                _AnimSpeed ("Animation Speed", Float) = 10.0
                                _AnimFreq ("Animation Frequency", Float) = 1.0
                                _AnimPowerX ("Animation Power X", Float) = 0.0
                                _AnimPowerY ("Animation Power Y", Float) = 0.1
                                _AnimPowerZ ("Animation Power Z", Float) = 0.0
                                _AnimOffsetX ("Animation Offset X", Float) = 10.0
                                _AnimOffsetY ("Animation Offset Y", Float) = 0.0
                                _AnimOffsetZ ("Animation Offset Z", Float) = 0.0

                                _FlowSpeedX ("Water Flow Speed X", Float) = 0.0
                                _FlowSpeedY ("Water Flow Speed Y", Float) = 0.0
                            }

                            SubShader
                            {
                                Tags
                                {
                                    "Queue" = "Transparent"
                                }
                                //More about blend types in the comments section below.
                                Blend SrcAlpha OneMinusSrcAlpha
                                Pass
                                {
                                    Tags {"LightMode" = "ForwardBase"}
                                    CGPROGRAM
                                    //pragmas
                                    #pragma vertex vert
                                    #pragma fragment frag

                                    //user defined variables
                                    uniform fixed4 _Color;
                                    uniform sampler2D _MainTex;
                                    uniform half4 _MainTex_ST;
                                    uniform sampler2D _BumpMap;
                                    uniform half4 _BumpMap_ST;
                                    uniform fixed _BumpDepth;
                                    uniform fixed4 _SpecColor;
                                    uniform half _Shininess;
                                    uniform half _SpecIntensity;
                                    //Cube Map Reflections variable
                                    uniform samplerCUBE _Cube;
                                    uniform half _CubemapMult;
                                    uniform half _AnimSpeed;
                                    uniform half _AnimFreq;
                                    uniform half _AnimPowerX;
                                    uniform half _AnimPowerY;
                                    uniform half _AnimPowerZ;
                                    uniform half _AnimOffsetX;
                                    uniform half _AnimOffsetY;
                                    uniform half _AnimOffsetZ;
                                    uniform half _FlowSpeedX;
                                    uniform half _FlowSpeedY;

                                    //unity defined variables
                                    uniform half4 _LightColor0;

                                    //base input structures
                                    struct vertexInput
                                    {
                                        half4 vertex : POSITION;
                                        half3 normal : NORMAL;
                                        half4 texcoord : TEXCOORD0;
                                        half4 tangent : TANGENT;
                                    };

                                    struct vertexOutput
                                    {
                                        half4 pos : SV_POSITION;
                                        fixed3 normalWorld : TEXCOORD0;
                                        fixed4 lightDir : TEXCOORD1;
                                        fixed3 viewDir : TEXCOORD2;
                                        fixed3 tangentWorld : TEXCOORD3;
                                        fixed3 binormalWorld : TEXCOORD4;
                                        half4 tex : TEXCOORD5;
                                        half4 offsets : TEXCOORD6;
                                    };


                                    //vertex function
                                    vertexOutput vert(vertexInput v)
                                    {
                                        vertexOutput o;

                                        //Passing along the vertex's texture coordinate for use in the fragment function.
                                        o.tex = v.texcoord;
                                        //texture movement
                                        //offsets .x = texture x axis, .y = texture y axis, .z = normal x axis, .w = normal y axis
                                        o.offsets.x = _MainTex_ST.z + (_Time.x * _FlowSpeedX);
                                        o.offsets.y = _MainTex_ST.w + (_Time.x * _FlowSpeedY);
                                        o.offsets.z = _BumpMap_ST.z + (_Time.x * _FlowSpeedX);
                                        o.offsets.w = _BumpMap_ST.w + (_Time.x * _FlowSpeedY);
                                        //vertex animation
                                        half3 animOffset = half3(_AnimOffsetX, _AnimOffsetY, _AnimOffsetZ) * v.vertex.xyz;
                                        half3 animPower = half3(_AnimPowerX, _AnimPowerY, _AnimPowerZ);
                                        half4 newPos = v.vertex;
                                        newPos.xyz = newPos.xyz + sin(_Time.x * _AnimSpeed +(animOffset.x+animOffset.y+animOffset.z) * _AnimFreq) * animPower.xyz;

                                        //normalDirection
                                        o.normalWorld = normalize( mul( half4( v.normal, 0.0 ), unity_WorldToObject ).xyz );
                                        o.tangentWorld = normalize(mul(unity_ObjectToWorld,v.tangent).xyz);
                                        o.binormalWorld = normalize(cross(o.normalWorld, o.tangentWorld) * v.tangent.w);

                                        //unity transform position
                                        o.pos = mul(UNITY_MATRIX_MVP, newPos);

                                        //world position
                                        half4 posWorld = mul(unity_ObjectToWorld, newPos);
                                        //view direction
                                        o.viewDir = normalize( _WorldSpaceCameraPos.xyz - posWorld.xyz );
                                        //light direction
                                        half3 fragmentToLightSource = _WorldSpaceLightPos0.xyz - posWorld.xyz;
                                        o.lightDir = fixed4(
                                            normalize( lerp(_WorldSpaceLightPos0.xyz , fragmentToLightSource, _WorldSpaceLightPos0.w) ),
                                            lerp(1.0 , 1.0/length(fragmentToLightSource), _WorldSpaceLightPos0.w)
                                        );

                                        return o;
                                    }

                                    //fragment functions
                                    fixed4 frag(vertexOutput i) : COLOR
                                    {
                                        //texture maps
                                        fixed4 tex = tex2D(_MainTex, i.tex.xy * _MainTex_ST.xy + i.offsets.xy);
                                        fixed4 texN = tex2D(_BumpMap, i.tex.xy * _BumpMap_ST.xy + i.offsets.zw);

                                        fixed3 localCoords = fixed3(2.0 * texN.ag - float2(1.0,1.0), _BumpDepth);
                                        half3x3 local2WorldTranspose = half3x3(i.tangentWorld, i.binormalWorld, i.normalWorld);
                                        fixed3 normalDir =  normalize(mul(localCoords, local2WorldTranspose));

                                        //CUBEMAP REFLECTION DIRECTION CALCULATION
                                        //	refract can be used in place of reflect with a third argument being 1 divided by the refraction index
                                        //	i.e. refract(i.viewDir, i.normalDir, 1 / 1.333);
                                        float3 reflectDir = reflect(i.viewDir, normalDir);
                                        //Cube Reflection texture map
                                        fixed4 texCM = texCUBE(_Cube, -reflectDir);
                                        //dot product
                                        fixed nDotL = saturate(dot(normalDir, i.lightDir.xyz));

                                        //lighting
                                        fixed3 diffuseReflection = i.lightDir.w * _LightColor0.xyz * nDotL;

                                        fixed3 specularReflection = i.lightDir.w * _SpecColor.xyz * max(0.0, dot(normalDir, i.lightDir.xyz)) *
                                         pow( max(0.0, dot(reflect(-i.lightDir.xyz, normalDir), i.viewDir)), _Shininess);


                                        //final lighting calculation
                                        fixed3 lightFinal = (texCM * _CubemapMult) + diffuseReflection + (specularReflection *  _SpecIntensity) + UNITY_LIGHTMODEL_AMBIENT.xyz;

                                        //return value
                                        return fixed4(lightFinal * _Color.xyz * tex.xyz, _Color.a);
                                    }

                                    ENDCG
                                }


                            }
                            Fallback "Diffuse"
                        }
                    
Copyright © Nicholus Huber / Crux Gaming