︎︎︎ Where Turtles Fly
Tags :
︎ PC Game
Status :
︎ In development
Role :
Game Programmer
︎ Collaboration with Andre Zakhya
Tags :
︎ PC Game
Status :
︎ In development
Role :
Game Programmer
︎ Collaboration with Andre Zakhya
︎ 2020 07
![]()

︎ Teaser
*the name of the project changed from “Sorry I drowned” to “Where Turtles Fly” before the teaser release.
*the name of the project changed from “Sorry I drowned” to “Where Turtles Fly” before the teaser release.
︎ Project Breakdown
. “ Where Turtles Fly ” is a PC Stealth Game Demo composed of 4 levels
. the architecture of the game aims to be scalable
the Demo is built with Unity HDRP and includes :
. state machine (game states)
. state machine (animation)
. save and load
. complex character controller with multiple abilities
. inventory
. GUI work
. cinematic camera
. particles systems
. shadergraph / VFX graph behaviours
. dynamic meshes
. “ Where Turtles Fly ” is a PC Stealth Game Demo composed of 4 levels
. the architecture of the game aims to be scalable
the Demo is built with Unity HDRP and includes :
. state machine (game states)
. state machine (animation)
. save and load
. complex character controller with multiple abilities
. inventory
. GUI work
. cinematic camera
. particles systems
. shadergraph / VFX graph behaviours
. dynamic meshes
︎︎︎ UML Diagram ![]()

︎ Cinematic Direction
. before of the making of the game preceded a conceptual design phase where the artist exposed his intent and the game has been conceived as a cinematic sequence
. before of the making of the game preceded a conceptual design phase where the artist exposed his intent and the game has been conceived as a cinematic sequence
︎︎︎ Visual Flow Chart Diagram ![]()

︎
. Early Prototype Demo : Game loop
. Early Prototype Demo : Game loop
︎ Character abilities
. the main character in the game has a wide range of abilities to be performed in the same scenes
. abilities are regulated through the mecanim system and successively retrieved and directed from the manager class
. the main character in the game has a wide range of abilities to be performed in the same scenes
. abilities are regulated through the mecanim system and successively retrieved and directed from the manager class
︎︎︎ Using the State Manager and the Mecanim Animation System![]()

︎ Shared Code Sample :
Character Climb
the character player inspector holds the following
. animator
. capsule Collider
. InputManager
. StateManager
(Handled mostly separately)
. climbing behavior
. climbing events
. InventoryPlayerManager
. the samples at the side focus on the climbing feature that is predominant in the third level of the game
Character Climb
the character player inspector holds the following
. animator
. capsule Collider
. InputManager
. StateManager
(Handled mostly separately)
. climbing behavior
. climbing events
. InventoryPlayerManager
. the samples at the side focus on the climbing feature that is predominant in the third level of the game
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; namespace SorryIDrowned.Climber { public class Climb : MonoBehaviour { #region VARIABLES // public variables [Header("References")] [Tooltip("Animator")] public Animator animator; [Tooltip("Is the character climbing")] public bool isClimbing; [Tooltip("Is the character position offset")] public float positionOffset = 0.4f; [Tooltip("How far from wall the character climbs")] public float offsetFromWall = 0.3f; [Tooltip("The speed multiplier of the climbing character ")] public float speedClimbMultiplier = 0.2f; [Tooltip("The speed of the climbing character ")] public float speedClimb = 3f; [Tooltip("The speed which the climbing character rotates ")] public float speedClimbRotate = 5f; // private variables // is the character in position ? bool inPosition; // is the character lerping ? bool isLerping; // tracking cycle of the climbing - counter float cycle; // tracking time in relation to the cycle float timeCycle; // starting position Vector3 startingPosition; // target position Vector3 targetPosition; // starting rotation Vector3 startingRotation; // target rotation Vector3 targetRotation; // orientation prediction Transform orientPosition; #endregion // #region MAIN METHODS // Start is called before the first frame update void Start() { Initialize(); } public void Update() { timeCycle = Time.deltaTime; ClimbCycle(timeCycle); } #endregion // #region FUNCTIONS /// <summary> /// the real climb cycle called in update /// </summary> public void ClimbCycle(float delta) { if (!inPosition) { GetPosition(); return; } // once we are in position correctly // we need to check if we are lerping if (!isLerping) { // resposible of finding the target position we want to go // get the input values float horizontal = Input.GetAxis("Horizontal"); float vertical = Input.GetAxis("Vertical"); float moveAmount = Mathf.Abs(horizontal) + Mathf.Abs(vertical); // find a position based on the current input Vector3 positionHorizontal = orientPosition.right * horizontal; Vector3 positionVertical = orientPosition.up * vertical; // normalized moving direction as result Vector3 moveDirection = (positionHorizontal + positionVertical).normalized; // can we move in that direction or not bool canMove = CanMove(moveDirection); if (!canMove || moveDirection == Vector3.zero) return; // if we can move cycle = 0; isLerping = true; startingPosition = transform.position; // set the new target position targetPosition = orientPosition.position; } else { // this section execute the movement from one point to another one cycle += timeCycle * speedClimb; if (cycle >1) { cycle = 1; isLerping = false; } Vector3 climbPosition = Vector3.Lerp(startingPosition, targetPosition, cycle); transform.position = climbPosition; transform.rotation = Quaternion.Slerp(transform.rotation, orientPosition.rotation, cycle * speedClimbRotate); } } /// <summary> /// finds out if you can move or not /// this works detecting solid around you as a character /// </summary> bool CanMove (Vector3 movingDirection) { // a new starting reference Vector3 origin = transform.position; // calculate a direction reference Vector3 direction = movingDirection; // calculate a distance reference float distance = positionOffset; // use them to calculate a ray RaycastHit hit; // LEFT-AND-RIGHT // // raycast forward if (Physics.Raycast(origin, direction, out hit, distance)) { return false; } // create a new origin origin += movingDirection * distance; direction = orientPosition.forward; // FRONT-AND-BACK // float distanceForward = 0.5f; if (Physics.Raycast(origin, direction, out hit, distance)) { // get the straight raycast and orient toward that direction orientPosition.position = TargetPositionWithOffset(origin, hit.point); orientPosition.rotation = Quaternion.LookRotation(-hit.normal); return true; } // if before we where checking where to move and if we could move // this section target specifcially the motion through corners origin += direction * distanceForward; direction = -Vector3.up; // UP-AND-DOWN // if (Physics.Raycast(origin, direction, out hit, distanceForward)) { // get the straight raycast and orient toward that direction // find angle between orient position and normal hit float angle = Vector3.Angle(orientPosition.up, hit.normal); if (angle < 50) { // get the straight raycast and orient toward that direction orientPosition.position = TargetPositionWithOffset(origin, hit.point); orientPosition.rotation = Quaternion.LookRotation(-hit.normal); return true; } } return false; } /// <summary> /// finds the surface to climb /// </summary> public void CheckForClimbingSurface() { // starting from our position Vector3 origin = transform.position; // lift it up some origin.y += 1.5f; // check frontal direction Vector3 direction = transform.forward; RaycastHit hit; // check if we are hitting something - climbing position if (Physics.Raycast (origin,direction, out hit, 1)) { orientPosition.position = TargetPositionWithOffset(origin, hit.point); ClimbingInitialization(hit); } } // initializes the climbing with raycast void ClimbingInitialization (RaycastHit hit) { // set the condition isClimbing = true; // setting up the orientation // it gets the opposite normal of the one we just hit orientPosition.transform.rotation = Quaternion.LookRotation(-hit.normal); // set the starting and target position startingPosition = transform.position; // https://docs.unity3d.com/ScriptReference/RaycastHit-point.html // apply a force to a rigidbody in the Scene at the point targetPosition = hit.point + (hit.normal * offsetFromWall); // reset cycle cycle = 0; // stil not in position inPosition = false; // crossfade current animation with climb animator.CrossFade("climb",2); } /// <summary> /// creates a reference for helping the orientation of the character /// </summary> public void Initialize() { // create a gameobject that coordinate the orientation // of the climber orientPosition = new GameObject().transform; orientPosition.name = "orientPosition"; CheckForClimbingSurface(); } /// <summary> /// get the initial position /// </summary> void GetPosition() { // starts the cycle cycle += timeCycle; if (cycle > 1) { cycle = 1; // set the climber in position inPosition = true; // enable the Inverse Kinematics } // find the coordinate of the target position Vector3 findTargetPosition = Vector3.Lerp(startingPosition, targetPosition, cycle); // align the new position transform.position = findTargetPosition; // transform.rotation = Quaternion.Slerp(transform.rotation, orientPosition.rotation, cycle * speedClimbRotate); } /// <summary> /// finds the target position also including the offset /// </summary> Vector3 TargetPositionWithOffset(Vector3 originPos, Vector3 targetPos) { Vector3 direction = originPos = targetPos; direction.Normalize(); Vector3 offset = direction * offsetFromWall; return targetPos + offset; } #endregion } } |
︎ VFX Graph : PointClouds workflow
Before Unity
. the pointcloud file need to have vertex informations. .ply files are good
. Pointcloud to Mesh in Mesh Lab
In Unity
. convert .dae file as pCache
. plug the file in the component using position and map info
Before Unity
. the pointcloud file need to have vertex informations. .ply files are good
. Pointcloud to Mesh in Mesh Lab
In Unity
. convert .dae file as pCache
. plug the file in the component using position and map info
︎︎︎ VFX Graph point cloud : Workflow

︎
. VFX Graph Demo : PointCloud
. VFX Graph Demo : PointCloud
︎ VFX Graph : Drones
︎ The goal for this effect is spawning a large amount of mesh-particles that can collide with a set collider in the scene
. as July 2020, VFX Graph is still in the development phase. Physics - collision events are not available retrievable from the graph
. a workaround for this limitation has been merging two systems of flocks, one GPU based and one CPU based
︎ The goal for this effect is spawning a large amount of mesh-particles that can collide with a set collider in the scene
. as July 2020, VFX Graph is still in the development phase. Physics - collision events are not available retrievable from the graph
. a workaround for this limitation has been merging two systems of flocks, one GPU based and one CPU based
︎︎︎ VFX Graph point cloud : Workflow

︎
. CPU + GPU flock demo : Drones
. CPU + GPU flock demo : Drones
︎ Shared Code Sample :
Drone’s attack
[fockObject]
. the behavior that regulates the motion of the drone’s group is inspired by conventional flock behaviors, but with some tweaks
. specifically the major change comes down to location of the goal position of the flock and the attacking behavior
. there are two scripts, one attached to every flocking object and one manager that organizes them
Drone’s attack
[fockObject]
. the behavior that regulates the motion of the drone’s group is inspired by conventional flock behaviors, but with some tweaks
. specifically the major change comes down to location of the goal position of the flock and the attacking behavior
. there are two scripts, one attached to every flocking object and one manager that organizes them
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEngine; using UnityStandardAssets.Utility; public class FlockObject : MonoBehaviour { #region VARIABLES // the average direction that the object is headed to Vector3 averageHeading; // the average position that the object has Vector3 averagePosition; // reference to the manager FlockManager manager; // counter reference private float timeCount = 0.0f; // object speed private float speed; // bool to track the turn motion bool turning; // bool to attack target bool attacking; // number of frames to completely interpolate between the 2 positions public int interpolationFramesCount = 45; int elapsedFrames = 0; #endregion #region MAIN METHODS void Start() { // assigning references manager = GameObject.FindGameObjectWithTag("DroneManager").GetComponent<FlockManager>(); attacking = manager.attack; } void Update() { // Ccheck the bool in the manager attacking = manager.attack; if (attacking) { Debug.Log("attack"); // Define a target position above and behind the target transform float step = manager.speedAttack * Time.deltaTime; transform.position = Vector3.MoveTowards(transform.position, manager.goalPos.position, step); } else { // define the turning state if (Vector3.Distance(transform.position, manager.goalPos.position) >= manager.boundSize) { turning = true; } else { turning = false; } if (turning) { // define rotation Vector3 direction = manager.goalPos.position - transform.position; direction.y = 0; transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), manager.rotationSpeed * Time.deltaTime); // } else { if (Random.Range(0, manager.flockingRate) < 1) { FlockRules(); } transform.Translate(0, 0, Time.deltaTime * manager.speed); } transform.Translate(0, 0, Time.deltaTime * manager.speed); } } #endregion #region DEFINE BEHAVIOR void FlockRules() { // get the "flockers" GameObject[] gos; gos = manager.allPrefab; // set initially // get reference for center of the flocking group Vector3 vCenter = manager.goalPos.position; // avoiding neighbours Vector3 vAvoid = manager.goalPos.position; // get group speed float gSpeed = manager.groupSpeed; // get goal position Vector3 goalPos = manager.goalPos.position; // float dist; // int groupSize = 0; foreach (GameObject go in gos) { // we are looking at other flockers - not us if (go != this.gameObject) { // distance between us and them dist = Vector3.Distance(go.transform.position, this.transform.position); // if we are close enough - we flock if (dist <= manager.neighbourDistance) { // average center of the group defined vCenter += go.transform.position; groupSize++; // if the distance is less than avoidance we are about to collide if (dist < manager.neighbourAvoidance) { // let's avoid vAvoid = vAvoid + (this.transform.position - go.transform.position); } // increase velocity in our total speed and set an average gSpeed = gSpeed + manager.speed; } } } // if the fish is in a group if (groupSize > 0) { // calculate average center of group vCenter = vCenter / groupSize + (goalPos - this.transform.position); // calculate average speed of group gSpeed = gSpeed / groupSize; // use the center of the group - add the vector away from anybody we could hit // minus our position // it gives s the direction we need to turn into Vector3 direction = (vCenter + vAvoid) - transform.position; // if direction is no zero if (direction != Vector3.zero) { // change direction - rotate transform.rotation = Quaternion.Slerp( transform.rotation, Quaternion.LookRotation(direction), manager.rotationSpeed * Time.deltaTime); } } } #endregion } |
︎ Code Sample :
Drone’s attack
[fockManager]
Drone’s attack
[fockManager]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class FlockManager : MonoBehaviour { #region VARIABLES [Tooltip("Prefab that is flocking")] public GameObject Prefab; [Tooltip("How many prefabs are flocking ?")] public static int numPrefab = 200; [Tooltip("Bounding box size containing the flocking objects")] public int boundSize; [Tooltip("The perpetual goal position for the flocking elements")] public Transform goalPos; // associated externally [Tooltip("Speed of the flocking object")] public float speed = 0.002f; [Tooltip("Rotation Speed of the flocking object")] public float rotationSpeed = 4.0f; [Tooltip("Neighbour distance for flocking")] public float neighbourDistance = 3.0f; [Tooltip("Neighbour threeshold for Avoidance")] public float neighbourAvoidance = 1.0f; [Tooltip("How often is flocking")] public float flockingRate = 5.0f; [Tooltip("The speed which the group runs, if not used Range")] public float groupSpeed = 0.1f; [Tooltip("MinSpeedValue")] public float speedRangeLow = 1f; [Tooltip("MaxSpeedValue")] public float speedRangeHigh = 5f; // Drone attack section [Tooltip("AttackTime01")] public float attackTime1 = 2f; [Tooltip("AttackTime02")] public float attackTime2 = 20f; [Tooltip("Speed Attack")] public float speedAttack = 20f; [Tooltip("Attack?")] public bool attack; // Keeping track of all the objects [Tooltip("List Of all teh objects")] public GameObject[] allPrefab = new GameObject[numPrefab]; #endregion #region MAIN METHODS void Start() { // starts a counter that keeps track when the drone prefabs // should be attacking StartCoroutine(AttackTiming()); // define the speed as a range of values speed = Random.Range(speedRangeLow, speedRangeHigh); // for now set the attack on false attack = false; // for (int i = 0; i < numPrefab; i++) { // setting the area where the drone flock is performing Vector3 pos = new Vector3(Random.Range(-boundSize, boundSize), Random.Range(-boundSize, boundSize), Random.Range(-boundSize, boundSize)); // instantiate the prefabs in those random pos allPrefab[i] = (GameObject)Instantiate(Prefab, pos, Quaternion.identity); } } void Update() { // do not update always if (Random.Range(0, 100000) < 50) { goalPos.position = new Vector3(Random.Range(-boundSize, boundSize), Random.Range(-boundSize, boundSize), Random.Range(-boundSize, boundSize)); } } #endregion #region COROUTINES // the coroutine defining the attack IEnumerator AttackTiming() { attack = false; yield return new WaitForSeconds(attackTime1); attack = true; yield return new WaitForSeconds(0.3f); attack = false; yield return new WaitForSeconds(attackTime2); StartCoroutine(AttackTiming()); } #endregion } |
︎
. Game-Demo Footages
. Game-Demo Footages
︎︎︎ 07.01.2020
︎ Shared Code Sample :
Game Manager Structure
Game Manager Structure
. the manager is easily accessible and a lot of elements can be interchanged by the designer.
. game manager sample structure
. game manager sample structure
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 | using System.Collections; using System.Collections.Generic; using UnityEditor.PackageManager; using UnityEngine; using UnityEngine.UI; using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.Video; // note // GameManager.cs. handles the addiction and subtractions of scenes in relation with the // advancing of the game / level namespace SorryIDrowned.Managers { public class ManagerUpdate : Singleton<GameManager> { #region VARIABLES // setting up a generic delegate type might be better eventually public static GameManager Instance { get; private set; } [Header("Booleans")] // set up the pause / not pause bool [Tooltip("Check if Game is Paused")] public bool GameIsPaused = false; [SerializeField] private bool gameIsPaused = false; public bool GameIsPaused { get => gameIsPaused; // private set { // if (gameIsPaused != value) OnGameIsPausedSwitched?.Invoke(); // gameIsPaused = value; } } public delegate void OnGameIsPausedSwitchedMethod(); public OnGameIsPausedSwitchedMethod OnGameIsPausedSwitched { get; set; } // set up the Gameplay / not Gameplay bool [Tooltip("Check if Game is Gameplay")] public bool GameIsGameplay = false; [SerializeField] private bool gameIsGameplay = false; public bool GameIsGameplay { get => gameIsGameplay; // private set { // if (gameIsGameplay != value) OnGameIsGameplaySwitched?.Invoke(); // gameIsGameplay = value; } } public delegate void OnGameIsGameplaySwitchedMethod(); public OnGameIsGameplaySwitchedMethod OnGameIsGameplaySwitched { get; set; } // [SerializeField, Tooltip("If you would like saving in dev mode - set it false")] private bool enableSave = true; [SerializeField, Tooltip("Reference the Player load/save script")] private Player player; [SerializeField, Tooltip("Reference the Scene for the developer mode switch")] private int developerSceneCounter = 0; [SerializeField, Tooltip("Loading time standard")] private int loadingTime = 3f; // set the singleton private static GameManager S { get { return _S; } set { if (_S != null) { // Destroy unnecessary one eventually Debug.LogError("A second GameManager exists in the scene!"); return; } _S = value; } } // game states // setting up an Enumerator to split level transitions in phases public enum eGameManagerState { entryPanel, developerMode, pausePanel, pausePanelSettings, aboutPanel, quitLastChoicePanel, cinematicTrailer, loadingPanel, gameplay, panelTitle, END // for getting the length } // the field that can be set in the inspector - most likely for debugging and tracking [Header("Game State Progress")] [SerializeField] public string currentLevelName = ""; [SerializeField] public int currentLevelNum = 0; [SerializeField] public eGameManagerState state; #endregion // #region MAIN METHODS /// <summary> /// Checks the level and defines variables. /// </summary> private void Start() { CheckLevel(); // set this class as singleton S = this; // set what is keeping track of the level currentLevelNum = player.level; currentLevelName = "Level " + currentLevelNum; // setting the state as entry panel state = eGameManagerState.entryPanel; } /// <summary> /// Running methods and level counter update /// </summary> private void Update() { currentLevelNum = player.level; currentLevelName = "Level " + currentLevelNum; // perpertual method PauseMenu(); // to temporarily save and test in developer mode if (state == eGameManagerState.developerMode && enableSave == false) player.level = developerSceneCounter; } #endregion // #region ADVANCEMENT FUNCTIONS /// <summary> /// Method executed during button press - new game /// </summary> // reference coroutine for protection private Coroutine loadSceneAndSetActiveGameCoroutine; private Coroutine loadSceneAndSetActiveGameContinueCoroutine; private Coroutine loadPreviousSceneSetActiveCoroutine; private Coroutine loadNextSceneSetActiveCoroutine; private Coroutine reloadScenesForgameplayCoroutine; private Coroutine reloadScenesForGameplayModeCoroutine; // public void NewGame() { if (loadNewSceneAndSetActiveCoroutine) { StopCoroutine(loadNewSceneAndSetActiveCoroutine); loadNewSceneAndSetActiveCoroutine = null; } // specific coroutine for new game loadNewSceneAndSetActiveCoroutine = StartCoroutine(LoadNewSceneAndSetActive()); } /// <summary> /// Method executed during button press - continue /// We are loading from the external save document /// </summary> public void Continue() { // load stats first player.LoadWithoutPlayer(); // coroutine check if (loadSceneAndSetActiveGameContinueCoroutine) { StopCoroutine(loadSceneAndSetActiveGameContinueCoroutine); loadSceneAndSetActiveGameContinueCoroutine = null; } // start coroutine loadSceneAndSetActiveGameContinueCoroutine = StartCoroutine(LoadSceneAndSetActiveGameContinue()); } /// <summary> /// Method executed during button press - developer mode - previous scene /// </summary> public void PreviousScene() { // coroutine check if (loadPreviousSceneSetActiveCoroutine) { StopCoroutine(loadPreviousSceneSetActiveCoroutine); loadPreviousSceneSetActiveCoroutine = null; } // start coroutine loadPreviousSceneSetActiveCoroutine = StartCoroutine(LoadPreviousSceneSetActive()); // StartCoroutine(CheckForLevelCoroutine()); } /// <summary> /// Method executed during button press - developer mode/gameplay mode - next scene /// </summary> public void NextScene() { if (state == GameManager.eGameManagerState.developerMode) { // coroutine check if (loadNextSceneSetActiveCoroutine) { StopCoroutine(loadNextSceneSetActiveCoroutine); loadNextSceneSetActiveCoroutine = null; } // start coroutine loadNextSceneSetActiveCoroutine = StartCoroutine(LoadNextSceneSetActive()); } if (state == GameManager.eGameManagerState.gameplay) { // coroutine check if (loadSceneAndSetActiveGameCoroutine) { StopCoroutine(loadSceneAndSetActiveGameCoroutine); loadSceneAndSetActiveGameCoroutine = null; } // start coroutine loadSceneAndSetActiveGameCoroutine = StartCoroutine(LoadSceneAndSetActiveGame()); // save player.SavePlayer(); } // StartCoroutine(CheckForLevelCoroutine()); } /// <summary> /// Method executed during the end of the game /// </summary> public void EndGame() { if (state == eGameManagerState.developerMode) // coroutine check if (reloadScenesForgameplayCoroutine) { StopCoroutine(reloadScenesForgameplayCoroutine); reloadScenesForgameplayCoroutine = null; } // start coroutine reloadScenesForgameplayCoroutine = StartCoroutine(ReloadScenesForgameplay()); // else if (state == eGameManagerState.gameplay) // coroutine check if (reloadScenesForGameplayModeCoroutine) { StopCoroutine(reloadScenesForGameplayModeCoroutine); reloadScenesForGameplayModeCoroutine = null; } // start coroutine reloadScenesForGameplayMode = StartCoroutine(ReloadScenesForGameplayMode()); } #endregion // #region RESUME-PAUSE-QUIT public void Resume() { OnGameIsPausedSwitched(); OnGameIsGameplaySwitched(); // Time.timeScale = 1; } public void PauseMenu() { if (Input.GetKeyDown(KeyCode.Escape)) { // pause works only in gameplay mode if (state == eGameManagerState.gameplay) { OnGameIsPausedSwitched(); OnGameIsGameplaySwitched(); // Time.timeScale = 0f; } } } public void Quit() { Application.Quit(); // is ignored in editor state = eGameManagerState.quitLastChoicePanel; } #endregion // #region COROUTINES // check if level has been reset private bool levelReset = false; /// <summary> /// Method executed during advancement of level /// </summary> // ADVANCEMENT private IEnumerator PlayerAdvances() { // an external script verifies trigger // the trigger varies based on level Transform myEndLevel = player.AdvancePlayer(); yield return null; } // ASYNC "NEW GAME" /// <summary> /// Coroutine for new game /// </summary> private IEnumerator LoadNewSceneAndSetActive() { player.ResetPlayer(); player.level = 1; player.SavePlayer(); // player.UnloadPlayer(); // notice that you are loading scene 01 no matter what AsyncOperation loadGameLevel = SceneManager.LoadSceneAsync(1, LoadSceneMode.Additive); // yield return new WaitForSeconds((float)introTrailerPlayer.length); // unload the current active scene - if there is more than one if (SceneManager.sceneCount > 1) { yield return SceneManager.UnloadSceneAsync(SceneManager.GetActiveScene().buildIndex); } while (loadGameLevel.progress < 1) { // set game state state = eGameManagerState.loadingPanel; yield return new WaitForSeconds(loadingTime); // now play the trailer state = eGameManagerState.cinematicTrailer; if (player.level == 0) { player.level++; } player.LoadPlayer(); // wait for the video to finish yield return new WaitForSeconds((float)video.length); // set state state = eGameManagerState.gameplay; // check for the level StartCoroutine(CheckForLevelCoroutine()); } } // ASYNC "CONTINUE" /// <summary> /// Coroutine continuing the scene - gameplay mode /// </summary> private IEnumerator LoadSceneAndSetActiveGameContinue() { state = eGameManagerState.gameplay; player.UnloadPlayer(); // if (currentLevelNum == 0) { yield break; } // yield return new WaitForSeconds(loadingTime); if (currentLevelNum == 4 && SceneManager.sceneCount > 1) { yield break; } // Unload the current active scene - if there is more than one if (SceneManager.sceneCount > 1) { yield return SceneManager.UnloadSceneAsync(SceneManager.GetSceneAt(1)); } // load stats and player player.LoadPlayer(); CheckForLevelCoroutine(); // if (currentLevelNum < 5) // we have only 4 levels for now { yield return SceneManager.LoadSceneAsync(currentLevelNum, LoadSceneMode.Additive); yield return new WaitForSeconds(loadingTime); loadingPanel.SetActive(false); } } // ASYNC "DEVELOPER MODE - RELOAD FROM END" /// <summary> /// Coroutine reloading the scene - developer mode /// </summary> private IEnumerator ReloadScenesForgameplay() { player.UnloadPlayer(); // unload the scene if (SceneManager.sceneCount > 1) { yield return SceneManager.UnloadSceneAsync(SceneManager.GetSceneAt(1)); } yield return new WaitForSeconds(loadingTime); // loading time might be an user experience decision loadingPanel.SetActive(false); // scene starts state = eGameManagerState.entryPanel; currentLevelNum = 0; currentLevelName = "Level 0"; developerSceneCounter = 0; player.ResetPlayer(); player.level = 0; while (!levelReset) yield return new WaitForEndOfFrame(); // if (currentLevelNum == 0 || currentLevelName == "Level 0" || developerSceneCounter == 0) { StartCoroutine(CheckForLevelCoroutine()); CheckForState(); } } // ASYNC "GAMEPLAY MODE - RELOAD FROM END" /// <summary> /// Coroutine reloading the scene - gameplay mode /// </summary> private IEnumerator ReloadScenesForGameplayMode() { player.UnloadPlayer(); // unload the scene if (SceneManager.sceneCount > 1) { yield return SceneManager.UnloadSceneAsync(SceneManager.GetSceneAt(1)); } yield return new WaitForSeconds(1); state = eGameManagerState.cinematicTrailer; endTrailerPanel.SetActive(true); // yield return new WaitForSeconds((float)endTrailerPlayer.length); // loadingPanel.SetActive(true); yield return new WaitForSeconds(loadingTime); state = eGameManagerState.entryPanel; // loadingPanel.SetActive(false); // player.level = 0; currentLevelNum = 0; currentLevelName = "Level 0"; developerSceneCounter = 0; player.ResetPlayer(); player.level = 0; // if (currentLevelNum == 0 || currentLevelName == "Level 0" || developerSceneCounter == 0) { StartCoroutine(CheckForLevelCoroutine()); CheckForState(); } } // ASYNC "GAMEPLAY MODE - ADVANCE TO NEXT SCENE" /// <summary> /// Coroutine for advancement after new game - gameplay mode /// </summary> private IEnumerator LoadSceneAndSetActiveGame() { player.level++; player.SavePlayer(); loadingPanel.SetActive(true); player.UnloadPlayer(); // set/reset state state = eGameManagerState.gameplay; // unload if (SceneManager.sceneCount > 1) { yield return SceneManager.UnloadSceneAsync(SceneManager.GetSceneAt(1)); } if (currentLevelNum <= 4) { player.LoadPlayer(); yield return SceneManager.LoadSceneAsync(currentLevelNum, LoadSceneMode.Additive); // yield return new WaitForSeconds(loadingTime); loadingPanel.SetActive(false); // } } // ASYNC "DEVELOPER MODE - NEXT SCENE" private IEnumerator LoadNextSceneSetActive() { loadingPanel.SetActive(true); player.UnloadPlayer(); // set/reset state state = eGameManagerState.developerMode; // if (developerSceneCounter == 4 && SceneManager.sceneCount > 1) { yield break; } // Unload the current active scene - if there is more than one if (SceneManager.sceneCount > 1) { yield return SceneManager.UnloadSceneAsync(SceneManager.GetSceneAt(1)); } if (developerSceneCounter < 4) { yield return SceneManager.LoadSceneAsync(developerSceneCounter + 1, LoadSceneMode.Additive); developerSceneCounter++; yield return new WaitForSeconds(loadingTime); player.LoadPlayer(); if (developerSceneCounter == 1) { StartCoroutine(FixPlayerPositionForFirstLevel()); } yield return new WaitForSeconds(loadingTime); loadingPanel.SetActive(false); } } // ASYNC "DEVELOPER MODE - PREVIOUS SCENE" /// <summary> /// ASYNC COROUNTINE FOR DEVELOPER MODE : GO TO PREVIOUS SCENE /// </summary> private IEnumerator LoadPreviousSceneSetActive() { loadingPanel.SetActive(true); player.player.UnloadPlayer(); // state state = eGameManagerState.developerMode; // if (developerSceneCounter == 1) { //state = eGameManagerState.entryPanel; yield return SceneManager.UnloadSceneAsync(SceneManager.GetSceneAt(1)); developerSceneCounter--; yield return new WaitForSeconds(1); loadingPanel.SetActive(false); yield break; } // Unload the current active scene - if there is more than one if (SceneManager.sceneCount > 1) { yield return SceneManager.UnloadSceneAsync(SceneManager.GetSceneAt(1)); } // if (developerSceneCounter <= 4) { yield return SceneManager.LoadSceneAsync(developerSceneCounter - 1, LoadSceneMode.Additive); developerSceneCounter--; yield return new WaitForSeconds(loadingTime); player.LoadPlayer(); if (developerSceneCounter == 1) { StartCoroutine(FixPlayerPositionForFirstLevel()); } yield return new WaitForSeconds(loadingTime); loadingPanel.SetActive(false); } } #endregion // #region STATE CHECK // CHANGE STATES [applied to on "button click"] public void ChangeState(uint stateNumberVariable) { uint stateNumber; stateNumber = stateNumberVariable; eGameManagerState stateChange = eGameManagerState.entryPanel; if (stateNumberVariable >= (uint)eGameManagerState.END) { Debug.LogWarning("Index Out of Range"); return; } stateChange = (eGameManagerState)stateNumber; state = stateChange; } // CHECK STATES /// <summary> /// Checking states - not really used in this scenario /// But good to have. Maybe to be handled through UI /// Audio should be probably handled separately /// </summary> public void CheckForState() { switch (state) { // MAIN - remember to add game play or not case eGameManagerState.pausePanel: break; case eGameManagerState.developerMode: break; case eGameManagerState.entryPanel: break; case eGameManagerState.aboutPanel: break; case eGameManagerState.cinematicTrailer: break; case eGameManagerState.gameplay: break; case eGameManagerState.loadingPanel: break; case eGameManagerState.panelTitle: break; case eGameManagerState.pausePanelSettings: break; case eGameManagerState.quitLastChoicePanel: break; } } // CHECK LEVEL /// <summary> /// Checking levels /// </summary> public void CheckLevel() { StartCoroutine(CheckForLevelCoroutine()); } IEnumerator CheckForLevelCoroutine() { // in this time you give the time to see what are actually the changes thart are happening yield return new WaitForSeconds(loadingTime); // Debug.Log($"Level {currentScene.buildIndex} is playing"); } } } #endregion |