Search Unity

Are there good example of GameManager script?

Discussion in 'Scripting' started by leegod, Mar 28, 2015.

  1. leegod

    leegod

    Joined:
    May 5, 2010
    Posts:
    2,476
    You know, most games have scenes or gameplay components like MainMenu, Actual game scene1, 2,,,, Option, Single mode, Multi mode, Other mode,,,

    So maybe GameManager script which does not destroyed and alive between scenes, and manage all of them.

    I had made this myself, but I want to know more decent scripting way. More neat, more re-usable, more easy-to-remember when see own code at later, etc

    Are there good example about scene & component organization method and code?

    Thanks.
     
  2. tobad

    tobad

    Joined:
    Mar 12, 2015
    Posts:
    90
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,539
    So here in my spacepuppy framework I have my Scene namespace:
    https://code.google.com/p/spacepuppy-unity-framework/source/browse/#svn/trunk/SpacepuppyBase/Scenes

    In it are some generalized base classes/interfaces for implementing what you're talking about.

    I break it into 3 parts.

    Scenes - what represents the actual scene being loaded. Basically it's a wrapper around the 'string' based scene loading the unity has built in. I prefer an object model to strings, so this wrapper exists for that purpose. The structure also allows compositing scenes together into a single scene represented as a single object.
    https://code.google.com/p/spacepupp...e/browse/trunk/SpacepuppyBase/Scenes/Scene.cs


    SceneBehaviour - This is the behaviour of the scene. It deals with selecting what actual scene gets loaded, what happens while its loaded, all sort of stuff.
    https://code.google.com/p/spacepupp...runk/SpacepuppyBase/Scenes/ISceneBehaviour.cs


    SceneManager - this is the singleton that acts as the central point where you can transition scenes. It's a singleton so that it's easy to access, as well as because it's the way unity operates... unity only has one stage upon which the scene runs... so the idea of multiple scenemanagers is mostly useless (well of course I could think of a multi-scene managing system that would simulate multiple stages on one... but that's fairly complicated as well as unnecessary complexity. If I wanted to write that I'd make it independent of this).
    https://code.google.com/p/spacepupp...e/trunk/SpacepuppyBase/Scenes/SceneManager.cs


    You'll notice my SceneManager inherits from a class called Singleton. All my singletons inherit from this class, it acts as boilerplate for singletons so that I don't have to write the code over and over.
    https://code.google.com/p/spacepuppy-unity-framework/source/browse/trunk/SpacepuppyBase/Singleton.cs




    With this all a single scenebehaviour may be very simple. For example this is my ErrorScreen:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. using com.spacepuppy;
    6. using com.spacepuppy.Scenes;
    7. using com.spacepuppy.Utils;
    8.  
    9. namespace com.apoc.Scenes.DebugScene
    10. {
    11.  
    12.     public class ErrorScreen : SPComponent, ISceneBehaviour
    13.     {
    14.  
    15.         public IProgressingYieldInstruction LoadScene()
    16.         {
    17.             var scene = new SimpleScene("ErrorScene");
    18.             return scene.LoadAsync();
    19.         }
    20.  
    21.         public void BeginScene()
    22.         {
    23.  
    24.         }
    25.  
    26.         public void EndScene()
    27.         {
    28.  
    29.         }
    30.  
    31.     }
    32.  
    33. }
    34.  

    All it really does is load a specific 'error' scene and displays it. This is the screen I go to if the game goes into a faulted state.

    Where as this is my LevelBehaviour:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5.  
    6. using com.spacepuppy;
    7. using com.spacepuppy.Cameras;
    8. using com.spacepuppy.Scenes;
    9. using com.spacepuppy.StateMachine;
    10. using com.spacepuppy.Utils;
    11.  
    12. using com.apoc.Ui;
    13. using com.apoc.Role;
    14. using com.apoc.Scenario.Generic;
    15.  
    16. namespace com.apoc.Scenes.Levels
    17. {
    18.  
    19.     public abstract class LevelBehaviour : SPNotifyingComponent, ISceneBehaviour
    20.     {
    21.  
    22.         #region Fields
    23.  
    24.         [System.NonSerialized()]
    25.         private ComponentStateMachine<AbstractLevelState> _stateManager;
    26.  
    27.         private ICamera _mainCamera;
    28.         private CameraMovementController _cameraMovementController;
    29.         private PlayerEntity _currentPlayerEntity;
    30.         private i_PlayerCheckPoint _lastCheckPoint = null;
    31.  
    32.         #endregion
    33.  
    34.         #region CONSTRUCTOR
    35.  
    36.         protected override void Awake()
    37.         {
    38.             base.Awake();
    39.  
    40.             _stateManager = new ComponentStateMachine<AbstractLevelState>(this.gameObject);
    41.             this.AddComponent<GamePlayState>();
    42.             this.AddComponent<GamePausedState>();
    43.             this.AddComponent<GameCutSceneState>();
    44.         }
    45.  
    46.         protected override void OnStartOrEnable()
    47.         {
    48.             base.OnStartOrEnable();
    49.  
    50.             _stateManager.StateChanged -= this._stateManager_StateChanged;
    51.             _stateManager.StateChanged += this._stateManager_StateChanged;
    52.         }
    53.  
    54.         protected override void OnDisable()
    55.         {
    56.             base.OnDisable();
    57.  
    58.             _stateManager.StateChanged -= this._stateManager_StateChanged;
    59.         }
    60.  
    61.         #endregion
    62.  
    63.         #region Properties
    64.  
    65.         public ITypedStateMachine<AbstractLevelState> States { get { return _stateManager; } }
    66.  
    67.         public ICamera MainCamera { get { return _mainCamera; } }
    68.  
    69.         public CameraMovementController MainCameraMovementController { get { return _cameraMovementController; } }
    70.  
    71.         public PlayerEntity CurrentPlayerEntity { get { return _currentPlayerEntity; } }
    72.  
    73.         public i_PlayerCheckPoint LastCheckPoint { get { return _lastCheckPoint; } }
    74.  
    75.         public bool GamePlayActive { get { return _stateManager.Current is GamePlayState; } }
    76.  
    77.         public bool Paused { get { return _stateManager.Current is GamePausedState; } }
    78.  
    79.         public bool InCutscene { get { return _stateManager.Current is GameCutSceneState; } }
    80.  
    81.         #endregion
    82.  
    83.         #region Methods
    84.  
    85.         protected abstract IProgressingYieldInstruction LoadScene();
    86.  
    87.         protected virtual IProgressingYieldInstruction ReloadScene()
    88.         {
    89.             return this.LoadScene();
    90.         }
    91.  
    92.         private void DoBeginScene()
    93.         {
    94.             //Code the MUST be ran before BeginScene is called. Otherwise any child classes may not be prepared in time.
    95.      
    96.             _mainCamera = CameraManager.Main;
    97.             if (_mainCamera == null)
    98.             {
    99.                 throw new System.InvalidOperationException("A camera of name 'MainCamera' must exist in the scene.");
    100.             }
    101.  
    102.             _cameraMovementController = _mainCamera.gameObject.GetComponent<CameraMovementController>();
    103.             if(_cameraMovementController == null)
    104.             {
    105.                 _cameraMovementController = LevelBehaviour.AddDefaultCameraMovementController(_mainCamera.gameObject);
    106.             }
    107.  
    108.  
    109.             //Register Notification Listeners
    110.             Notification.RemoveGlobalObserver<PlayerEntity.PlayerDied>(this.OnGlobalPlayerDiedNotification);
    111.             Notification.RegisterGlobalObserver<PlayerEntity.PlayerDied>(this.OnGlobalPlayerDiedNotification);
    112.  
    113.             this.BeginScene();
    114.         }
    115.  
    116.         protected virtual void BeginScene()
    117.         {
    118.             this.SpawnPlayerAndStartGamePlay();
    119.  
    120.             this.Invoke(() =>
    121.             {
    122.                 if (_cameraMovementController != null) _cameraMovementController.SnapToTarget();
    123.             }, 0.1f);
    124.         }
    125.  
    126.  
    127.         private void DoEndScene()
    128.         {
    129.             Notification.RemoveGlobalObserver<PlayerEntity.PlayerDied>(this.OnGlobalPlayerDiedNotification);
    130.  
    131.             this.EndScene();
    132.  
    133.             SPTime.Normal.Paused = false;
    134.         }
    135.  
    136.         protected virtual void EndScene()
    137.         {
    138.         }
    139.  
    140.         public void SpawnPlayerAndStartGamePlay()
    141.         {
    142.             this.States.ChangeState<GamePlayState>();
    143.             var startPoint = GameObject.Find("PlayerStartPoint");
    144.             if (startPoint == null) throw new System.InvalidOperationException("Level must contain a PlayerStartPoint.");
    145.             var checkpoint = startPoint.GetComponent<i_PlayerCheckPoint>();
    146.             if (checkpoint == null) throw new System.InvalidOperationException("Level must contain a PlayerStartPoint.");
    147.  
    148.             this.SetCheckPoint(checkpoint);
    149.  
    150.             this.SpawnPlayerAtLastCheckpoint();
    151.         }
    152.  
    153.         public void SpawnPlayerAtLastCheckpoint()
    154.         {
    155.             if (_lastCheckPoint == null) throw new System.InvalidOperationException("No Checkpoint Present.");
    156.  
    157.             _currentPlayerEntity = Game.SpawnPlayer(_lastCheckPoint.transform.position, Quaternion.LookRotation(Vector3.right, Vector3.up));
    158.             if (_cameraMovementController != null) _cameraMovementController.TetherTarget = _currentPlayerEntity.transform;
    159.         }
    160.  
    161.         public void SetCheckPoint(i_PlayerCheckPoint checkpoint)
    162.         {
    163.             _lastCheckPoint = checkpoint;
    164.         }
    165.  
    166.         public void ReturnToGamePlay()
    167.         {
    168.             if (this.GamePlayActive) return;
    169.  
    170.             _stateManager.ChangeState<GamePlayState>();
    171.         }
    172.  
    173.         public void Pause()
    174.         {
    175.             if (this.Paused) return;
    176.  
    177.             _stateManager.ChangeState<GamePausedState>();
    178.         }
    179.  
    180.         public void StartCutscene()
    181.         {
    182.             if (this.InCutscene) return;
    183.  
    184.             var state = _stateManager.GetState<GameCutSceneState>();
    185.             _stateManager.ChangeState(state);
    186.         }
    187.  
    188.         #endregion
    189.  
    190.         #region Event Handlers
    191.  
    192.         private void _stateManager_StateChanged(object sender, StateChangedEventArgs<AbstractLevelState> e)
    193.         {
    194.             if (e.FromState != null)
    195.             {
    196.                 e.FromState.OnExitState(e.ToState);
    197.             }
    198.             if (e.ToState != null)
    199.             {
    200.                 e.ToState.OnEnterState(e.FromState);
    201.             }
    202.         }
    203.  
    204.         private void OnGlobalPlayerDiedNotification(PlayerEntity.PlayerDied n)
    205.         {
    206.             if (n.Entity == this.CurrentPlayerEntity)
    207.             {
    208.                 //this.Invoke(() =>
    209.                 //{
    210.                 //    this.SpawnPlayerAtLastCheckpoint();
    211.                 //}, Game.Settings.PlayerRespawnWaitDuration);
    212.  
    213.                 this.StartRadicalCoroutine(this.OnGlobalPlayerDiedNotification_Routine());
    214.             }
    215.         }
    216.  
    217.         private System.Collections.IEnumerator OnGlobalPlayerDiedNotification_Routine()
    218.         {
    219.             yield return new WaitForSeconds(Game.Settings.PlayerRespawnWaitDuration);
    220.  
    221.             if(Game.Settings.ReloadLevelOnDeath)
    222.             {
    223.                 yield return this.ReloadScene();
    224.             }
    225.  
    226.             this.SpawnPlayerAtLastCheckpoint();
    227.  
    228.             yield return null;
    229.  
    230.             if (_cameraMovementController != null) _cameraMovementController.SnapToTarget();
    231.         }
    232.  
    233.         protected virtual void Update()
    234.         {
    235.             if (_stateManager.Current != null) _stateManager.Current.UpdateState();
    236.         }
    237.  
    238.         #endregion
    239.  
    240.         #region ILevelBehaviour Interface
    241.  
    242.         IProgressingYieldInstruction ISceneBehaviour.LoadScene()
    243.         {
    244.             return this.LoadScene();
    245.         }
    246.  
    247.         void ISceneBehaviour.BeginScene()
    248.         {
    249.             this.DoBeginScene();
    250.         }
    251.  
    252.         void ISceneBehaviour.EndScene()
    253.         {
    254.             this.DoEndScene();
    255.         }
    256.  
    257.         #endregion
    258.  
    259.         #region Static Utils
    260.  
    261.         public static CompositeScene CreateLevelScene(string sceneName, Scene primaryScene, params Scene[] extraScenes)
    262.         {
    263.             if (primaryScene == null) throw new System.ArgumentNullException("primaryScene");
    264.  
    265.             var result = new CompositeScene(sceneName);
    266.             result.Add(primaryScene);
    267.             result.SetPrimaryScene(primaryScene);
    268.             for (int i = 0; i < extraScenes.Length; i++)
    269.             {
    270.                 result.Add(extraScenes[0]);
    271.             }
    272.  
    273.             //result.Add(new SimpleScene("InGameHUD"));
    274.  
    275.             return result;
    276.         }
    277.  
    278.         public static CameraMovementController AddDefaultCameraMovementController(GameObject camera)
    279.         {
    280.             if (camera == null) throw new System.ArgumentNullException("camera");
    281.  
    282.             var controller = camera.AddComponent<CameraMovementController>();
    283.             controller.TargetCamera = camera.transform;
    284.             controller.UseUpdateSequence = UpdateSequence.FixedUpdate;
    285.  
    286.             var state = controller.AddComponent<com.apoc.Entities.Cameras.SimpleCameraTether>();
    287.             state.Offset = new Vector3(0f, 1.5f, -10f);
    288.             state.LerpSpeed = 5f;
    289.  
    290.             controller.StartingCameraStyle = state;
    291.             controller.States.ChangeState(state);
    292.  
    293.             return controller;
    294.         }
    295.  
    296.         #endregion
    297.  
    298.     }
    299.  
    300. }
    301.  

    This sort of scene represents a level in the game, so it does quite a bit. Not only does it do quite a bit, but it too actually has a statemachine to change the state of the scene itself (ingame, pause, cutscene).

    Here you can see what happens in those states. Right now, not very much.

    GamePlayState - this really just waits for you to press the pause button, which transitions the level to the paused state.
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Linq;
    4.  
    5. using com.spacepuppy;
    6. using com.spacepuppy.Utils;
    7.  
    8. using com.apoc.Ui;
    9.  
    10. namespace com.apoc.Scenes.Levels
    11. {
    12.  
    13.     public class GamePlayState : AbstractLevelState
    14.     {
    15.  
    16.         #region Fields
    17.  
    18.         #endregion
    19.  
    20.         #region Properties
    21.  
    22.         #endregion
    23.  
    24.         #region Methods
    25.  
    26.         #endregion
    27.  
    28.         #region AbstractLevelState Abstract Overrides
    29.  
    30.         public override void OnEnterState(AbstractLevelState lastState)
    31.         {
    32.  
    33.         }
    34.  
    35.         public override void OnExitState(AbstractLevelState nextState)
    36.         {
    37.  
    38.         }
    39.  
    40.         public override void UpdateState()
    41.         {
    42.             var inputDevice = Game.InputManager.FirstOrDefault() as ApocPlayerInputDevice;
    43.             if (inputDevice == null) return;
    44.             var input = inputDevice.GetCurrentState();
    45.  
    46.             if (input.Pause == spacepuppy.Ui.ButtonState.Down)
    47.             {
    48.                 this.Level.Pause();
    49.             }
    50.         }
    51.  
    52.         #endregion
    53.  
    54.  
    55.  
    56.  
    57. #if UNITY_EDITOR
    58.         private void Update()
    59.         {
    60.             if (Input.GetButtonDown("Kick"))
    61.             {
    62.                 var logEntries = System.Type.GetType("UnityEditorInternal.LogEntries,UnityEditor.dll");
    63.                 var clearMethod = logEntries.GetMethod("Clear", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
    64.                 clearMethod.Invoke(null, null);
    65.             }
    66.         }
    67. #endif
    68.  
    69.     }
    70.  
    71. }
    72.  

    GamePausedState - when enters it just sets all the stuff that needs to be paused to pause (the time scale, audio, etc) as well as dispatches a global notification for entities in the level to react to.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5.  
    6. using com.spacepuppy;
    7. using com.spacepuppy.Audio;
    8. using com.spacepuppy.Utils;
    9.  
    10. using com.apoc.Ui;
    11.  
    12. namespace com.apoc.Scenes.Levels
    13. {
    14.  
    15.     public class GamePausedState : AbstractLevelState
    16.     {
    17.    
    18.         #region Fields
    19.  
    20.         private bool _active;
    21.  
    22.         #endregion
    23.  
    24.         #region Properties
    25.  
    26.         #endregion
    27.  
    28.         #region Methods
    29.  
    30.         #endregion
    31.  
    32.         #region AbstractLevelState Abstract Overrides
    33.  
    34.         public override void OnEnterState(AbstractLevelState lastState)
    35.         {
    36.             _active = true;
    37.             SPTime.Normal.Paused = true;
    38.             AudioManager.Global.Pause();
    39.             Notification.PostNotification<GamePausedNotification>(this.Level, new GamePausedNotification(true), false);
    40.         }
    41.  
    42.         public override void OnExitState(AbstractLevelState nextState)
    43.         {
    44.             SPTime.Normal.Paused = false;
    45.             AudioManager.Global.UnPause();
    46.             _active = false;
    47.             Notification.PostNotification<GamePausedNotification>(this.Level, new GamePausedNotification(false), false);
    48.         }
    49.  
    50.         public override void UpdateState()
    51.         {
    52.             var inputDevice = Game.InputManager.FirstOrDefault() as ApocPlayerInputDevice;
    53.             if (inputDevice == null) return;
    54.             var input = inputDevice.GetCurrentState();
    55.  
    56.             if (input.Pause == spacepuppy.Ui.ButtonState.Down)
    57.             {
    58.                 this.Level.ReturnToGamePlay();
    59.             }
    60.         }
    61.  
    62.         #endregion
    63.  
    64.  
    65.         private void OnGUI()
    66.         {
    67.             if (!_active) return;
    68.  
    69.             var c = Color.black;
    70.             c.a = 0.5f;
    71.             GUI.color = c;
    72.             GUI.depth = int.MinValue;
    73.             GUI.DrawTexture(new Rect(0f, 0f, Screen.width, Screen.height), SPAssets.WhiteTexture);
    74.         }
    75.  
    76.     }
    77. }
    78.  

    Anyways... sure I'm not tutorializing all of it. Nor am I saying this is the best route of doing this. Main issue is probably because I really use all sorts of parts of my spacepuppy framework, like RadicalCoroutine, RadicalYieldInstruction, SPComponent, Notification, SPTween (I just wrote this one last week... I'm very pleased with it so far), etc... all of which is a framework designed very much with my own interests in mind for how an application should be structured. This is just how I do this... and I'm more than happy to let you take a look.
     
    Last edited: Mar 28, 2015
    andreyefimov2010, musaranya and tobad like this.
  4. leegod

    leegod

    Joined:
    May 5, 2010
    Posts:
    2,476
    @lordofduck Thanks for sharing your thought, codes.
    But it seems too hard, complicated. I can't understand well how it works at first glance.
    Why use abstract class and interface, #region?
     
  5. leegod

    leegod

    Joined:
    May 5, 2010
    Posts:
    2,476
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,539
    abstract class - this is a class that has some implementation in it, but can't be used directly. Instead it requires you to inherit from it to implement the extra stuff that is needed. This way you can have classes that are related in functionality, and code relevant to all of them is in one place. Scene is abstract in my example, this is because you can have different kinds of scenes, like SimpleScene (represents a single scene), CompositeScene (represents multiple scenes loaded as one).

    interface - this is similar to abstract class, but there is NO implementation of the type. It is just a contract that defines methods and properties that must be defined on the class. Again, this is so you can have types that are related. ISceneBehaviour is the interface in my example, your various scenes can implement this interface, all they must do is implement the 3 functions defined, and they can be used by the SceneManager.

    region - this is just something for organizing code. In your IDE, if you have a region, it puts a little +/- sign that when you click it hides the code up, or expands it back out.

    As for using the code I posted... you wouldn't necessarily just use that code, that's not why I posted it. It may be a bit over your head as I do not know what level programmer you are. But you can look into it and learn from it if you like... coding can be hard, but once you learn this kind of stuff, it gets easy. This is why I shared it... so you can learn. Not so you can just have it.

    Technically though, you don't really need to do much just to use it if you had my framework included in your project.

    If you did that, and you created a scene like my 'ErrorScreen' (which is a super simple scene behaviour). Then to load it from somewhere (say on a trigger enter), you'd just say:

    Code (csharp):
    1.  
    2. SceneManager.Instance.LoadScene<ErrorScreen>();
    3.  
     
  7. leegod

    leegod

    Joined:
    May 5, 2010
    Posts:
    2,476
    Thx, but why don't just implement class and method instead of using abstract, interface?
     
  8. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,539
    Then all the scenebehaviours wouldn't be of the same type.

    The SceneManager expects ISceneBehaviours.

    If you just implemented 'ErrorScreen' and 'MenuScreen' and 'LevelScreen'... how would the SceneManager know how to use them? They might have similar functions on them, but there is nothing saying they actually do have similar functions.

    This is called 'polymorphism'. It's one of the major parts of object oriented paradigm.
    http://en.wikipedia.org/wiki/Polymorphism_(computer_science)
    (specifically check out the 'suptyping' section...)


    It's the same reason you can treat your colliders as just 'Collider' and not have to figure out if they're SphereCollider or MeshCollider, unless you specifically need a SphereCollider.
     
    Last edited: Mar 29, 2015
  9. ensiferum888

    ensiferum888

    Joined:
    May 11, 2013
    Posts:
    317
    I had issues grasping the concept of interfaces before as well. It becomes really useful when you want to have the same functionality across different classes.

    For example, in my game I have three main components on my buildings that have an inventory in which you can take and drop items. StoreageScript, WorkScript and HouseScript.

    Each script has an array representing the amount of items in stock. If I didn't use interfaces, I would need three different methods to grab items or a switch case to determine if I'm dealing with a workplace, a house or a storeage barn. Instead all three implement the Iinventory interface. That way my classes do not care where they take or drop items from.

    So now instead of guessing, any of these will work:
    Code (CSharp):
    1. //just an array indicating which items to drop
    2. int[] itemsToDrop;
    3.  
    4. DropItemsAtLocation(citizen.workScript, itemsToDrop);
    5. DropItemsAtLocation(citizen.houseScript, itemsToDrop);
    6. DropItemsAtLocation(nearestStorage, itemsToDrop);
    All this works because the method's signature is:
    Code (CSharp):
    1. DropItemsAtLocation(Iinventory inventory, int[] itemsToDrop);
     
    chelnok likes this.
  10. leegod

    leegod

    Joined:
    May 5, 2010
    Posts:
    2,476
    @ensiferum888
    Thx, real game's coding needs example make it easy to understand why interface is needed.
     
  11. leegod

    leegod

    Joined:
    May 5, 2010
    Posts:
    2,476
    But you know because you made it, isn't it enough?
     
  12. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,539
    Well then every time I added a new scene type, I'd have to go into SceneManager and add support for it.

    Where as with the interface, when ever I create a new scene, I just implement what the interface says to implement, and the SceneManager can handle it.

    I'm getting the impression you still don't know what polymorphism is, I linked an article on it. Though it's a bit abstract in its definition.

    Here's the article from Microsoft describing it specifically in the context of C#:
    https://msdn.microsoft.com/en-us/library/ms173152.aspx
     
    chelnok likes this.
  13. leegod

    leegod

    Joined:
    May 5, 2010
    Posts:
    2,476
    Yes this make sense and helpful to understand. Thanks.