├── README.md └── Unity Tutorials ├── CarMovement ├── README.md └── Scripts │ ├── Front-Wheel-Drive │ └── CarController.cs │ └── Rear-Wheel-Drive │ └── CarController.cs ├── HelpScreenUI ├── README.md └── Scripts │ └── HowToManager.cs ├── Inspired_by_Games-Series ├── DeadEyeSystem │ ├── OBalfaqih-DeadEyeSystem-RDR2.unitypackage │ ├── README.md │ └── scripts │ │ └── DeadEyeSystemFinished.cs ├── Imitiating_the_Axe_of_Kratos │ ├── README.md │ └── ThrowableAxe.cs ├── README.md └── Wingsuit │ ├── CameraShake.cs │ ├── README.md │ └── WingsuitController.cs ├── Interacting-with-Doors ├── README.md └── Scripts │ └── DoorController.cs ├── Intro to UI ├── README.md └── Scripts │ └── ColorTest.cs ├── Save&Load ├── README.md └── Scripts │ └── SavingTutorial.cs ├── SceneLoader ├── README.md └── Scripts │ └── SceneLoaderScript.cs ├── Simple Game - series ├── README.md └── Scripts │ ├── Coin.cs │ ├── DestroyAfter.cs │ ├── Player.cs │ └── Spawner.cs ├── Unity_IAP ├── README.md └── Scripts │ └── StoreManager.cs ├── Upgradeable Objects ├── README.md └── Scripts │ └── SwitchingLevels.cs ├── Virtual Joysticks ├── README.md └── Scripts │ └── Player.cs └── WaypointMarker ├── Assets └── waypoint-marker.png ├── README.md └── Scripts └── MissionWaypoint.cs /README.md: -------------------------------------------------------------------------------- 1 | # Unity-Tutorials 2 | Here I will upload the scripts of the Unity tutorials that I make on my YouTube channel. 3 | Channel Link: 4 | http://youtube.com/OBalfaqih 5 | 6 | السلام عليكم ورحمة الله وبركاته 7 | هنا برفع السكريبتات الي موجودة في دروس يونيتي الي اسويها على قناتي. 8 | رابط القناة: 9 | http://youtube.com/OBalfaqih 10 | -------------------------------------------------------------------------------- /Unity Tutorials/CarMovement/README.md: -------------------------------------------------------------------------------- 1 |

Unity | Car Movement

2 | These two scripts are from the "Unity | Car Movement" tutorial from my channel.
3 | Front-Wheel-Drive or (FWD) is applying torque force to the front wheels. Whereas Rear-Wheel-Drive or (RWD) is the opposite, it applies the torque to the back/rear ones.
4 | But both uses the front wheels for steering.
5 | You can watch the full tutorial:
6 | https://www.youtube.com/watch?v=_e2RrZQs_1Q 7 | -------------------------------------------------------------------------------- /Unity Tutorials/CarMovement/Scripts/Front-Wheel-Drive/CarController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class CarController : MonoBehaviour { 6 | 7 | public WheelCollider[] front_wheels; 8 | public float maxSpeed = 500.0f; 9 | public float maxSteer = 30.0f; 10 | 11 | private void FixedUpdate() 12 | { 13 | // Store the affected value of the torque speed by the user input 14 | float motorT = Input.GetAxis("Vertical") * maxSpeed; 15 | 16 | // Store the affected value of the steering by the user input 17 | float steerA = Input.GetAxis("Horizontal") * maxSteer; 18 | 19 | // Setting the torque speed for both wheels 20 | front_wheels[0].motorTorque = motorT; 21 | front_wheels[1].motorTorque = motorT; 22 | 23 | // Setting the steering angle for both wheels 24 | front_wheels[0].steerAngle = steerA; 25 | front_wheels[1].steerAngle = steerA; 26 | 27 | // If the user stopped controlling, will pull the brakes. Otherwise, release them 28 | if(Mathf.Abs(Input.GetAxis("Vertical")) > 0.01f){ 29 | front_wheels[0].brakeTorque = 0; 30 | front_wheels[1].brakeTorque = 0; 31 | }else{ 32 | front_wheels[0].brakeTorque = 220; 33 | front_wheels[1].brakeTorque = 220; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Unity Tutorials/CarMovement/Scripts/Rear-Wheel-Drive/CarController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class CarController : MonoBehaviour { 6 | 7 | public WheelCollider[] front_wheels, back_wheels; 8 | public float maxSpeed = 500.0f; 9 | public float maxSteer = 30.0f; 10 | 11 | private void FixedUpdate() 12 | { 13 | // Store the affected value of the torque speed by the user input 14 | float motorT = Input.GetAxis("Vertical") * maxSpeed; 15 | 16 | // Store the affected value of the steering by the user input 17 | float steerA = Input.GetAxis("Horizontal") * maxSteer; 18 | 19 | // Setting the torque speed for both wheels 20 | back_wheels[0].motorTorque = motorT; 21 | back_wheels[1].motorTorque = motorT; 22 | 23 | // Setting the steering angle for both wheels 24 | front_wheels[0].steerAngle = steerA; 25 | front_wheels[1].steerAngle = steerA; 26 | 27 | // If the user stopped controlling, will pull the brakes. Otherwise, release them 28 | if(Mathf.Abs(Input.GetAxis("Vertical")) > 0.01f){ 29 | back_wheels[0].brakeTorque = 0; 30 | back_wheels[1].brakeTorque = 0; 31 | }else{ 32 | back_wheels[0].brakeTorque = 220; 33 | back_wheels[1].brakeTorque = 220; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Unity Tutorials/HelpScreenUI/README.md: -------------------------------------------------------------------------------- 1 |

Help Screen UI

2 | Adding a help / instructions screen to your game and showing it to your player only for the first time he plays.
3 | You can learn more about this tutorial by watching it:
4 | https://www.youtube.com/watch?v=PKP5yGqCxFA 5 | -------------------------------------------------------------------------------- /Unity Tutorials/HelpScreenUI/Scripts/HowToManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class HowToManager : MonoBehaviour { 6 | 7 | // The object that contains the Help UI 8 | public GameObject help; 9 | 10 | private void Start() 11 | { 12 | // Check if the key exists 13 | if(PlayerPrefs.HasKey("FirstTime")){ 14 | // It is not the first time 15 | // You can set any code you want here if it is not the first time 16 | // Maybe you can show "Welcome Again" screen 17 | }else{ 18 | // It is the first 19 | PlayerPrefs.SetInt("FirstTime", 1); 20 | showHelp(); 21 | } 22 | } 23 | 24 | void showHelp(){ 25 | // Setting 'help' active / enabled 26 | help.SetActive(true); 27 | } 28 | } -------------------------------------------------------------------------------- /Unity Tutorials/Inspired_by_Games-Series/DeadEyeSystem/OBalfaqih-DeadEyeSystem-RDR2.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OBalfaqih/Unity-Tutorials/b03dfde64b69c025095b5b569e91386f65593362/Unity Tutorials/Inspired_by_Games-Series/DeadEyeSystem/OBalfaqih-DeadEyeSystem-RDR2.unitypackage -------------------------------------------------------------------------------- /Unity Tutorials/Inspired_by_Games-Series/DeadEyeSystem/README.md: -------------------------------------------------------------------------------- 1 |

Making The Dead Eye System - Red Dead Redemption 2 (Inspired by Games) | Unity

2 |

This is the second tutorial in this series (Inspired by Games). In this tutorial, we copy the Dead Eye System from Red Dead Redemption 2 in a simple way.

3 |

This series is to inspire all game developers to get ideas from other games and think creatively how to copy the feature but in their own and with their own touch on it.

4 |

You can find the Unity package for the whole project, but if you don't want to download it, get the script directly from the scripts folder.

5 |

You can find out more about the series by going to its YouTube Playlist.

6 |

Watch the full tutorial here

7 | -------------------------------------------------------------------------------- /Unity Tutorials/Inspired_by_Games-Series/DeadEyeSystem/scripts/DeadEyeSystemFinished.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Created by: Omar Balfaqih (@OBalfaqih) 3 | * http://obalfaqih.com 4 | * 5 | * Unity | Dead Eye - Red Dead Redemption (Inspired by Games) 6 | * 7 | * This is the second episode of the new series: Inspired by Games 8 | * Where we will choose a feature from our favorite games 9 | * and try to copy it and learn the science behind it 10 | * 11 | * Full tutorial available at: 12 | * https://www.youtube.com/watch?v=LofJMnWPClo&index=2&list=PLaqp5z-4pFi5auiUbsq_KChZKX-DufAOI 13 | */ 14 | 15 | /* 16 | * How to use: 17 | * 1- Attach this script to the camera 18 | * 2- Create multiple UI Crosses (the "x" target ui) and assign them to the "cross" variable 19 | * 3- Change the "CameraLook" to your camera look script, in this case it is CameraLook, if you're using it then leave it as it is. 20 | * Then assign the camera script to it. 21 | * 4- Create a PostProcessingLayer and PostProcessingVolume then assign the volume to the "ppv" variable. 22 | * 5- Add an AudioSource component and assign the gun shot sound, you can find it in "SFX" folder or you can use your own. 23 | * Assign it to "shot_sfx" variable. 24 | */ 25 | 26 | 27 | using System.Collections; 28 | using System.Collections.Generic; 29 | using UnityEngine; 30 | // Using the PostProcessing library, to control the PostProcessingVolume (ppv) 31 | using UnityEngine.Rendering.PostProcessing; 32 | 33 | public class DeadEyeSystemFinished : MonoBehaviour { 34 | // Three different states of DeadEye [off: not using DeadEye, targeting: Aiming in DeadEye mode, 35 | // shooting: Auto-Shoot at targets (player won't have control over looking around and shooting)] 36 | public enum DeadEyeState 37 | { 38 | off, 39 | targeting, 40 | shooting 41 | }; 42 | // Create an instance of DeadEyeState as a variable 43 | private DeadEyeState deadEyeState = DeadEyeState.off; 44 | 45 | // List of assigned targets 46 | private List targets = new List(); 47 | // An array of the cross UI, the small "x" indicator for the targets 48 | public Transform[] cross; 49 | 50 | // Your camera script, if you're using another one, simply change the class name to yours 51 | public CameraLook camLook; 52 | // Storing the PostProcessVolume component to adjust its weight (On/Off) 53 | public PostProcessVolume ppv; 54 | 55 | // The animator component of your gun 56 | public Animator anim; 57 | // Timer for the gun to cooldown, you can link it to your current gun's script 58 | private float cooldownTimer = 0; 59 | // The audio source that contains the gun shot sound 60 | public AudioSource shot_sfx; 61 | 62 | private void Update() 63 | { 64 | // Update timer 65 | if (cooldownTimer > 0.0f) 66 | cooldownTimer -= Time.deltaTime; 67 | else 68 | cooldownTimer = 0.0f; 69 | 70 | // Aim (Hold Right Click) - Enter DeadEye 71 | if (Input.GetButtonDown("Fire2")) 72 | { 73 | // Enter targeting mode if it's off 74 | if (deadEyeState == DeadEyeState.off) 75 | { 76 | deadEyeState = DeadEyeState.targeting; 77 | } 78 | } 79 | 80 | // Fire (Left Click) - If DeadEye, SetTarget. Else just Fire() 81 | if (Input.GetButtonDown("Fire1")) 82 | { 83 | // If we're not in the DeadEye mode, fire a single shot 84 | if (deadEyeState == DeadEyeState.off) 85 | Fire(); 86 | 87 | // If we're in targeting state in the DeadEye mode, then we will assign a new target 88 | if (deadEyeState == DeadEyeState.targeting) 89 | { 90 | // Setting targets 91 | // Storing the collision info into a new variable "hit" 92 | RaycastHit hit; 93 | // Check if we hit an object starting from our position, going straight forward with a max distance of 100 units 94 | if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hit, 100)) 95 | { 96 | // Creating a temporary GameObject to store the target info 97 | GameObject tmpTarget = new GameObject(); 98 | // Assign the target position to it 99 | tmpTarget.transform.position = hit.point; 100 | // Attach it to the target (child of the target) so it updates its position if the target is moving 101 | tmpTarget.transform.parent = hit.transform; 102 | // Add its transform to our List of targets 103 | targets.Add(tmpTarget.transform); 104 | } 105 | } 106 | } 107 | 108 | // Release (Release Right Click) 109 | if (Input.GetButtonUp("Fire2")) 110 | { 111 | // If we're in 'targeting' mode, we will go to 'shooting' mode 112 | if (deadEyeState == DeadEyeState.targeting) 113 | deadEyeState = DeadEyeState.shooting; 114 | } 115 | } 116 | 117 | private void FixedUpdate() 118 | { 119 | // Enable/Disable the camera script, update timescale (slow-motion) and turn on/off the post processing effect 120 | UpdateState(); 121 | // Update the small target UI indicators. 122 | UpdateTargetUI(); 123 | } 124 | 125 | private void UpdateState() 126 | { 127 | // Reset if DeadEye is off 128 | if (deadEyeState == DeadEyeState.off) 129 | { 130 | // Enable the camera script 131 | camLook.enabled = true; 132 | // Reset time back to normal 133 | Time.timeScale = 1; 134 | Time.fixedDeltaTime = Time.timeScale * 0.02f; 135 | // Deactivate the post processing effect 136 | if (ppv.weight > 0) 137 | ppv.weight -= Time.deltaTime * 2; 138 | } 139 | // When we're in shooting mode 140 | else if (deadEyeState == DeadEyeState.shooting) 141 | { 142 | // Reset time 143 | Time.timeScale = 1; 144 | Time.fixedDeltaTime = Time.timeScale * 0.02f; 145 | // Disable the camera controls 146 | camLook.enabled = false; 147 | // Updating looking at targets and shooting 148 | UpdateDeadEye(); 149 | } 150 | // We're in targeting mode 151 | else 152 | { 153 | // Slow-motion 154 | Time.timeScale = 0.3f; 155 | Time.fixedDeltaTime = Time.timeScale * 0.02f; 156 | // Enable camera script 157 | camLook.enabled = true; 158 | // Show the post processing effect (Colors and feel of the DeadEye mode) 159 | if (ppv.weight < 1) 160 | ppv.weight += Time.deltaTime * 2; 161 | } 162 | } 163 | 164 | private void UpdateTargetUI() 165 | { 166 | // Loop through all "cross" target indicators 167 | for (int i = 0; i < cross.Length; i++) 168 | { 169 | // If we are still within the targets we have 170 | if (i < targets.Count) 171 | { 172 | // Activate it 173 | cross[i].gameObject.SetActive(true); 174 | // Then update its position to the screen position of the target 175 | cross[i].position = Camera.main.WorldToScreenPoint(targets[i].position); 176 | } 177 | else // If we exceeded the last target 178 | cross[i].gameObject.SetActive(false); //Deactivate it 179 | } 180 | } 181 | 182 | private void Fire() 183 | { 184 | // Here you can integrate it with your weapon script "myGunScript.Fire()" 185 | // For this tutorial's scope, we just triggered the animation and played the shot sound 186 | // To indicate a gun shot 187 | 188 | // Trigger "Shot1" parameter in the animator 189 | anim.SetTrigger("Shot1"); 190 | // 1.0f / bullets per second to get the cooldown timer between each shot 191 | cooldownTimer = 1.0f / 2.0f; 192 | // Play the gun shot sfx 193 | shot_sfx.Play(); 194 | } 195 | 196 | private void UpdateDeadEye() 197 | { 198 | // If we're in shooting state and we still have targets in our list 199 | if (deadEyeState == DeadEyeState.shooting && targets.Count > 0) 200 | { 201 | // Get the current target in a temporary variable of type Transform which is the first element in the list 202 | Transform currentTarget = targets[0]; 203 | // Get the required rotation for our camera to be looking at the target 204 | Quaternion rot = Quaternion.LookRotation(currentTarget.position - transform.position); 205 | // Updating the camera rotation to the "Looking at target" rotation gradually, 30deg/s 206 | transform.rotation = Quaternion.Slerp(transform.rotation, rot, 30 * Time.deltaTime); 207 | // Get the difference between our current rotation and the target's 208 | float diff = (transform.eulerAngles - rot.eulerAngles).magnitude; 209 | // Check if the diff is less than or quals "0.1f" (You can change it depending on the desired accuracy) 210 | // AND the gun has cooled down (You can use your gun script's cooldown if you are using one) 211 | if (diff <= 0.1f && cooldownTimer <= 0) 212 | { 213 | // We are looking at the target 214 | // Fire a single shot 215 | Fire(); 216 | // Remove the target form the list 217 | targets.Remove(currentTarget); 218 | // Destroy the target 219 | Destroy(currentTarget.gameObject); 220 | } 221 | } 222 | else // Either we're not in shooitng mode or we ran out of targets 223 | deadEyeState = DeadEyeState.off; // Reset the DeadEye state to off 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /Unity Tutorials/Inspired_by_Games-Series/Imitiating_the_Axe_of_Kratos/README.md: -------------------------------------------------------------------------------- 1 |

Imitating Kratos Axe (Inspired by Games) | Unity

2 |

This is the first tutorial in this series (Inspired by Games) where we imitate the basic mechanics of the axe of Kratos from God of War

3 |

This series is to inspire all game developers to get ideas from other games and think creatively how to copy the feature but in their own and with their own touch on it.

4 |

You can find out more about the series by going to its YouTube Playlist.

5 |

Watch the full tutorial here

6 | -------------------------------------------------------------------------------- /Unity Tutorials/Inspired_by_Games-Series/Imitiating_the_Axe_of_Kratos/ThrowableAxe.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | /* 6 | * Created by: Omar Balfaqih (@OBalfaqih) 7 | * http://obalfaqih.com 8 | * 9 | * Unity | Imitating the Axe of Kratos 10 | * 11 | * This is the first tutorial of the new series: Inspired by Games 12 | * Where we will choose a feature from our favorite games 13 | * and try to copy it and learn the science behind it 14 | * 15 | * Full tutorial available at: 16 | * https://www.youtube.com/watch?v=PxdoBJBCcrw 17 | */ 18 | 19 | /* 20 | * How to use: 21 | * 1- Attach this script to the player, or the parent of your axe 22 | * 2- Make sure that your axe has Rigidbody component on it 23 | * 3- You will have a target which the handler of the axe, or where the axe should be when the player is holding it 24 | * 4- Create an empty game object which should stay attached to the hander (target) and place it next to it. It is the 25 | * point that gives the curve to your axe returning movement. Keep adjusting it until you find the perfect curve. 26 | */ 27 | 28 | public class ThrowableAxe : MonoBehaviour { 29 | 30 | // The axe object 31 | public Rigidbody axe; 32 | // Amount of force to apply when throwing 33 | public float throwForce = 50; 34 | // the target; which is the player's hand. 35 | public Transform target 36 | // The middle point between the axe and the player's hand, to give it a curve 37 | public Transform curve_point; 38 | // Last position of the axe before returning it, to use in the Bezier Quadratic Curve formula 39 | private Vector3 old_pos; 40 | // Is the axe returning? To update the calculations in the Update method 41 | private bool isReturning = false; 42 | // Timer to link to the Bezier formual, Beginnning = 0, End = 1 43 | private float time = 0.0f; 44 | 45 | // Update is called once per frame 46 | void Update () { 47 | // When the user/player hits the mouse left button 48 | if (Input.GetButtonUp("Fire1")) 49 | { 50 | ThrowAxe(); 51 | } 52 | 53 | // When the user/player hits the mouse right button 54 | if (Input.GetButtonUp("Fire2")) 55 | { 56 | ReturnAxe(); 57 | } 58 | 59 | // If the axe is returning 60 | if(isReturning){ 61 | // Returning calcs 62 | // Check if we haven't reached the end point, where time = 1 63 | if(time < 1.0f){ 64 | // Update its position by using the Bezier formula based on the current time 65 | axe.position = getBQCPoint(time, old_pos, curve_point.position, target.position); 66 | // Reset its rotation (from current to the targets rotation) with 50 units/s 67 | axe.rotation = Quaternion.Slerp(axe.transform.rotation, target.rotation, 50 * Time.deltaTime); 68 | // Increase our timer, if you want the axe to return faster, then increase "time" more 69 | // With something like: 70 | // time += Timde.deltaTime * 2; 71 | // It will return as twice as fast 72 | time += Time.deltaTime; 73 | }else{ 74 | // Otherwise, if it is 1 or more, we reached the target so reset 75 | ResetAxe(); 76 | } 77 | } 78 | } 79 | 80 | // Throw axe 81 | void ThrowAxe(){ 82 | // The axe isn't returning 83 | isReturning = false; 84 | // Deatach it form its parent 85 | axe.transform.parent = null; 86 | // Set isKinematic to false, so we can apply force to it 87 | axe.isKinematic = false; 88 | // Add force to the forward axis of the camera 89 | // We used TransformDirection to conver the axis from local to world 90 | axe.AddForce(Camera.main.transform.TransformDirection(Vector3.forward) * throwForce, ForceMode.Impulse); 91 | // Add torque to the axe, to give it much cooler effect (rotating) 92 | axe.AddTorque(axe.transform.TransformDirection(Vector3.right) * 100, ForceMode.Impulse); 93 | } 94 | 95 | // Return axe 96 | void ReturnAxe(){ 97 | // We are returning the axe; so it is in its first point where time = 0 98 | time = 0.0f; 99 | // Save its last position to refer to it in the Bezier formula 100 | old_pos = axe.position; 101 | // Now we are returning the axe, to start the calculations in the Update method 102 | isReturning = true; 103 | // Reset its velocity 104 | axe.velocity = Vector3.zero; 105 | // Set isKinematic to true, so now we control its position directly without being affected by force 106 | axe.isKinematic = true; 107 | } 108 | 109 | // Reset axe 110 | void ResetAxe(){ 111 | // Axe has reached, so it is not returning anymore 112 | isReturning = false; 113 | // Attach back to its parent, in this case it will attach it to the player (or where you attached the script to) 114 | axe.transform.parent = transform; 115 | // Set its position to the target's 116 | axe.position = target.position; 117 | // Set its rotation to the target's 118 | axe.rotation = target.rotation; 119 | } 120 | 121 | // Bezier Quadratic Curve formula 122 | // Learn more: 123 | // https://en.wikipedia.org/wiki/B%C3%A9zier_curve 124 | Vector3 getBQCPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2){ 125 | // "t" is always between 0 and 1, so "u" is other side of t 126 | // If "t" is 1, then "u" is 0 127 | float u = 1 - t; 128 | // "t" square 129 | float tt = t * t; 130 | // "u" square 131 | float uu = u * u; 132 | // this is the formula in one line 133 | // (u^2 * p0) + (2 * u * t * p1) + (t^2 * p2) 134 | Vector3 p = (uu * p0) + (2 * u * t * p1) + (tt * p2); 135 | return p; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Unity Tutorials/Inspired_by_Games-Series/README.md: -------------------------------------------------------------------------------- 1 |

Inspired by Games

2 |

It is an online Unity series that is created to take interesting features in games and try to copy it in Unity. Converting science to art.

3 |

The series is created mainly to be inspired by other games and try to think creatively on achieving something similar but in your own way based on your analysis and reverse-engineering skills.
4 | It combines logic with the sense of art.

5 |

You can find out more about the series by going to its YouTube Playlist.

-------------------------------------------------------------------------------- /Unity Tutorials/Inspired_by_Games-Series/Wingsuit/CameraShake.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | /* 6 | * CameraShake.cs 7 | * 8 | * Created by: Omar Balfaqih (@OBalfaqih) 9 | * http://obalfaqih.com 10 | * 11 | * Making Wingsuit - Far Cry 5 (Inspired by Games) | Unity 12 | * 13 | * This is the third episode of the series: Inspired by Games 14 | * Where we choose a feature from our favorite games 15 | * and try to copy it and learn the science behind it 16 | * 17 | * Full tutorial available at: 18 | * https://www.youtube.com/watch?v=g4YWcklQoVg 19 | */ 20 | 21 | /* 22 | * How to use: 23 | * 1- Attach this script to the camera 24 | * 2- Make sure the camera is a child of your player 25 | * 3- Attach the player's 'WingsuitController' script to the 'wc' variable 26 | * 4- You can adjust the 'shaking' variable, which is the value of how much can the camera shake (Optional) 27 | */ 28 | 29 | public class CameraShake : MonoBehaviour 30 | { 31 | // The wingsuit script that the player has 32 | public WingsuitController wc; 33 | // The amount of shaking 34 | public float shaking = 0.5f; 35 | 36 | private void LateUpdate() 37 | { 38 | // Will affect the shaking based on the player's x rotation 39 | float mod_shaking = shaking * wc.percentage; 40 | // Give the camera a random position based on the percentage and shaking variables 41 | transform.localPosition = new Vector3(Random.Range(-mod_shaking, mod_shaking), Random.Range(-mod_shaking, mod_shaking), 0); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Unity Tutorials/Inspired_by_Games-Series/Wingsuit/README.md: -------------------------------------------------------------------------------- 1 |

Making The Wingsuit - Far Cry 5 (Inspired by Games) | Unity

2 |

This is the third tutorial in this series (Inspired by Games) where we are going to make our player fly using a the wingsuit feature from Far Cry 5. 3 |

In this series, we study a feature from a game and then try to implement it for the sake of learning.

4 |

You can find out more about the series by going to its YouTube Playlist.

5 |

Watch the full tutorial here

6 | -------------------------------------------------------------------------------- /Unity Tutorials/Inspired_by_Games-Series/Wingsuit/WingsuitController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.Audio; 5 | 6 | /* 7 | * WingsuitController.cs 8 | * 9 | * Created by: Omar Balfaqih (@OBalfaqih) 10 | * http://obalfaqih.com 11 | * 12 | * Making Wingsuit - Far Cry 5 (Inspired by Games) | Unity 13 | * 14 | * This is the third episode of the series: Inspired by Games 15 | * Where we choose a feature from our favorite games 16 | * and try to copy it and learn the science behind it 17 | * 18 | * Full tutorial available at: 19 | * https://www.youtube.com/watch?v=g4YWcklQoVg 20 | */ 21 | 22 | /* 23 | * How to use: 24 | * 1- Attach this script to the player 25 | * 2- Make sure that your player has a Rigidbody component 26 | * 3- Create an AudioMixer that has the sound effects, if you don't want to, just comment line: 60 and 103 27 | * 4- You don't have to modify the public variables, but you can adjust them. (Optional) 28 | */ 29 | 30 | public class WingsuitController : MonoBehaviour 31 | { 32 | // Get the player Rigidbody component 33 | public Rigidbody rb; 34 | // Rotation 35 | private Vector3 rot; 36 | 37 | // Min speed, when the player is on 0 deg or whatever minAngle you have 38 | public float lowSpeed = 12.5f; 39 | // Max speed 40 | public float highSpeed = 13.8f; 41 | 42 | // Max drag, if the player is on 0 deg or minAngle 43 | public float maxDrag = 6; 44 | // Min drag 45 | public float minDrag = 2; 46 | 47 | // Here we will store the modified force and drag 48 | private float mod_force; 49 | private float mod_drag; 50 | 51 | // Min angle for the player to rotate on the x-axis 52 | public float minAngle = 0; 53 | // Max angle 54 | public float maxAngle = 45; 55 | 56 | // Converting the x rotation from min angle to max, into a 0-1 format. 57 | // 0 means minAngle 58 | // 1 means maxAngle 59 | public float percentage; 60 | 61 | // Audio mixer, to control the sound FX pitch 62 | public AudioMixer am; 63 | 64 | private void Start() 65 | { 66 | // Make sure the player has a Rigidbody component 67 | rb = GetComponent(); 68 | // Setting rotation variable to the current angles 69 | rot = transform.rotation.eulerAngles; 70 | } 71 | 72 | private void LateUpdate() 73 | { 74 | // Rotation 75 | // Y 76 | rot.y += 20 * Input.GetAxis("Horizontal") * Time.deltaTime; 77 | // Z 78 | rot.z = -5 * Input.GetAxis("Horizontal"); 79 | // Limiting the z-axis 80 | rot.z = Mathf.Clamp(rot.z, -5, 5); 81 | // X 82 | rot.x += 20 * Input.GetAxis("Vertical") * Time.deltaTime; 83 | // Limiting x-axis 84 | rot.x = Mathf.Clamp(rot.x, minAngle, maxAngle); 85 | // Update rotation 86 | transform.rotation = Quaternion.Euler(rot); 87 | 88 | // Speed and drag based on angle 89 | // Get the percentage (minAngle = 0, maxAngle = 1) 90 | percentage = rot.x / maxAngle; 91 | // Update parameters 92 | // If 0, we'll have maxDrag and lowSpeed 93 | // If 1, we'll get minDrag and highSpeed 94 | mod_drag = (percentage * (minDrag - maxDrag)) + maxDrag; 95 | mod_force = (percentage * (highSpeed - lowSpeed)) + lowSpeed; 96 | // Getting the local space of the velocity 97 | Vector3 localV = transform.InverseTransformDirection(rb.velocity); 98 | // Change z velocity to mod_force 99 | localV.z = mod_force; 100 | // Convert the local velocity back to world space and set it to the Rigidbody's velocity 101 | rb.velocity = transform.TransformDirection(localV); 102 | // Update drag to the modified one 103 | rb.drag = mod_drag; 104 | // Change pitch value based on the player's angle and percentage 105 | am.SetFloat("Pitch", 1 + percentage); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Unity Tutorials/Interacting-with-Doors/README.md: -------------------------------------------------------------------------------- 1 |

Interacting with Doors | Unity

2 |

In this simple tutorial we are going to open and close doors.

3 |

We will cover three things: 4 |

    5 |
  • Door animation (Open/Close)
  • 6 |
  • Showing instructions message
  • 7 |
  • The script that manages the above
  • 8 |
9 |

10 |

Watch the full tutorial here

-------------------------------------------------------------------------------- /Unity Tutorials/Interacting-with-Doors/Scripts/DoorController.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Created by: Omar Balfaqih (@OBalfaqih) 3 | * http://obalfaqih.com 4 | * 5 | * Interacting with Doors | Unity 6 | * 7 | * This simple script is to interact with doors (open/close) when the player presses "E" 8 | * 9 | * Full tutorial available at: 10 | * https://www.youtube.com/watch?v=nONlAXpCkag 11 | */ 12 | 13 | /* 14 | * How to use: 15 | * 1- Attach this script to your player 16 | * 2- Make sure that the player has Rigidbody and Collider components 17 | * 3- The door's parent has a collider with trigger checked 18 | * 4- Door's parent has the tag "Door" 19 | * 5- The door itself has an Animator and it has a parameter of type trigger called "OpenClose" 20 | * which is responsible for the transition between opening and closing. 21 | */ 22 | 23 | using System.Collections; 24 | using System.Collections.Generic; 25 | using UnityEngine; 26 | 27 | public class DoorController : MonoBehaviour 28 | { 29 | // The gameobject / UI that has the instructions for the player "Press 'E' to interact." 30 | public GameObject instructions; 31 | 32 | // As long as we are colliding with a trigger collider 33 | private void OnTriggerStay(Collider other) 34 | { 35 | // Check if the object has the tag 'Door' 36 | if(other.tag == "Door") 37 | { 38 | // Show the instructions 39 | instructions.SetActive(true); 40 | // Get the Animator from the child of the door (If you have the Animator component in the parent, 41 | // then change it to "GetComponent") 42 | Animator anim = other.GetComponentInChildren(); 43 | // Check if the player hits the "E" key 44 | if(Input.GetKeyDown(KeyCode.E)) 45 | anim.SetTrigger("OpenClose"); //Set the trigger "OpenClose" which is in the Animator 46 | } 47 | } 48 | 49 | // Once we exit colliding with a trigger collider 50 | private void OnTriggerExit(Collider other) 51 | { 52 | // Check it is a door 53 | if (other.tag == "Door") 54 | { 55 | // Hide instructions 56 | instructions.SetActive(false); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Unity Tutorials/Intro to UI/README.md: -------------------------------------------------------------------------------- 1 |

Unity | UI and Menus

2 | This script is from a short tutorial explaining the basics of Unity's UI system.
3 | For more information, you can watch the full tutorial through this link:
4 | https://www.youtube.com/watch?v=40RQqhTGEo4 5 | -------------------------------------------------------------------------------- /Unity Tutorials/Intro to UI/Scripts/ColorTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class ColorTest : MonoBehaviour { 6 | 7 | public Material blue; 8 | public Material red; 9 | 10 | public void changeBlue(){ 11 | gameObject.GetComponent().material = blue; 12 | } 13 | 14 | public void changeRed(){ 15 | gameObject.GetComponent().material = red; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Unity Tutorials/Save&Load/README.md: -------------------------------------------------------------------------------- 1 |

Unity | Save & Load

2 | This snippet is form the "Unity | Save & Load" tutorial which you can watch here:
3 | https://www.youtube.com/watch?v=FwiRf-pvsD4 4 | -------------------------------------------------------------------------------- /Unity Tutorials/Save&Load/Scripts/SavingTutorial.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class SavingTutorial : MonoBehaviour { 6 | 7 | // Use this for initialization 8 | void Start () { 9 | // Check if the key exists 10 | if (PlayerPrefs.HasKey("PlayerLevel")) 11 | { 12 | // If it's there, then load it and store it in a vairable 13 | int playerLevel = PlayerPrefs.GetInt("PlayerLevel"); 14 | print("Player level is " + playerLevel); 15 | }else{ 16 | // Otherwise, we will save the value as a new one 17 | PlayerPrefs.SetInt("PlayerLevel", 4); 18 | print("Player level was saved successfully"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Unity Tutorials/SceneLoader/README.md: -------------------------------------------------------------------------------- 1 |

Unity | Loading Scenes

2 | Loading scenes in Unity is simple.
3 | So this is a simple tutorial for beginenrs to load other scenes using a custom public function which can be used from outside (Ex: Buttons).
4 | Watch the full tutorial here:
5 | https://www.youtube.com/watch?v=iqooL4o4V-E 6 | -------------------------------------------------------------------------------- /Unity Tutorials/SceneLoader/Scripts/SceneLoaderScript.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.SceneManagement; 5 | 6 | public class SceneLoaderScript : MonoBehaviour 7 | { 8 | public void goToScene(string sceneName) 9 | { 10 | // Loading the passed scene name 11 | SceneManager.LoadScene(sceneName); 12 | } 13 | } -------------------------------------------------------------------------------- /Unity Tutorials/Simple Game - series/README.md: -------------------------------------------------------------------------------- 1 |

Unity | Develop Your First Simple Game (Series)

2 | Hi all!
3 | These scripts here are from the "Develop Your First Simple Game" series which I created.
4 | The series / playlist is consisted of Unity tutorials showing you how to create a simple game that teaches you the basics of Unity and coding.
5 | Start creating your first game right now !
6 | Link to the series:
7 | https://www.youtube.com/watch?v=GCfxcxrwSEo&list=PLaqp5z-4pFi4ZyzcMoGvGkqrY2gnYTJPI 8 | -------------------------------------------------------------------------------- /Unity Tutorials/Simple Game - series/Scripts/Coin.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class Coin : MonoBehaviour { 6 | 7 | // Update is called once per frame 8 | void Update () { 9 | transform.Rotate(Vector3.right, transform.rotation.y + 10.0f); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Unity Tutorials/Simple Game - series/Scripts/DestroyAfter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class DestroyAfter : MonoBehaviour { 6 | public float time; 7 | // Use this for initialization 8 | void Start () { 9 | Destroy(gameObject, time); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Unity Tutorials/Simple Game - series/Scripts/Player.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.UI; 5 | using UnityEngine.SceneManagement; 6 | 7 | public class Player : MonoBehaviour { 8 | 9 | public Rigidbody rb; 10 | public float speed = 90.0f; 11 | private float maxSpeed = 9.0f; 12 | public int points = 0; 13 | public Text text; 14 | public GameObject coinSound; 15 | public AudioClip c; 16 | 17 | void FixedUpdate(){ 18 | if(rb.velocity.magnitude < maxSpeed) 19 | rb.AddForce(new Vector3(Input.GetAxisRaw("Horizontal") * speed, 0, Input.GetAxisRaw("Vertical") * speed)); 20 | } 21 | 22 | void OnTriggerEnter(Collider col){ 23 | if(col.tag == "Coin"){ 24 | AudioSource.PlayClipAtPoint(c, col.transform.position); 25 | Destroy(col.gameObject); 26 | points++; //points = points + 1; points += 1; 27 | text.text = points.ToString(); 28 | // Instantiate(coinSound, col.transform.position, Quaternion.identity); 29 | } 30 | 31 | if(col.tag == "Out"){ 32 | transform.position = new Vector3(0.0f, 1f, 0.0f); 33 | } 34 | } 35 | 36 | void OnCollisionEnter(Collision col){ 37 | if(col.collider.tag == "Fire"){ 38 | SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Unity Tutorials/Simple Game - series/Scripts/Spawner.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class Spawner : MonoBehaviour { 6 | 7 | public float waitTime; 8 | private float timer; 9 | public Rigidbody fireBall; 10 | 11 | void Update(){ 12 | if(timer > 0.0f) 13 | timer -= Time.deltaTime; 14 | if(timer <= 0.0f) 15 | spawnBall(); 16 | } 17 | 18 | public void spawnBall(){ 19 | Rigidbody tempBall = Instantiate(fireBall, transform.position, Quaternion.identity) as Rigidbody; 20 | tempBall.AddForce(new Vector3(Random.Range(-1, 1), Random.Range(-10, 10), Random.Range(-1, 1)) * Random.Range(100, 200)); 21 | timer = Random.Range(waitTime, waitTime + 3.0f); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Unity Tutorials/Unity_IAP/README.md: -------------------------------------------------------------------------------- 1 |

Unity | Adding In-App Purchase to Your Game

2 | Unity is implementing In-App Purchase (IAP) in a simple, almost coding-less way.
3 | The script you will find is only to handle the product when the purchase is completed.
4 | You can watch full tutorial here:
5 | https://www.youtube.com/watch?v=wKdYGfAZC84 6 | -------------------------------------------------------------------------------- /Unity Tutorials/Unity_IAP/Scripts/StoreManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.Purchasing; 5 | using UnityEngine.UI; 6 | 7 | public class StoreManager : MonoBehaviour { 8 | 9 | // To update the text that has your current points 10 | public Text diamonds_text; 11 | 12 | // This function gets called once a purchase is complete 13 | public void OnPurchaseCompleted(Product product){ 14 | // Check if the product exists 15 | if(product != null){ 16 | // Checking the product's id (Ex: com.example.diamonds.500) 17 | switch(product.definition.id){ 18 | case "diamonds.500": 19 | print("You have successfully purchased 500 diamonds !"); 20 | // Convert string to integer 21 | int current_diamonds = int.Parse(diamonds_text.text); 22 | // Converting back the integer to a string 23 | diamonds_text.text = (current_diamonds + 500).ToString(); 24 | break; 25 | default: 26 | // If the id is not covered, then just print it is not there 27 | print("Sorry, this product is not defined :("); 28 | break; 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Unity Tutorials/Upgradeable Objects/README.md: -------------------------------------------------------------------------------- 1 |

Unity | Upgradeable Objects

2 | If you want to make different levels of the same object upgradeable to switch from one level to another in your Unity game.
3 | Watch this tutorial to learn how to switch between objects to use it as an upgrade feature:
4 | https://www.youtube.com/watch?v=mz9NGD21VVA -------------------------------------------------------------------------------- /Unity Tutorials/Upgradeable Objects/Scripts/SwitchingLevels.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class SwitchingLevels : MonoBehaviour { 6 | 7 | // Storing different levels' 8 | public GameObject[] levels; 9 | // Counting current level 10 | int current_level = 0; 11 | 12 | public void Upgrade(){ 13 | // Check if we're safe to upgrade (We haven't reached the last level) 14 | if(current_level < levels.Length - 1){ 15 | // Increase current level 16 | current_level++; 17 | // Switch to the updated level 18 | SwitchObject(current_level); 19 | } 20 | } 21 | 22 | void SwitchObject(int lvl){ 23 | // Count from zero the last level in our array 24 | for (int i = 0; i < levels.Length; i++){ 25 | // Check if we're in the desired level, then activate 26 | if (i == lvl) 27 | levels[i].SetActive(true); 28 | else 29 | //Otherwise, hdie it 30 | levels[i].SetActive(false); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Unity Tutorials/Virtual Joysticks/README.md: -------------------------------------------------------------------------------- 1 |

Unity | Adding Joystick Control for Mobiles

2 | Unity is providing virtual joysticks under one of its free packages.
3 | The "Player.cs" script you will find, is a simple script showing you how to take input from that virtual joystick and apply it to the player's movement.
4 | Check out the full tutorial here:
5 | https://www.youtube.com/watch?v=yggYAUC-_-c 6 | -------------------------------------------------------------------------------- /Unity Tutorials/Virtual Joysticks/Scripts/Player.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.UI; 5 | using UnityEngine.SceneManagement; 6 | using UnityStandardAssets.CrossPlatformInput; 7 | 8 | public class Player : MonoBehaviour { 9 | 10 | public Rigidbody rb; 11 | public float speed = 90.0f; 12 | private float maxSpeed = 9.0f; 13 | public int points = 0; 14 | public Text text; 15 | public GameObject coinSound; 16 | public AudioClip c; 17 | 18 | void FixedUpdate(){ 19 | if(rb.velocity.magnitude < maxSpeed) 20 | rb.AddForce(new Vector3(CrossPlatformInputManager.GetAxisRaw("Horizontal") * speed, 0, CrossPlatformInputManager.GetAxisRaw("Vertical") * speed)); 21 | } 22 | 23 | void OnTriggerEnter(Collider col){ 24 | if(col.tag == "Coin"){ 25 | AudioSource.PlayClipAtPoint(c, col.transform.position); 26 | Destroy(col.gameObject); 27 | points++; //points = points + 1; points += 1; 28 | text.text = points.ToString(); 29 | // Instantiate(coinSound, col.transform.position, Quaternion.identity); 30 | } 31 | 32 | if(col.tag == "Out"){ 33 | transform.position = new Vector3(0.0f, 1f, 0.0f); 34 | } 35 | } 36 | 37 | void OnCollisionEnter(Collision col){ 38 | if(col.collider.tag == "Fire"){ 39 | SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Unity Tutorials/WaypointMarker/Assets/waypoint-marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OBalfaqih/Unity-Tutorials/b03dfde64b69c025095b5b569e91386f65593362/Unity Tutorials/WaypointMarker/Assets/waypoint-marker.png -------------------------------------------------------------------------------- /Unity Tutorials/WaypointMarker/README.md: -------------------------------------------------------------------------------- 1 |

Waypoint Marker | Unity

2 |

In this tutorial I will show how to add mission or quest indicator UI (The little icon that tells you where to go).

3 |

We will cover: 4 |

    5 |
  • Showing a marker on the target
  • 6 |
  • The waypoint UI will stick to the sides of the screen
  • 7 |
  • Adding meter on the icon (Distance between the player and the target)
  • 8 |
9 |

10 |

Watch the full tutorial here

11 | -------------------------------------------------------------------------------- /Unity Tutorials/WaypointMarker/Scripts/MissionWaypoint.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.UI; 5 | 6 | public class MissionWaypoint : MonoBehaviour 7 | { 8 | // Indicator icon 9 | public Image img; 10 | // The target (location, enemy, etc..) 11 | public Transform target; 12 | // UI Text to display the distance 13 | public Text meter; 14 | // To adjust the position of the icon 15 | public Vector3 offset; 16 | 17 | private void Update() 18 | { 19 | // Giving limits to the icon so it sticks on the screen 20 | // Below calculations witht the assumption that the icon anchor point is in the middle 21 | // Minimum X position: half of the icon width 22 | float minX = img.GetPixelAdjustedRect().width / 2; 23 | // Maximum X position: screen width - half of the icon width 24 | float maxX = Screen.width - minX; 25 | 26 | // Minimum Y position: half of the height 27 | float minY = img.GetPixelAdjustedRect().height / 2; 28 | // Maximum Y position: screen height - half of the icon height 29 | float maxY = Screen.height - minY; 30 | 31 | // Temporary variable to store the converted position from 3D world point to 2D screen point 32 | Vector2 pos = Camera.main.WorldToScreenPoint(target.position + offset); 33 | 34 | // Check if the target is behind us, to only show the icon once the target is in front 35 | if(Vector3.Dot((target.position - transform.position), transform.forward) < 0) 36 | { 37 | // Check if the target is on the left side of the screen 38 | if(pos.x < Screen.width / 2) 39 | { 40 | // Place it on the right (Since it's behind the player, it's the opposite) 41 | pos.x = maxX; 42 | } 43 | else 44 | { 45 | // Place it on the left side 46 | pos.x = minX; 47 | } 48 | } 49 | 50 | // Limit the X and Y positions 51 | pos.x = Mathf.Clamp(pos.x, minX, maxX); 52 | pos.y = Mathf.Clamp(pos.y, minY, maxY); 53 | 54 | // Update the marker's position 55 | img.transform.position = pos; 56 | // Change the meter text to the distance with the meter unit 'm' 57 | meter.text = ((int)Vector3.Distance(target.position, transform.position)).ToString() + "m"; 58 | } 59 | } 60 | --------------------------------------------------------------------------------