UNITY 2D Action Tutorial: Platformer and Top-Down Gameplay
This tutorial covers 22 modules for Unity Action 2D Games: Platformers and Top-Down / Isometric. This includes Player Movement, Battling, helpful and enemy NPCs, Interactive Environments and Hazards, Inventory and Pause menus, Audio and Game Feel.
Peruse each section menu to get inspired! Read about how to work in teams! You are not expected to use all of these tutorials in your game: pick and choose the ones that work for your design.

PART 1: BASIC PLATFORMER SET-UP
[1A]: Environment: Tilemapping vs Parallax
[1B]: Platformer Player Movement (with gravity)
[1C]: Top-Down Player Move / Setting
[1D]: Camera Follow: LERP
[1E]: GameHandler: Player State, Score/Health UI
[1F]: Battle: Melee, Shoot, Projectiles, Defense
[1G]: Frame Scenes: MainMenu, Win / Lose
[1H]: Menus: Pause, Shop, and MiniMap
[1I]: Level Changes: Switches and Doors
[1J]: Multi-Player: Local Inputs vs Netcode
[1k]: Read / Write to a JSON file
PART 2: ADVANCED SETTINGS
[2A]: Pickups / Inventory, boosts, keys, random
[2B]: Respawn, Checkpoints, Lives, Map Doors
[2C]: Interactive Environments and Hazards
[2D]: NPC Enemy: Melee, Shooting, Patrol
[2E]: NPC Friendly: Dialogue, Flocking
[2F]: More Moves: Teleport, Hookshot, Fly, Dash
[2G]: Game Feel (Feedback VFX / SFX)
[2H]: Adding a Timer or a Recharge system
[2I]: Adding Drag and Drop and a Notepad
[2J]: Adding Music and Audio Events 
[2K]: Intro Animation (add an Opening Comic)

 
HOW TO NAVIGATE THESE TUTORIALS
  Using These Tutorials   |   Organizing a Team Project   |  Starting your Project
This site contains many tutorials for a wide range of features in a 2D Platformer, Sidescroller, or Isometric game.
Each tutorial contains the relevant steps and scripts for setting up a feature: making GameObjects, adding Physics2D and script Components, adding art and audio, etc.

To find the feature you want to implement on this page, read through the index list at the very top of tutorial sections, click the relevant link, open the rolldown, and navigate within that section. For example, if you want to implement Doors to move between levels, go to section [1I].

Most tutorial sections include multiple options or features. The top includes an index of that section for easy navigation, as you can see, above. To return to the section top, scroll up or click the purple triangle .
To return to the main index at the top of this entire page, click the white arrow at a section top .


 HOW TO USE THESE TUTORIALS
To use a tutorial follow its steps: read every step and implement them in the order presented.
Missing any part of a Unity tutorial can result in a feature not working as intended.
The good news is, any problem or error can be fixed, so dive in, try it out, and ask for help if you get stuck!

If you want a feature that is not yet explained on this site, start by going to YouTube to search for a tutorial.
Type "Unity tutorial" and then the feature you are seeking, like "Unity tutorial platformer wall-jumping." Watch a few tutorials on the subject to get a sense of which solution you might want to try.
If you are still stuck, email your teacher: JasonWiserArt@gmail.com.
NOTE: If a tutorial listed here is based on a YouTube tutorial, there is a "source" link at the top which can be clicked to view the original video.

Your game will need many of the tutorials and scripts on this page, but not all of them.
Add each mechanic thoughtfully: follow all the steps described, and only add the scripts needed for the feature you are implementing. Some scripts, like the GameHandler, will need to be updated when adding other scripts. This is because the GameHandler is meant to hold the Player State, and other scripts may want to refer to or update that State.
ScriptName.cs script:
Lighter blocks like this one contain the code for each script in this tutorial.

The Class name should always match the script file name.
Create a new C# Script in Unity named for the provided script, DoubleClick to open in your Script Editor, and then copy and paste the content of these light gray blocks into the new script to replace the default content. Save and apply to a GameObject as instructed to playtest the feature in Unity.

Don't be afrad to modify these scripts to get functionality specific to your intended game mechanics;
the original is always waiting here for you if you want to change back!


Some scripts in these tutorials depend on other scripts here to function.
Some directly reference another script. For example, the player melee script directly references the enemy damage script. Adding the first without the second will create an error in Unity (that is easy to fix).
Other scripts depend on the specific implementation of other features to function. The enemy knockback, for example, depends on the player movement script to NOT zero-out the velocity. The player movement scripts provided here work well with the enemy knockback.



  HOW TO DIVIDE UP TASKS FOR A TEAM PROJECT
Making an entire multi-level game all on your own is daunting. Game Development is a team sport!

1. BACKLOG (Concepts from Agile development):
Create a Backlog (prioritized list) of all the functionlity you want to see in your game.
Make a shared document, like a Google Doc, so ever team member has easy access to view and update it.
Include in this list all intended mechanics: player verbs (what the player can do to the game), game verbs (what the game can do to the player), intended levels, player and NPC characters, pickups, power-ups, environment hazards, doors, HUD content and other elements you want included.

Prioritize this list based on what will be most important to the user experience ("User Stories"):
(A) = Critical: The most vital functionality for your core concept, to test a working level in your game.
(B) = Important: Necessary for the game to be complete, like building multiple levels with a rising difficulty curve and populating them with revised art, audio, and Game Feel, but implemented after all of (A) is done.
(C) = Hopefulls: Features you hope to include, after (B) is all complete. Cool, but the game works without them.
(D) = No Way: Fun to imagine, but no plan to implement in your alloted time. Honor your edge ideas here!
After each item, list art and audio asset needs (in parenthesis).


2. WEEKLY PLANS:
Each week ("Sprint"), talk with your team about which high priority elements you will complete that week, and document below your Backlog what each person has agreed to take on. Sometimes not everything will get done, and that is OK-- many features can take longer than we expect. Make a good faith effort to complete your tasks, be in daily communication with your team about your progress, and alert the team if you get stuck.

Not sure what you could or should be doing?
Consult your team Backlog for available tasks and talk with your teammates to be sure no one else is currently working on the task you are considering!


3. PLACEHOLDER ART AND SCRATCH AUDIO:
Use placeholder art and scratch audio for all features to test functionality as soon as possible, and then replace the art as better PNGs and audio are made. Work as quickly as possible to add features and test them, so you have time for both design and functionality iteration!


4. GITHUB: ONLY ONE USER PER ASSET AT A TIME:
Remember, MANY people can work on the same Unity GAME at a time, but any collaboration in Unity means only ONE person can update a specific ASSET at a time.
If two creators change the same asset, especially a Unity Scene, one craetor will need to discard their work, pull the other person's pushed work in GitHub, and re-add their changes to the udpated file.
Please communicate with each other about who is working on what asset at what time!


5. CREATE PREFABS TO REDUCE MERGE CONFLICTS:
A great way to work together around the Unity Scene exclusivity problem is to make heavy use of Prefabs.
For example, if the Pause Menu is in all scenes as a Prefab, Creator1 can work on a Scene and Creator2 can work on the PauseMenu Prefab in the Prefabs folder without creating a merge conflict in GitHub.
The player, all enemies and pickups, friendly NPCs, etc should be Prefabs!

This also helps with feature distribution across Scenes. Make Prefabs for all duplicating elements, like a combo Canvas and GameHandler for all gameplay Scenes!



 GETTING YOUR PROJECT STARTED
1. CREATE A GITHUB REPOSITORY with the default Unity .GitIgnore.
On Unity.com replace the default .GitIgnore with this correct Unity GitIgnore.
Clone this repo into a folder on the desktop. Never chaneg the name and location after cloning.
Open Unity Hub and create a new Unity 2D project in this repository folder.

2. MAKE ASSET FOLDERS:
In the Unity Project panel Assets folder, RightClick to create new folders for Media, Scripts, and Prefabs.
NOTE: these folders will only get shared over GitHub if they contain files.


3. NOTES ON MAKE SPRITE PNGs:
a) SIZE: All art for a 2D game should be created and imported in the intended final size.
For interactive content like character or effect spritesheets use power-of-two-square: 256x256, 512x512, 104x1024, etc, always 72ppi.
Images meant to fill a static background can be 1280 x 720, 72ppi to fit a 16:9 ratio screen (like MainMenu splash screens), but environment Tilemaps and Spritesheets must be power-of-two-square.
Create a test image of the character and background to see the sizing in game before comitting lots of time to art production!

b) SPRITESHEETS: Tilemap backgrounds and animations should be imported as Spritesheets.
Your art can use any desired style. Please see this tutorial for working with pixel art and spritesheets.

c) TRANSFORMS: Every environment or character Sprite added to a 2D Scene should have Position Z = 0.
The only Scene object that should have a Position Z other than 0 is the Main Camera, which should be -10.

d) ORDER IN LAYER: To choose which Sprites display in front of others, set higher Order In Layer values
(try Character = 100, platforms = 90, enemies and pickups = 95).

e) PARENT INTERACTIVE SPRITES TO EMPTY GAME OBJECTS: All interactive elements, like the Player, Enemy NPCs, Doors, Pickups, etc, should distinguish between functionality and graphics by putting all Physics and Script Components onto an Empty Game Object, and parent the Sprite art to that EmptyGO. This allows any animation on the Sprite to work independantly from the Physics functionality on the object.

FOR EXAMPLE:The Player will be an EmptyGO named "Player" with Components for Physics2D > Rigidbody2D and BoxCollider2D, and all scripts for player Movement, Jump, etc.
Art will be a child called "player_art", with the Animator component that holds the animation state machine.
The Player scripts access this Animator on the child using gameObject.GetComponentInChildren<Animator>();


4. PHYSICS 2D:
All Physics in a 2D game must use Physics2D options: Rigidbody2D and BoxCollider2D or CircleCollider2D on all moving elements, TilemapCollider2D and CompositeCollider2D on Tilemaps.
Be sure all code references to Rigidbody and Colliders are 2D: gameObject.GetComponent<Rigidbody2D>();


 
[1A]: PLATFORMER ENVIRONMENT:
1. Tilemapping   |   2. Static Sprites   |   3. Tiling Sprites   |   4. Parallax
5. Foreground Sprites Leaves Blowing

2D Sprite Notes: There are multiple ways to organize your backgrounds.
Start by considering the collide-able objects and the non-collidable objects.

In a side-oriented PLATFORMER:
The collide-able objects are surfaces the Player can jump on and the walls that stop their progress.
These are usually best made with a Timemap spritesheet. Tilemap layers can also be used for hazards like damage-spikes or lava.
The non-collideable objects are images in the background and the foreground that the player moves past, which can be made with more tilemap layers OR with large individual sprites.

In a TOP-DOWN game:
The collide-able objects are the shapes that block player movement.
Non-collideable spaces are the background the player walks on and foreground objects they walk beneath.
These are all best made with Timemap Spritesheets painted into Unity Tilemap layers.
Additional Tilemap layers can be used for hazards like damage-spikes.

ORDER IN LAYER:
Set all Scene content to Z-Position = 0, except the Camera, with should be Z = -10.
The display order (what is in front, what is in back) is controlled by the Order In Layer setting in the Inspector (look under SpriteRenderer for each image). The higher numbers appear in front (set Player = 100).

IMAGE SIZES: PNGs, POWER-OF-TWO SQUARE, 72ppi:
All art imported into Unity should be PNGs sized power-of-two-square and 72ppi.
An animated pixel art character sheet, for example, will typically be 256x256, with each pose frame set to fit in a 64x64 square, with consistent feet positions. This allows for 16 animation positions (4x4 grid).
For art drawn with anti-aliasing, try 1024x1024 with each frame set to 256x256 (also a 4x4 grid).

SIZE UNIFORMITY:
Spritesheets for Tilemaps and character animation must have uniform content (all images fitting into rectangles of the same size) so they can be sliced uniformly in Unity by cellcount or cell size.
For random background sprites, the images can be varied sizes, and sliced using the automatic setting (but the images still need to fit into a non-overlapping grid PNG that is power-of-two square).
Individual sprites can also be used, and should be power-of-two square PNGs, with as little qsted space around them as possible.



 OPTION 1: Tilemapping (grid layers of small images):
Follow this Tilemapping Tutorial to make a long Platformer using layers of Tilemaps.
Each layer is "painted" on the Unity Grid out of 16x16 squares.
Make these layers (each one with a progresively higher Order in Layer to display in front of the previous layers):
    Background 1: Far background for sky or back wall.
    Background 2 Hills or architecture that are non-collide-able in the background.
    Collider The layer for platforms, walls, and ground that will stop/support the player movement.
    Foreground layer for thin content for the player to pass behind (trees, grass, vines, pillars, etc).

Tilemap Collision Note:
The collide-able Tilemap layer or layers usually require 3 Components:
    Tilemap Collider with "Used by Composite" enabled.
    Rigidbody2D
with Body Type set to "Static".
    Composite Collider with Geometry type set from Outlines to Polygons (to prevent player jumping inside).
This arrangement is good for the player to collide with all sides of each object.

Alternatively, consider a 2-component system, to allow players to jump up through platforms, to land on top:
    Tilemap Collider with "Used by Effector" enabled.
    Platform Effector 2D. Under "One Way" enable "Use One Way" and set "Surface Arc" to 180 degrees.

Tilemapping is great for Platformers and Top-down Isometric exploration games. The art style is usually pixel-based, as that tends to be easier to manage in 16x16 squares, but other options may be explored.


 OPTION 2: Static Sprites (manually placed large images)
Sidescroller game backgrounds can be built with large, square, tiling PNG paintings instead of tiny images in Tilemap Grids.
These large sprites are in 1-point perspective: flat back interior walls or flat sides of buildings, with up to the half of the bottom devoted to the floor, ground, or road that the player can move around.

A long level with lane-based motion, for example, can have a long, repeating background built with just a few png images sized 1024x1024, 72ppi.
With snaps turned on, these images can be arranged horizontally to create a long hallway or a city street, populated by obstacles, enemies and other NPCs.
Colliders are added and sized manually to a parent object to prevent the player from wandering out of the intended space.


On the other hand, a contained space like a room can be built with a single png sized 2048x2048, 72ppi.
The playable space would be closer to 1280x720 at the center of this image, surrounded by art that is seen as the camera follows player movement around the space.
Colliders are added to a parent object and sized manually to prevent the player from wandering out of the intended space.


To set up a Sidescroller background:
1. Create an Empty Game Object called "LevelBG".
Reset Transforms (important!).

2. Drag the 1024x1024 background sprites directly into the Hierarchy.
Reset Transforms (position Z MUST = 0).
Move them as desired. Try turning on the Snaps button in the top toolbar, or zoom in to close gaps.
Drag all these background images onto LevelBG to make them children.
Behind all images, consider putting a large black 2D sprite for the space outside of the playable space.

A player character in a Sidescroller game typically has Gravity Scale=0, so the up and down arrows imply movign forward and back on the ground. Colliders for the wall, bottom, and sides prevent the player from moving outside of the intended space:

3. With LevelBG selected [Add Component] Phyics2D > BoxCollider2D.
Hit the colliders editor button to adjust the size and position. The bottom should be near the base of the back wall and extend upwards and as wide as the tiled images.
Add a second BoxCollider2D for the bottom of the lanes
Add a third and fourth BoxCollider2D for the left and right sides of the entire level.

4. Add Doors and obstacles as separate objects with colliders,
For each, create a named Empty Game Object. Reset transforms.
Drag in the art, reset transforms, drag onto the Empty GO to make it a child.
Select the Empty GO to add [Add Component] Phyics2D > BoxCollider2D, and size to fit the art.

Consider cutting windows or alleys out of these big PNGs to allow parallax motion behind them:


 OPTION 3: Tiling Sprites: "lay track" to follow player
Platformer game backgrounds can be built with large, square, tiling PNG paintings instead of tiny images in Tilemap Grids. Lay these tiling images in a row, and as the player travels from left to right, the left-most background image gets put at the right end, so the background seems infinite!


[Version A] Simple, Single-Layer Shift (Source)
This version is great for very simple backgrounds, like underwater or in outer space (video shows vertical tiling, the script below is for horizontal tiling, and could be modified for both):
1. Create and import a horizontally oriented background. Drag into the Unity Hierarchy, name BG_Center.
2. Duplicate twice, name them BG_Left and BG_Right. Position exactly at left and right sides of BG_Center.
(hold down [V] key to snap: Position Y and Z values should be zero on all three).
3. Check the Position X of each side BG image. This value is the same as the image width, and will be our Offset value (in a 1280x720 image, the offset is 1280).
4. Drag both side BG images onto BG_Center, to make them children (only BG_Center gets the script).

backgroundLoopSimple.cs script:
using System.Collections.Generic;
using System.Collections;
using UnityEngine;

public class backgroundLoopSimple : MonoBehaviour {
      private Transform centerBG;
      public float offset = 1280f;       //this value is the width of the image

      void Update(){
            if (transform.position.x >= centerBG.position.x + offset){
                  centerBG.positon = new Vector2(transform.position.x + offset, centerBG.position.y);
            }
            else if (transform.position.x <= centerBG.position.x - offset){
                  centerBG.positon = new Vector2(transform.position.x - offset, centerBG.position.y);
            }

      }
}

 


[Version B] Complex, Multi-Layer Robust Shift (Source)
This version can be used with multi-layer real world scenes.
1. Create a new C# script "backgroundLoop.cs" containing the content at this link (also available here). Save.
In Unity add the script to the Main Camera in your Scene.

2. In Unity, import your background art layers:
a) Select the Project > Assets folder, RightClick, choose Import New Asset, find your background art.
Consider a desert scene with a Sky, Mountains, Desert, and TelephonePoles.
b) Drag each into the Hierarchy.
c) In the Inspector, set the Transform > Position value on each to (0, 0, 0)
d) Set their Sprite Renderer > Additional Settings > Order In Layer values so the Sky is lowest (furthest back) and the foreground is the highest (most forward): 0, 10, 20, 30.

3. With the Camera selected, find the Backgrounds array.
Set the array number to the desired number of backrounds (for this example, choose 4)
Drag all of your backgrounds into the Array slots, in order from background to foreground
Hit Play to test: the script clones the backgrounds and lays them before the player as the player moves (test by moving the camera by hand in the Scene view)


  OPTION 4: Parallax Backgrounds:
An exciting way to add a dynamic element of depth to a sidescroller is to include parallax motion to the background: the appearance that distant elements of the environment move past slower than closer elements.

Import into the Asset folder: Background sky and city sprites, and Character images (NOTE: These scripts also work on Tilemap grids! Notes to come).


STEP 1. SETTING: SPRITE LAYERS AND QUADS
In Unity 2D art is added as Sprites.
To determine which sprites appear in front of others:
a) they can occupy different Sorting Layers, OR
b) within a Sorting Layer they can be assigned a different Order in Layer.
The only Sorting Layer at the start is "Default". Click Default to Add a Sorting Layer.
The lower the Layer in that list, the more in-front its contents appears in the scene.
Within a Sorting Layer, multiple Sprites can be arranged by simply putting those intended to be in front with higher Order in Layer numbers.

EXAMPLE: This project has three Sprites for the sky:
tileSky_blue, tileSky_clouds, and tileSky_wave.
  • We can drag them all into the Hierarchy and select one to find Sorting Layer in the Inspector.
  • Click "Default," add new Sorting Layer "BG_Sky." Move the Layer above Default (so it is furthest back).
  • Select each of these 3 sky sprites to add them to that Sorting Layer.
  • Select tilingSky_blue to set Order in Layer to 2. Set tileSky_wave to Order in Layer 4 (to be in front of tileSky_blue), and set tileSky_clouds to 6.
    (To start the next tutorial, delete these sprites from Hierarchy).


    STEP 2. SKY INFINITE SCROLLING (sources: sprite settings and scrolling)
    Create a scrolling parallax effect with the 3 sky sprites applied to Quads and scrolled at varied speeds.
    NOTE this script does not work on ordinary sprites; they need to be applied to a 3D Quad mesh:
    a. Select each sky image in the Project, change Mesh Type from "Tight" to "Full Rect," Wrap Mode from "Clamp" to "Repeat," compression = none, (hit [Apply]).

    b. RightClick the Hierarchy to create a 3D Object > Quad. Scale X=18 and Y =10 to fill the stage.

    c. With the Quad selected, drag a sky sprite onto the Inspector to create and apply a Material.
    Set the Material Shader to Unlit/Texture for full brightness.
    Find this Material in the Materials folder and duplicate it twice: select it and hit [Cmd/Ctr]+[d].
    With these other two Materials selected, hit [select] in the Inspector to add the other sprites.

    d. In the Hierarchy, duplicate the Quad twice and apply the two other sky sprites, so each sprite has a Quad.

    e. To push a Quad behind the others, set Position Z slightly higher (like 0.1).

    f. Drag the BG_Scroll.cs script to each of the sky Quads in the Hierarchy.
    BG_Scroll.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class BG_Scroll : MonoBehaviour {

          public float BGspeed = 0.5f;
          public Renderer BGrend;

          void Start(){
                BGrend = GetComponent<Renderer>();
                //Change the GameObject's Material Color to red
                //BGrend.material.color = Color.red;
          }

          void Update(){
                Vector2 hOffset = new Vector2 (Time.time * BGspeed, 0);
                BGrend.material.mainTextureOffset = hOffset;
          }
    }

    //https://www.youtube.com/watch?v=32EIYs6Z18Q

    g. In the Inspector, change the BGspeed value for varied speeds.

    h. Clean up: create an EmptyGO, call it BG_Sky. Drag the sky quads onto it to make them children.



    STEP 3. CITY PARALLAX TO CAMERA MOTION (source)
    With the 3 city background layers, we can make a dynamic parallax effect that responds to player movement.
    This is a good system if you do not intend the player to travel very far:

    a. Create an Empty GameObject, name it BG_City. Drag onto the Camera to make it a child.

    b. Drag each city sprite into the Hierarchy, then onto BG_City to make them children.

    c. Set Order in Layer to 2 (city_back), 4 (city_mid), and 6 (city_front). In front of quads, set the Z-position higher than the quads (NOTE: these city sprites are added to the Default Sorting Layer, so are all automatically in front of any sky sprites on the BG_Sky Sorting Layer).


    d. Duplicate the layers twice.
    Move
    those duplicates to either side of the original.
    Position them as precisely as you can, so no seems show.
    If the image is 2,000 pixels long, the default pixels per unit = 100 means they side images can be Position X = 20 and X = -20.
    IMPORTANT: Drag each duplicate onto its original to make them children:
  • Drag city1_back(1) and city1_back(2) onto city1_back
  • Drag city2_mid(1) and city2_mid(2) onto city2_mid
  • Drag city3_front(1) and city3_front(2) onto city3_front
    The script in the next step only gets applied to the original parent images:


    e. Create a new C# script "BG_Parallax.cs" with the content below.
    Back in Unity, drag the script onto each original city sprite (the parent sprites only, not their children).
    Select one of these sprites and drag Camera into the script slot.
    For BG_Sky (the Empty GO holding the sky Quads), simply drag it onto Camera so it follows Camera absolutely.


    BG_Parallax.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class BG_Parallax : MonoBehaviour {

          private float length, startposX;
          //startposY;
          public GameObject cam;
          public float parallaxEffect;
          //parallaxUpEffect;

          void Start(){
                startposX = transform.position.x;
                //startposY = transform.position.y;
                length = GetComponent<SpriteRenderer>().bounds.size.x;
          }

          void FixedUpdate(){
                float temp = (cam.transform.position.x * (1 - parallaxEffect));
                float distX = (cam.transform.position.x * parallaxEffect);
                //float distY = (cam.transform.position.y * parallaxUpEffect);
                transform.position = new Vector3(startposX + distX, transform.position.y, transform.position.z);
                //transform.position = new Vector3(startposX + distX, startposY + distY, 1);
                if (temp > startposX + length){
                      startposX += length;
                }
                else if (temp < startposX - length){
                      startposX -= length;
                }
          }

    }


    f. Set different values for each Parallax Effect slot:
          city1_front = 0 (so it moves the most),
          city2_mid = 0.3,
          city_Back = 0.7,
          BG_Sky = 1 (so it does not move at all: doers not actually need the script).

    g. Hit play and in the Scene view, drag the camera back and forth to see the script work!

    CREATE TEST PLATFORM:
    Create an Empty GameObject, name it "TestPlatform."
    Add Physics2D > BoxCollider2D.
    Set Collider Size X to the width of original background, and Y to 0.5.
    Set Position Z = 0, to match the Player.


     
    FOREGROUND SPRITES: LEAVES BLOWING BY
    Want sprites to move in front of your tilemaps, like blowing leaves?
    Instead of Offset, we move the sprites themselves.

    a. Create a spritesheet PNG of a leaf blowing in the wind.
    Import into Unity, set to Multiple, hit [Apply], and in the Sprite Editor set slice = automatic, slice and apply.


    b.
    Create an animation with the leaves:
  • Drag the first into the Hierarchy, name it "leaf_art".
  • With leaf_art selected, open Window > Animation > Animation. [Create] to save a clip called "leaf_anim".
  • Drag all leaf frames into the Animation Timeline, and drag the first one again to be the end.
    Space the keyframes along the Timeline as desired.


    c. Create 3 Empty Game Objects, name them leaf1, leaf2 and leaf3 and Reset Transforms.
  • Make 2 copies of leaf_art and drag one each onto the leaf GOs to make them children.
  • Position the leaf GOs next to each other, side by side, directly to the right of the Camera frame.

    d. Create a new Empty Game Object called "FG_SPRITES_LEAVES".
    Reset Transforms and drag all three tiles onto it to make them children.

    e. Create a new C# script called "SpriteTiler.cs" with the content below, and apply to FG_SPRITES_LEAVES.
    Load each of the three leaf GOs into the script slots.
    NOTE: to move in the opposite direction, set the bool moveLeft = false and position the sprites on the left.

    SpriteTiler.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class SpriteTiler : MonoBehaviour {

          public Transform BG1;
          public Transform BG2;
          public Transform BG3;

          private Vector2 BG1Start;
          private Vector2 BG2Start;
          private Vector2 BG3Start;

          public float BGspeed = 8f;
          public float BGdistance = 40f;      //30- 60
          private Transform playerTarget;

          public bool moveLeft = true;

          void Start(){
                playerTarget = GameObject.FindWithTag("Player").GetComponent<Transform>();
                BG1Start = new Vector2 (BG1.position.x, BG1.position.y);
                BG2Start = new Vector2 (BG2.position.x, BG2.position.y);
                BG3Start = new Vector2 (BG3.position.x, BG3.position.y);
          }

          void FixedUpdate(){
                //set parent object to player x-position
                transform.position = new Vector2(playerTarget.position.x, transform.position.y);

                int moveDir = 1;
                if (moveLeft == false){moveDir = 1;}
                else {moveDir = -1;}

                float BG1rand = Random.Range(0.5f, 2f);
                float BG2rand = Random.Range(0.5f, 2f);
                float BG3rand = Random.Range(0.5f, 2f);

                float bg1Pos = moveDir * BG1rand * BGspeed * Time.deltaTime;
                float bg2Pos = moveDir * BG2rand * BGspeed * Time.deltaTime;
                float bg3Pos = moveDir * BG3rand * BGspeed * Time.deltaTime;

                BG1.localPosition = new Vector2(BG1.localPosition.x + bg1Pos,
                      BG1.localPosition.y + (BG1rand/20));
                BG2.localPosition = new Vector2(BG2.localPosition.x + bg2Pos,
                      BG1.localPosition.y + (BG1rand/20));
                BG3.localPosition = new Vector2(BG3.localPosition.x + bg3Pos,
                      BG1.localPosition.y + (BG1rand/20));

                if (Mathf.Abs(BG1.position.x - BG1Start.x) >= (Mathf.Abs(BG1Start.x) + BGdistance)){
                      BG1.localPosition = BG1Start;
                      BG2.localPosition = BG2Start;
                      BG3.localPosition = BG3Start;
                }
          }
    }






  •  
    [1B]: PLATFORMER PLAYER CREATION
    Player Set-Up   |   Movement   |   Jumping   |   Crouching   |   other: Squishy package

     STEP 1. CREATE PLAYER OBJECT
    a. Add an Empty GameObject. In Inspector name it Player, reset Transforms, set tag = Player.

    b. Import your character art (single or sprite sheet, (power-of-2 square PNGs with power-of-2 square character images: 64x64, 128x128, 256x256, 512x512 etc).
    In the Inspector set the following and hit [Apply]:
               Sprite Mode = Multiple
               Pixels Per Unit = 16
               Filter Mode = Point (no filter)
               Compression = None
    Then open the [Sprite Editor] and Slice by count: 4 x 4. Hit Apply.

    c. In the Project panel open the character to view all poses as individual sprites.
    Add the first Sprite to the Hierarchy for a Player character.

    d. In the Inspector Set Order in Layer = 100, and Reset Transforms. Name it PlayerArt.

    e. Drag PlayerArt onto the Player GameObject to make it a child.
    NOTE: Character art should be sized using Pixels Per Unit, NOT by changing scale in the Scene Inspector.
    Once the sprite is in the Scene, adjust its size by selecting the source image in the project panel and changing Pixles Per Unit and hitting [Apply]. Choose large numbers to shrink characters, smaller numbers make larger.

    f. Add these Components to the parent Player Game Object:
          Component Physics2D > BoxCollider2D and scale to fit the upper half of the art (the torso and head).
          Add Physics2D > CircleCollider2D and scale to fit the lower half (to more smoothly colide with ramps).
          Component Physics2D > Rigidbody2D, set Constraints > Freeze Rotation Z to on, and change Collision Detection from Discrete to Continuous.

    NOTE: All Player controls in this tutorial use the legacy Input Manager system in Unity.
    These can be seen and changed in the Edit > Project Settings > Input Manager.
    Movement and Jump use the default Horizontal Axis and Jump inputs.

    REMEMBER:
    Test initial character art in Unity BEFORE making a lot of poses!

    All of your art for Unity should be 72ppi, and drawn/ painted/ etc at the final intended size (like 64x64). Bring your PNGs into a Unity Scene to see how character look against backgrounds, to check your overall size and the thickness of lines, etc, before you create a hundred keyframes!


     STEP 2. PLAYER MOVEMENT (sources: simple vs controller-driven Movement)
    a. Create a new C# script called PlayerMove.cs.

    b. Add the content below and drag the script onto your Player.
    The commented-out code is for animation, if an Animator is applied to the character art.
    (Here is an alternate movement script).

    PlayerMove.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PlayerMove : MonoBehaviour {

          //public Animator animator;
          public Rigidbody2D rb2D;
          private bool FaceRight = true; // determine which way player is facing.
          public static float runSpeed = 10f;
          public float startSpeed = 10f;
          public bool isAlive = true;
          //public AudioSource WalkSFX;
          private Vector3 hMove;

          void Start(){
               //animator = gameObject.GetComponentInChildren<Animator>();
               rb2D = transform.GetComponent<Rigidbody2D>();
          }

          void Update(){
                //NOTE: Horizontal axis: [a] / left arrow is -1, [d] / right arrow is 1
               hMove = new Vector3(Input.GetAxis("Horizontal"), 0.0f, 0.0f);
               if (isAlive == true){
                      transform.position = transform.position + hMove * runSpeed * Time.deltaTime;

                      if (Input.GetAxis("Horizontal") != 0){
               
          //       animator.SetBool ("Walk", true);
               
          //       if (!WalkSFX.isPlaying){
               
          //             WalkSFX.Play();
                
          //      }
                      } else {
               
          //      animator.SetBool ("Walk", false);
               
          //      WalkSFX.Stop();
                      }

                      // Turning: Reverse if input is moving the Player right and Player faces left
                     if ((hMove.x <0 && !FaceRight) || (hMove.x >0 && FaceRight)){
                            playerTurn();
                      }
               }
          }

          void FixedUpdate(){
                //slow down on hills / stops sliding from velocity
                if (hMove.x == 0){
                      rb2D.velocity = new Vector2(rb2D.velocity.x / 1.1f, rb2D.velocity.y) ;
                }
          }

          private void playerTurn(){
                // NOTE: Switch player facing label
                FaceRight = !FaceRight;

                // NOTE: Multiply player's x local scale by -1.
                Vector3 theScale = transform.localScale;
                theScale.x *= -1;
                transform.localScale = theScale;
          }
    }

    PLAYER WALKING AUDIO:
    For basic footsteps, [Add Component] AudioSource to the Player with your footsteps sound.
    Disable "Play on awake" but make sure "Looping" is enabled.
    Uncomment the SFX lines in the PlayerMove script, including the if-condition.
    In the Unity Inspector, drag the footsteps AudioSource Component into the WalkSFX script slot.
    Hit Play and walk around!

    For a jump sound, [Add Component] a separate AudioSource to the Player with the jump sound.
    Disable both "Play on awake" AND "Looping".
    Uncomment the JumpSFX lines below.
    In the Unity Inspector drag the Jump AudioSource into the JumpSFX script slot.


     STEP 2. PLAYER JUMPING
    a. Create a new C# script called PlayerJump.cs. Add the content below and drag the script onto your Player.
    Note the commented-out code for animation, for when an Animator is applied to the character art.

    b. This script uses Inputs for Jump: keyboard Spacebar and controller Joystick Button 3 (on XBox controller: (Y)). Change these or add more options: Open Edit > Project Settings > Inputs.

    PlayerJump.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PlayerJump : MonoBehaviour {

          //public Animator anim;
          public Rigidbody2D rb;
          public float jumpForce = 20f;
          public Transform feet;
          public LayerMask groundLayer;
          public LayerMask enemyLayer;
          public bool canJump = false;
          public int jumpTimes = 0;
          public bool isAlive = true;
          //public AudioSource JumpSFX;

          void Start(){
                //anim = gameObject.GetComponentInChildren<Animator>();
                rb = GetComponent<Rigidbody2D>();
          }

         void Update() {
                if ((IsGrounded()) || (jumpTimes <= 1)){
                // if ((IsGrounded()) && (jumpTimes <= 1)){ // for single jump only
                      canJump = true;
                }  else if (jumpTimes > 1){
                // else { // for single jump only
                      canJump = false;
                }

               if ((Input.GetButtonDown("Jump")) && (canJump) && (isAlive == true)) {
                      Jump();
                }
          }

          public void Jump() {
                jumpTimes += 1;
                rb.velocity = Vector2.up * jumpForce;
                // anim.SetTrigger("Jump");
                // JumpSFX.Play();

                //Vector2 movement = new Vector2(rb.velocity.x, jumpForce);
                //rb.velocity = movement;
          }

          public bool IsGrounded() {
                Collider2D groundCheck = Physics2D.OverlapCircle(feet.position, 2f, groundLayer);
                Collider2D enemyCheck = Physics2D.OverlapCircle(feet.position, 2f, enemyLayer);
                if ((groundCheck != null) || (enemyCheck != null)) {
                      //Debug.Log("I am trouching ground!");
                      jumpTimes = 0;
                      return true;
                }
                return false;
          }
    }

    c. Set-up GroundPoint: RightClick the Player to create an Empty Game Object as a child.
    In the Inspector name it "GroundPoint", reset Transforms. Move it directly to the bottom of the Player's Collider / feet. Drag this GroundPoint into the PlayerJump "feet" script slot.

    d. Assign layers to ground and enemies:
    Select the environment Ground layer/s and in the Inspector add two new Layers: "Ground" and "Enemies." Apply Ground to the Ground layer/s.
    Select the Player and set the "groundLayer" LayerMask rolldown to Ground and the "enemyLayer" LayerMask rolldown to Enemies.
    IMPORTANT: The player will not jump at all if these last two steps are not done!

    e. Hit [Play] and test movement and jump!
    Do your Player character's colliders get stuck on things? Turn off [Play] and add a slippery new Physics Material to the Player collider material slots, with Friction and Bounciness set to zero (rightclick Project panel to create). If it is still an issue, be sure your player bottom collider is a circle, and consider adding circle colliders to the ends of platforms.

    f. BETTER JUMP: Does your Player jump feel too float-ty?
    Create a new C# script called "PlayerBetterFall.cs" with the content below.
    Save and add this script to the player to enhance the fall on each jump:

    PlayerBetterFall.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PlayerBetterFall : MonoBehaviour {

          public float fallMultiplier = 2.5f;
          public float lowJumpMultiplier = 2f;
          Rigidbody2D rb;

          void Awake(){
                rb = GetComponent <Rigidbody2D> ();
          }

          void Update(){
                if (rb.velocity.y < 0) {
                      rb.velocity += Vector2.up * Physics.gravity.y * (fallMultiplier - 1) * Time.deltaTime;
                } else if (rb.velocity.y > 0 && !Input.GetButton ("Jump")){
                      rb.velocity += Vector2.up * Physics.gravity.y * (lowJumpMultiplier - 1) * Time.deltaTime;
                }
          }
    }
     



     STEP 4. OPTIONAL PLAYER CROUCH
    A crouch mechanic requires art that shows the player kneeling lower, and also needs the BoxCollider2D that normally covers the upper parts to be disabled when crouching, so the player can move under low structures or dodge blasts or blows.

    An easy way to disable the torso Collider is to move it to an Empty Game Object parented to the Player, and to access that new Empty Game Object with a script slot so it can be set inactive when the crouch buton is pressed.

    a. CREATE THE TORSO OBJECT:
    Create an Empty Game Object.
    In the Inspector name it "Torso" and reset Transforms. Drag it onto Player to make it a child.
    Select the Player object, RightClick-hold the BoxCollider2D component and select "Copy Component".
    Select the Torso and RightClick-hold the Transform component to select "Past Component As New".
    Select the Player object, turn off the BoxCollider2D component.

    b. ADD "CROUCH" INPUT:
    Open Edit > Project Settings > Inputs.
    Open the first Fire3 button, rename "Crouch". Set "Positive Button" = "s" and "Alt Positive Button" = "down".
    Open the second Fire3 button, rename "Crouch". Set "Positive Button" = "joystick button 3".

    c. ADD CROUCH SCRIPT:
    Create a new C# script, name it "PlayerCrouch.cs".
    Add the following content and add to the Player object.
    Drag the GroundPoint object into the "feet" script slot.
    Add the new Torso into its script slot.

    PlayerCrouch.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PlayerCrouch : MonoBehaviour {

          //public Animator animator;
          public Rigidbody2D rb2D;
          public GameObject torso;
          public Transform feet;
          public bool isAlive = true;
          public LayerMask groundLayer;
          public LayerMask enemyLayer;

          void Start(){
                //animator = gameObject.GetComponentInChildren<Animator>();
                rb = GetComponent<Rigidbody2D>();
          }

         void Update() {
               if ((Input.GetButtonDown("Crouch")) && (IsGrounded()) && (isAlive==true)) {
                      torso.SetActive(false);
                      //animator.SetBool("Crouch", true);
                }
               if (Input.GetButtonUp("Crouch")) {
                      torso.SetActive(true);
                      //animator.SetBool("Crouch", false);
                }
          }

          public bool IsGrounded() {
                Collider2D groundCheck = Physics2D.OverlapCircle(feet.position, 2f, groundLayer);
                Collider2D enemyCheck = Physics2D.OverlapCircle(feet.position, 2f, enemyLayer);
               if ((groundCheck != null) || (enemyCheck != null)) {
                      return true;
                      //Debug.Log("I can crouch now!");
                }
                return false;
          }
    }


    PLAYER SPRITE ANIMATION
    Want to add player animation that give feedback to player actions, like walking, jumping, etc?
    Follow this 2D Art Tutorial to:
    (1). Creating a character spritesheet in your art tool of choice
    (2) Import and slice your spritesheet in Unity.
    (3) Set animation clips in the Unity Animation editor,
    (4) Organize the clips in Unity's Mecanim State Machine system for the code to activate.



    OTHER CHARACTER CREATION APPROACHES:

     PLATFORMER SQUISHINESS:
    Want a sharp, bouncy, squishy player controller?
    Consider modifying this Squishy Platfomer Character Package by developer Ezra Szanton
    (Unity Physics / Velocity based, rather than transfom-based)


     
    [1C]: TOP-DOWN PLAYER CREATION
    Top-Down Player: Set-Up   |   Movement 
    Top-Down Shooter: Rotate to Mouse   |   Rotate to Movement 
    Multi-Lane Environment   |   Climbing    |   Blend Trees


    TOP-DOWN MOVEMENT:
    Top-down games are great for stealth, combat, or exploration gameplay!
    A big difference for Top-down games is we use both Horizontal AND Vertical axis input to move the player.
    We turn off gravity on the Player: "up" means towards the top, not a jump.
    This also means we cannot use the usual jump script above, and we do not use collisions to define floors.
    Instead, collisions to represent the walls and other blocking objects and architecture.

    MULTI-LANE SIDESCROLLER MOVEMENT:
    Instead of a single linear platformer space, do you want the player to have the freedom to walk around the floor? While still looking like a sidescroller (still a horizontal view of the space, but more of the floor visible), this kind of side-scroller movement is more akin to a top-down game, and uses both Horizontal AND Vertical axis input to move the player.
    So the player can move "up" (away from the camera) and "down" (towars the camera) without the notion of "falling".


    REMEMBER: Test initial character art in Unity BEFORE making a lot of poses!
    All of your art for Unity should be 72ppi, and drawn/ painted/ etc at the final intended size.
    For a sidescroller, characters tend to be large, like 256x512, laid out in a 2048x2048 spritesheet.
    Bring your PNGs into a Unity Scene to see how character looks against backgrounds, to check your overall size and the thickness of lines, etc, before you create a hundred keyframes!

     STEP 1. PLAYER CREATION: TOP-DOWN or SIDESCROLLER / MULTI-LANE
    a. Create an Empty GameObject. In the Inspector name it "Player". Add the tag Player. Reset Transforms.

    b. Drag the player art onto Player:
    If your player art is currently a single sprite:

    Drag it into the Hierarchy, parent under Player.
    In the Inspector re-name it "player_art" and set Order In Layer = 100.

    OR
    If your player art is a spritesheet for multiple frames of animation:

    In the Inspector set SpriteMode to Multiple, hit [Apply].
    Open the [SpriteEditor] to slice it up (set Type = Grid by Cell Count, set columns and rows. Hit [Slice] and [Apply]).
    Open the spritesheet in the project panel to view the poses (click the triangle).

    Drag the first pose into the Hierarchy, parent under Player.
    In the Inspector rename is "player_art" and set Order In Layer = 100.

    NOTE: Character art should be sized using Pixels Per Unit in the Project panel Inspector , NOT by changing scale in the Scene Inspector.
    Once the sprite is in the Scene, adjust its size by selecting the source image in the Project panel, changing Pixles Per Unit, and hitting [Apply].
    Choose large numbers to shrink characters, smaller numbers make larger.

    FOR ANIMATION: Create a folder called "Animation" to locate under Media.

    Select player_art, open the Window > Animation > Animation panel.
    Hit [create] and locate the Animation folder to save the clip name as charName_idle.
    Click the clip name in the Animation window (upper left) to create new clips:
    charName_walk, charName_GetHurt, and any other clips for which you have created images.
    (like charName_climbLeft, charName_climbRight, charName_stairsUp, charName_stairsDown, etc).
    Each of these clips is automatically assigned to the Statemachine Animator Component that was added to player_art when the first clip was created.

    For each clip in the list, drag the intended art from the Project panel into the Animation timeline.
    Hit Play in the Animation panel to see the character run the animation in the Scene view.

    c. Select Player, [Add Component] Physics2D > BoxCollider2D (adjust to surround the feet), and Rigidbody2D (turn on Constraints > Freeze Rotation Z, and set Gravity Scale = 0).


     STEP 2. PLAYER MOVEMENT: ISOMETRIC or SIDESCROLLER / MULTI-LANE
    a. Create new C# script, name it PlayerMoveAround.cs.

    b. Add the content below to the script, save, and in Unity drag the script onto your Player.
    The commented-out code is for animation. Un-comment if an Animator is applied to the character art.


    PlayerMoveAround.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PlayerMoveAround : MonoBehaviour {

          //public Animator anim;
          //public AudioSource WalkSFX;
          public Rigidbody2D rb2D;
          private bool FaceRight = true; // determine which way player is facing.
          public static float runSpeed = 10f;
          public float startSpeed = 10f;
          public bool isAlive = true;

          void Start(){
               //anim = gameObject.GetComponentInChildren<Animator>();
               rb2D = transform.GetComponent<Rigidbody2D>();
          }

          void Update(){
                //NOTE: Horizontal axis: [a] / left arrow is -1, [d] / right arrow is 1
                //NOTE: Vertical axis: [w] / up arrow, [s] / down arrow
                Vector3 hvMove = new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"), 0.0f);
               if (isAlive == true){
                      transform.position = transform.position + hvMove * runSpeed * Time.deltaTime;

                      if ((Input.GetAxis("Horizontal") != 0) || (Input.GetAxis("Vertical") != 0)){
               
          //     anim.SetBool ("Walk", true);
               
          //     if (!WalkSFX.isPlaying){
               
          //           WalkSFX.Play();
               
          //     }
                      } else {
               
          //     anim.SetBool ("Walk", false);
               
          //     WalkSFX.Stop();
                     }

                      // Turning. Reverse if input is moving the Player right and Player faces left.
                     if ((hvMove.x <0 && !FaceRight) || (hvMove.x >0 && FaceRight)){
                            playerTurn();
                      }
                }
          }

          private void playerTurn(){
                // NOTE: Switch player facing label
                FaceRight = !FaceRight;

                // NOTE: Multiply player's x local scale by -1.
                Vector3 theScale = transform.localScale;
                theScale.x *= -1;
                transform.localScale = theScale;
          }
    }
    Hit [Play] to test your character motion: WASD or Joystick to move both horizontally and vertically. Turn off Play to continue editing your Scene.

    PLAYER WALKING AUDIO:
    For basic footsteps, [Add Component] AudioSource to the Player with your footsteps sound.
    Disable "Play on awake" but make sure "Looping" is enabled.
    Uncomment the SFX lines in the PlayerMoveAround script, above, including the if-condition.
    In the Unity Inspector, drag the footsteps AudioSource Component into the WalkSFX script slot.
    Hit Play and walk around!

    For other sounds, like attacks or getting hurt, [Add Component] separate AudioSources to the Player with each new sound. Turn off both "Play on awake" AND "Looping".
    Add class variables for AudioSource to the relevant scripts, like public AudioSource AttackSFX;
    Add activation commands to relevant triggers in the same scripts, like AttackSFX.Play();





     TOP DOWN SHOOTER: ROTATE TOWARDS MOUSE

    PLAYER MOVEMENT (source video):
    a). Create a GameObject > Empty GameObject.
    In the Inspector name it "Player". Reset Transforms. Assign the tag = "Player"
    Add Physics2D > Rigidbody2D. Set Gravity Scale = 0.

    Add Physics2D > BoxCollider2D.

    b). Add Character sprite to Hierarchy (power-of-two square PNG: 64x64, 128x128, 256x256, 512x512, etc).
    Inspector: Set Order in Layer = 10. Name = player_art. Reset Transforms.
    Drag PlayerArt onto Player to make it a child of Player.
    Adjust the size of the Player BoxCollider2D to fit around the player_art body/head.

    c). Add FirePoint to prepare for shooting:
    RightClick Player to add an Empty Game Object child. Name it "FirePoint".
    In the Scene move FirePoint to the tip of the gun.

    d). Create a new C# script named "PlayerMove_MouseAim.cs". Add the content below, save.
    In Unity, apply the script to the Player.
    Hit [Play]. The player should rotate player towards mouse.

    PlayerMove_MouseAim.cs script:
    using UnityEngine;
    using System;
    using System.Collections;

    public class
    PlayerMove_MouseAim :   MonoBehaviour {

          public float moveSpeed = 5f;
          public Rigidbody2D rb;
          public Camera cam;
          public Vector2 movement;
          public Vector2 mousePos;

          void Awake(){
                //assign rigidbody2D and camera to variables
                rb = GetComponent <Rigidbody2D>();
                cam = GameObject.FindGameObjectWithTag("MainCamera").GetComponent<Camera>() as Camera;
          }

          void Update(){
                if (gameObject != null) {
                      //for position, get axis inputs
                      movement.x = Input.GetAxisRaw ("Horizontal");
                      movement.y = Input.GetAxisRaw ("Vertical");
                      //for rotation: get mouse position
                      mousePos = cam.ScreenToWorldPoint (Input.mousePosition);
                }
          }

          void FixedUpdate(){
                //actual movement uses Rigidbody2D, so goes in FixedUpdate
                rb.MovePosition(rb.position + movement * moveSpeed * Time.fixedDeltaTime);
                //actual rotation uses vector math to calculate angle, then rotates character to mouse
                Vector2 lookDir = mousePos - rb.position;
                float angle = Mathf.Atan2(lookDir.y, lookDir.x) * Mathf.Rad2Deg -90f;
                rb.rotation = angle;
          }
    }



     TOP DOWN SHOOTER: ROTATE TOWARDS MOVEMENT (source video)
    This script can be applied instead of the one above, to rotate the Player based on movement direction.

    As above, create an Empty GameObject called Player.
    Reset Transforms
    [Add Component] BoxCollider2D
    [Add Component] Rigidbody2D (Gravity Scale = 0).
    Add art as a child. Name it "player_art".
    Adjust the BoxCollider to surround the player-art torso and head.
    RightClick Player to add a GameObject as a child. Name it "FirePoint".
    In the Scene move it to end of gun barrel.
    Create a C# script called "PlayerMove_Rotate.cs".
    Add the content below, save, and apply to the Player.





    PlayerMove_Rotate.cs script:
    using UnityEngine;
    using System;
    using System.Collections;

    public class
    PlayerMove_Rotate :  MonoBehaviour {

          public float moveSpeed = 5f;
          public float rotationSpeed = 720f;

          void Update(){
                float horizontalInput = Input.GetAxis ("Horizontal");
                float verticalInput = Input.GetAxis ("Vertical");
                Vector2 moveDirection = new Vector2(horizontalInput, verticalInput);
                float inputMagnitude = Mathf.Clamp01(moveDirection.magnitude);
                moveDirection.Normalize();

                transform.Translate(moveDirection * moveSpeed * inputMagnitude * Time.deltaTime, Space.World);

                if (moveDirection != Vector2.zero) {
                      Quaternion toRotation = Quaternion.LookRotation (Vector3.forward, moveDirection);
                      transform.rotation = Quaternion.RotateTowards (transform.rotation, toRotation, rotationSpeed * Time.deltaTime);
                }
          }

    }





      SIDESCROLLER / MULTI-LANE ENVIRONMENT:
    a. Create an Empty GameObject called "SETTING" to act as a folder.

    b. Add Background Art into the Scene: Drag into the Hierarchy all background sprites, and drag them onto SETTING to make them children. For a sidescroller, this art should include a significant ground space (seen at angle) and a wall space (like the wall of a room interior, or an exterior building wall).

    c. Create an Empty Game Object, drag it onto SETTING, and in the Inspector name it "Collider_Ground".
    [Add Component] Physics > BoxCollider2D and adjust the collider using the Edit Collider button to fit the bottom of background elements, along their entire length, to create a stopping place for your character.

    d. Duplicate Collider_Ground and name it "Collider_Left". Use Edit Collider to create a left-side barrier.

    e. Duplicate again for "Collider_Wall". Use Edit Collider to surround the back wall in the Background art.
    Re-position Player so that its collider (around the feet) is standing on the ground area, between all the background colliders.

    Hit [Play] to test! Turn off [Play] to resume editing.


    WHO'S IN FRONT? DYNAMIC SORTING ORDER:
    Does your game have multiple NPC characters pasing in front of and behind the player character? They will need to have their colliders set to isTrigger and to have any attack interaction scripts set to OnTriggerEnter2D().

    In order for character that are in the lower / foreground lane to appear in front of characters in the middle or top / back lanes, their sorting order must be changed in relation to their Y-position: the further down the character, the higher their Sorting number, the more it appears in front.

    Create a new C# script called "CharacterSortingOrder.cs" and add it to all characters: Player and NPCs.

    CharacterSortingOrder.cs
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class CharacterSortingOrder : MonoBehaviour {

          // Update is called once per frame
          void Update() {
                GetComponentInChildren<SpriteRenderer>().sortingOrder = Mathf.RoundToInt(transform.position.y * 100f) * -1;
          }
    }


     SIDESCROLLER / MULTI-LANE CLIMBING:
    With no Gravity, the usual Jump script will not work. The idea of Jumping can still be used to get onto higher objects, with a custom script that manages both the upward and downward movement.

    Alternatively, we can make some objects trigger Player animation, to make the player appear to climb objects or stairs. These objects need just:
    (1) a tag for identifcation
    (2) BoxCollider2D Components, set to isTrigger, adjusted to fit around the area where we want the animation.
    We then add a script to the Player (or modify the PlayerMoveAround script) with OnTriggerStay2D functions to trigger climbing animation when encountering those tagged objects , as the Player moves to the top or down the other side. A similar system can be used for stair-walking up or down.

    For an area where you want a distinct animation (a ladder, the side of a box, etc):
    a. Create an Empty GameObject. In the Inspector name it "Collider_objectName" and reset Transforms.

    b. Move Collider_objectName in the Scene to where the object visual exists in the background (or drag the art sprite into the Hierarchy, and make another GameObject to parent both the Sprite and Collider_objectName).

    c. Select Collider_objectName and [Add Component] Physics2D > BoxCollider2D.
    Set this to isTrigger, and Edit Collider to surround the area meant to trigger the climbing animation.

    d. Add a tag to the GameObject that will be used to identify it for the collision (can be "climbable" for a ladder, or "climbLeft" and "climbRight" for the two sides of an abject, like a box, in which case two separate GameObjects would need to be created, one for each side).
    The Animation clips can be called "charName_ClimbLeft" and "charName_ClimbRight".
    In the Animator, draw transitions to and from the default Idle clip to both of these climb clips.
    Create Parameters to regulate those Animator transitions: bools named "ClimbLeft" and "ClimbRight".


    e. Create new C# script, name it "PlayerMoveAround.cs".
    Add the content below to the script, save, and in Unity drag the script onto your Player.
    The commented-out code is for animation. Un-comment if an Animator is applied to the character art.


    PlayerClimb.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PlayerClimb : MonoBehaviour {

          public Animator anim;
          public Rigidbody2D rb2D;
          public bool isAlive = true;

          void Start(){
               animator = gameObject.GetComponentInChildren<Animator>();
               rb2D = transform.GetComponent<Rigidbody2D>();
          }

          void OnTriggerStay2D(Collider2D other){
                if (other.gameObject.tag == "climbLeft"){
                      anim.SetBool ("ClimbLeft", true);
                } else if (other.gameObject.tag == "climbRight"){
                      anim.SetBool ("ClimbRight", true);
                } else {
                      anim.SetBool ("ClimbLeft", false);
                      anim.SetBool ("ClimbRight", false);
                }
          }

         void OnTriggerExit2D(Collider2D other){
                if (other.gameObject.tag == "climbLeft"){
                      anim.SetBool ("ClimbLeft", false);
                } else if (other.gameObject.tag == "climbRight"){
                      anim.SetBool ("ClimbRight", false);
                }
          }
    }
    This script can be adopted for any interaction you want with objects in the environment.



     ADVANCED ISOMETRIC MOVEMENT: BLEND-TREES
    The above player movement script only allows the Player character to animate in the four cardinal directions:
        (1) down, (2) right, (3) up, or (4) left.

    Isometric games often allow players to animate in diagonals as well, for a total of 8 directions:
        (1) down (front), (2) right-front-diagonal, (3) right, (4) right-back-diagonal,
        (5) up (back), (6) left-back-diagonal, (7) left, (8) left-front-diagonal

    Watch this video tutorial for player movement using a blend tree to switch between all 8 animations, depending on ranges of angle of movement.










     
    [1D]: CAMERA FOLLOW

    CAMERA DISTANCE:
    In a 2D Unity game, the MainCamera shoudl always be Z postion = -10 ("in front" of all game content).

    All other assets must be Z position = 0 (grid, characters, pickups, even the folder-objects that hold content).

    To "move" the camera further back, in order to see more of the screen, change the Camera SIZE in the Inspector.

    By default, Size=5. Just a small change to 7 or 8 can show a lot more content!
    In most Platformers the camera is expected to follow the player position. A LERP (linear interpolation) will move the camera smoothly to the player's new position, rather than jumping jerkily.

    Alternatively, see below for a "Bounded" camera that follows the player but stops at world boundaries; good for an exploration game.


    CAMERA-FOLLOW-PLAYER SCRIPTS:

    [1]. FOLLOW WITH LERP (A delay with motion: 2D source and 3D source. See this excellent LERP Tutorial).
    a. Create a new C# script, name it CameraFollow2DLERP.cs.

    b. Drag it onto the MainCamera. Note that higher camSpeed values cause the camera to move as fast as the player (4, better for action), while low ones cause a big delay (1, for slower player movement).

    CameraFollow2DLERP.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class CameraFollow2DLERP : MonoBehaviour {

          private GameObject target;
          public float camSpeed = 4.0f;

          void Start(){
                target = GameObject.FindWithTag("Player");
          }

          void FixedUpdate () {
                Vector2 pos = Vector2.Lerp ((Vector2)transform.position, (Vector2)target.transform.position, camSpeed * Time.fixedDeltaTime);
                transform.position = new Vector3 (pos.x, pos.y, transform.position.z);
          }
    }

    (NOTE: For 3D projects, here is a 3D camera follow script: CameraControl3DLERP.cs).

    c. Hit [Play] and test the scripts by moving the Player.



    [2]. ALTERNATIVE 1: MATHF FOLLOW
    CameraFollow.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class CameraFollow : MonoBehaviour {

          public Transfrom target;

          void Update () {
                transform.position = new Vector3(
                      transform.position.x,
                      //Mathf.Clamp(targetToFollow.positon.y, 0f, 10f),
                      transform.position.y,
                      transform.position.z);
          }
    }



    [3]. ALTERNATIVE 2: BASIC FOLLOW (video source)
    Try this CameraFollowX.cs script onto the Camera for a side-scrolling game (NOTE: this script only follows the X-axis, for scrolling backgrounds. Be sure player has Player Tag aplied).
    Try this CameraFollow.cs script if you need the camera (or another object) to follow in all directions.



    [4]. BOUNDED CAMERA (typically for top-down games):
    For a camera that follows the player but does not move/show outside the play area, create the following CameraMoveBounded.cs script and apply it to the Camera.

    In the Inspector, enter the Max and Min position X and Y values for the UpperRight and LowerLeft corners of the space. Theaeasiest way to get these values is to move the Camera to those corners and copy Transform values.
    If the original space was 2x the game screen size, try max = 2 x 10 and min = -10 x -2.

    NOTE: LERP takes 3 arguments: current position, destination, and speed. CLAMP limits the range of a value between two other values.

    CameraMoveBounded.cs:
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class
    CameraMoveBounded : MonoBehaviour {

           pivate Transform target; // the player
           public float smoothing = 0.6f; // how quickly camera moves to player
           public Vector2 minPosition; // X & Y values for player area LowerLeft corner
           public Vector2 maxPosition; // X & Y values for UpperRight corner

          void Start(){
             target = GameObject.FindWithTag("Player").GetComponent<Transform>();
          }

           void Update () {
              if (transform.position != target.position){
                   Vector3 targPos = new Vector3(target.position.x, target.position.y, transform.position.z);
                    targPos.x=Mathf.Clamp(targPos.x, minPosition.x, maxPosition.x);
                    targPos.y=Mathf.Clamp(targPos.y, minPosition.y, maxPosition.y);
                    transform.position = Vector3.Lerp(transform.position, targPos, smoothing);
                  }
           }
    }




     
    [1E]: PLAYER STATE
    Canvas   |   GameHandler  |   Static Variables Explained  |   Player Damage script
    Make a GameHandlerCanvas Prefab  |   More Advanced UI: Heart Health System or HealthBar

     STEP 1. CANVAS
    1. CANVAS: If you do not already have a GameObject > UI > Canvas object in your Hierarchy, create one.
    Note in the Scene view the Unity Canvas displays enormous compared to the Sprite / Tilemap content (by design), but they overlap perfectly in the Game view.

    a. In Inspector, set Canvas Scaler > UI Scale Mode from "Constant Pixel Size" to "Scale With Screen Size" and Reference Resolution = 1280 x 720.

    b. Add Player Stat #1:
    BACKGROUND: Add a GameObject > UI > Image, name it "imageHealthBG".
    Size to fit in an upper corner, around 200 x 60. Set color dark and alpha transparency = 180.
    TEXT: Add a GameObject > UI > Legacy > Text, name it "textHealth". Size to fit within imagehealth, around 190 x 50. Set text field to "HEALTH: ??", Font Size = 30 and bold, color light.

    c. Add Player Stat #2
    BACKGROUND: Add a GameObject > UI > Image, name it "imageTokensBG".
    Size to fit in the opposite upper corner, around 100 x 60. Set color dark and alpha transparency = 180.
    TEXT: Add a GameObject > UI > Legacy > Text, name it "textTokens". Size to fit within imagehealth, around 190 x 50. Set text field to "Gold: ???", Font Size = 30 and bold, color light.

    NOTE: Naturally, these stats should be changed per the needs of your game, both in content and number.


     STEP 2. GAMEHANDLER
    a. Create a new Empty Game Object. In the Inspector name it "GameHandler" and reset Transforms. In the Inspector upper left corner add the tag "GameHandler" to the project and then add it to the GameHandler object

    b. Create a new C# script called GameHandler.cs.
    Add the content below and drag the script onto your GameHandler object.

    NOTE: We put all of these critical player stats on the GameHandler instead of the Player to allow them to persist even if the player object is "killed" (removed).

    GameHandler.cs script: (current health is displayed as text)
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.UI;
    using UnityEngine.SceneManagement;

    public class GameHandler : MonoBehaviour {

          private GameObject player;
          public static int playerHealth = 100;
          public int StartPlayerHealth = 100;
          public GameObject healthText;

          public static int gotTokens = 0;
          public GameObject tokensText;

          public bool isDefending = false;

          public static bool stairCaseUnlocked = false;
          //this is a flag check. Add to other scripts: GameHandler.stairCaseUnlocked = true;

          private string sceneName;
          public static string lastLevelDied;  //allows replaying the Level where you died

          void Start(){
                player = GameObject.FindWithTag("Player");
                sceneName = SceneManager.GetActiveScene().name;
                //if (sceneName=="MainMenu"){ //uncomment these two lines when the MainMenu exists
                      playerHealth = StartPlayerHealth;
                //}
                updateStatsDisplay();
          }

          public void playerGetTokens(int newTokens){
                gotTokens += newTokens;
                updateStatsDisplay();
          }

          public void playerGetHit(int damage){
               if (isDefending == false){
                      playerHealth -= damage;
                      if (playerHealth >=0){
                            updateStatsDisplay();
                      }
                      if (damage > 0){
                            player.GetComponent<PlayerHurt>().playerHit();       //play GetHit animation
                      }
                }

               if (playerHealth > StartPlayerHealth){
                      playerHealth = StartPlayerHealth;
                      updateStatsDisplay();
                }

               if (playerHealth <= 0){
                      playerHealth = 0;
                      updateStatsDisplay();
                      playerDies();
                }
          }

          public void updateStatsDisplay(){
                Text healthTextTemp = healthText.GetComponent<Text>();
                healthTextTemp.text = "HEALTH: " + playerHealth;

                Text tokensTextTemp = tokensText.GetComponent<Text>();
                tokensTextTemp.text = "GOLD: " + gotTokens;
          }

          public void playerDies(){
                player.GetComponent<PlayerHurt>().playerDead();       //play Death animation
                lastLevelDied = sceneName;       //allows replaying the Level where you died
                StartCoroutine(DeathPause());
          }

          IEnumerator DeathPause(){
                player.GetComponent<PlayerMove>().isAlive = false;
                player.GetComponent<PlayerJump>().isAlive = false;
                yield return new WaitForSeconds(1.0f);
                SceneManager.LoadScene("EndLose");
          }

          public void StartGame() {
                SceneManager.LoadScene("Level1");
          }

          // Return to MainMenu
          public void RestartGame() {
                Time.timeScale = 1f;
                SceneManager.LoadScene("MainMenu");
                 // Reset all static variables here, for new games:
                playerHealth = StartPlayerHealth;
          }

          // Replay the Level where you died
          public void ReplayLastLevel() {
                Time.timeScale = 1f;
                SceneManager.LoadScene(lastLevelDied);
                 // Reset all static variables here, for new games:
                playerHealth = StartPlayerHealth;
          }

          public void QuitGame() {
                    #if UNITY_EDITOR
                    UnityEditor.EditorApplication.isPlaying = false;
                    #else
                    Application.Quit();
                    #endif
          }

          public void Credits() {
                SceneManager.LoadScene("Credits");
          }
    }

    c. Make a Prefab: Drag the GameHandler object into the Project panel to make a Prefab, and add this Prefab to every Scene, so the static variables can preserve player state information between Scenes.


    NOTES ABOUT STATIC VARIABLES:
    Class Static Variables can be used for Player "states" (like Player health) to preserve information between Scenes. Want to keep track of player health, gold, a weapon charge, or even whether the player has already collected or killed something in a previous scene, so the player sees that change maintained when they return to that scene later? Set a static variable in the GameHandler to hold that information (and clear all that information on Restart).

    While non-static class variables are accessed through a reference to the GameObject that holds their script, static variables are accessed directly through the class.

    For example, in GameHandler.cs (above), there is a class static variable:
          public static int playerHealth;

    if we want another script to assign the value of this static variable, or to check it,
    we call it directly from the Class:
          int pHealth = GameHandler.playerHealth;

    or
          if (GameHandler.playerHealth >= 1){
                //do a thing
          }



    This is different with non-static variables, that need a reference to the local GameObject in the Scene that has the script component with the Class (because non-static variables are only a local instance).
    For example, in GameHandler.cs (above), there is a class variable:
          public bool isDefending = false;

    to access this, we need:
     (1) a variable to hold the referenced GameObject, then
     (2) load that GameObject and access the script Component, and
     (3) finally access the variable:

          private GameHandler gameHandler; //(1)

          void Start(){
                gameHandler = GameObject.FindWithTag("GameHandler").GetComponent<GameHandler>(); //(2)
          }

          void Update(){
                if (Input.GetKeyDown(KeyCode.D)){ gameHandler.isDefending = true;} //(3)
                if (Input.GetKeyUp(KeyCode.D)){ gameHandler.isDefending = false;}         
          }




      STEP 3. PLAYER DAMAGE
    Create a new C# script called PlayerHurt.cs. Add the content below and drag the script onto your Player.
    This script manages the player reaction to damage; the actual health stat is managed in the GameHandler
    The commented-out code is for animation, when an Animator is applied to the character art.

    PlayerHurt.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PlayerHurt: MonoBehaviour{

          //public Animator animator;
          public Rigidbody2D rb2D;

          void Start(){
               //animator = gameObject.GetComponentInChildren<Animator>();
               rb2D = transform.GetComponent<Rigidbody2D>();           
          }

          public void playerHit(){
                //animator.SetTrigger ("GetHurt");
          }

          public void playerDead(){
                rb2D.isKinematic = true;
                //animator.SetTrigger ("Dead");
          }
    }

          Want to display the Player health as a graphic bar instead of a number stat?
          Try this HealthBar tutorial in the UI page.
          There is an option to keep the HealthBar in the HUD or to make it a separate Canvas (with Render Mode set to World Space, so the Rect can be shaped to fit in the scene), parented to the Player.




      MAKE YOUR WORK MUCH EASIER: CREATE THE GAMEHANDLER_CANVAS PREFAB
    Typically, multiple Canvas UI elements need to be dragged into th GameHandler script slots to assign them to script variables. The Camvas and the GameHandler are each identical across game Scenes, so save yourself a lot of work and opportunities for mistakes by making them part of a single prefab for the in-game Scenes:

    Start with an in-game Scene in which you have already added your GameHandler Prefab and already created an in-game Canvas (with Health UI):

    a. Create the "folder" Empty GameObject:
    In the Hierarchy, RightClick to choose Create Empty. This makes a new GameObject.
    In the Inspector name it "GameHandlerCanvas", set Transform Position X, Y, and Z = 0.
    In the Hierarchy, drag the GameHandlerCanvas object to the top.

    b. Add the GameHandler to GameHandlerCanvas:
    In the Hierarchy, drag the GameHandler prefab up onto the GameHandlerCanvas, to make it a child.
    NOTE: this child is still its own prefab! The GameHandler prefab will later be dragged separately into the framing scenes (MainMenu, EndWin, EndLose, Credits), without the in-game Canvas, and then changes to the GameHandler prefab will change it in all Scenes.

    c. Add the in-game Canvas to GameHandlerCanvas:

    In Hierarchy, drag the Canvas up onto GameHandlerCanvas, to make it a child (place above GameHandler).
    NOTE: this child is NOT its own prefab. It is just a child of GameHandlerCanvas

    d. CRITICAL: Add the in-game EventHandler to GameHandlerCanvas:

    In Hierarchy, drag the EventHandler up onto GameHandlerCanvas, to make it a child (place above GameHandler, below Canvas). If you do not include the EventHandler, no buttons will work!

    e. Make the Prefab, and share it!:

    Drag the GameHandlerCanvas into your Project > Assets > Prefabs folder to make it a prefab.
    Save the Scene.
    Open all other in-game scenes and add the GameHandlerCanvas prefab!
    WANT TO EDIT THE IN_GAME CANVAS? Open the prefab to make the changes, like adding a PauseMenu or Shop to the bottom of the Canvas.

    NOTE:
    We only use the GameHandlerCanvas Prefab in the In-Game Scenes, NOT the Framing Scenes.

    Framing Scenes include MainMenu, EndWin, EndLose, Instructions, and Credits.
    The Framing Scenes get their own Canvas with no player feedback UI.
    Instead the Framing Scene Canvas gets two "placeholder" objects: UI > Legacy > Text and UI > Image.
    In the Scene view these placeholders are draggedabove the Canvas rectangle to stay out of sight.
    In the Inspector these unseen placeholders are then dragged into the GameHandler script slots to fill UI feedback variables that are not used in the Framing Scenes (because all script slots must be filled).




      ALTERNATE UI: HEALTH HEARTS SYSTEM
    The following script allows 3 hearts to be displayed instead of a text number or a health bar.

    a. Create two pieces of art:
    (1) a black and white image of a full heart, named "HeartArt".
    (2) a black silhouette of the same heart to go behind, named "HeartEmpty".

    b. Add the art:

    In your Canvas, RightClick to create a UI > Image. In the Scene hit [w] and position in the upper-left corner.
    In the Inspector name it "HeartBG1", set SourceImage = HeartEmpty.
    Click the color rectangle to set alpha = 150.

    In the Hierarchy select HeartBG1 and hit [Ctrl/Cmd] + [d] to duplicate it.
    Drag the duplicate below HeartBG1 in the Hierarchy so it appears in front of HeartBG1 in the Scene.
    In the Inspector rename the duplicate "Heart1", set SourceImage = HeartArt. Set color = green.
    IMPORTANT, for a fill effect:
          At the bottom of the Heart1 Image Component, change Image Type from Simple to "Filled".
          Set Fill Method from Radial 360 to "Horizontal".

    c. Duplicate the heart for 3 hearts:

    Select both HeartBG1 and Heart1, [Ctrl/Cmd]+[d] to copy twice for Heart2/HeartBG2 & Heart3/HeartBG3.
    In Scene hit [w] to drag each pair a bit to the right, to make a line of hearts in the Canvas upperleft corner
    (Heart1 is left-most and will disappear last, Heart3 is right-most and will disappear first).

    d. Change the GameHandler.cs script to add the content below (new content is boldfaced).
    Note the variables for the three Heart gameobjects and static variables for the current number of hearts and the fill amount for each heart.
    Also note the changes to these 3 functions: Start(), playerGetHit(), and updateStatsDisplay().

    e. In the GameHandlerCanvas prefab, drag Heart1, Heart2, and Heart3 into the new script slots in the GameHandler.

    f. In the framing scenes (EndWin, EndLose, MainMenu, and Credits, when they are created), drag the ImagePlaceholder object into the three GameHandler Heart slots, to avoid the error message of those slots being unassigned.

    GameHandler.cs script (current health is displayed as a row of hearts):
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.UI;
    using UnityEngine.SceneManagement;

    public class GameHandler : MonoBehaviour {

          private GameObject player;

          public static int playerHearts = 3;
          public GameObject heart1;
          public GameObject heart2;
          public GameObject heart3;
          public static float heart1fill = 1.0f;
          public static float heart2fill = 1.0f;
          public static float heart3fill = 1.0f;


          public static int playerHealth = 100;
          public int StartPlayerHealth = 100;
          //public GameObject healthText;

          public static int gotTokens = 0;
          public GameObject tokensText;

          public bool isDefending = false;

          public static bool stairCaseUnlocked = false;
          //this is a flag check. Add to other scripts: GameHandler.stairCaseUnlocked = true;

          private string sceneName;
          public static string lastLevelDied;  //allows replaying the Level where you died

          void Start(){
                player = GameObject.FindWithTag("Player");
                sceneName = SceneManager.GetActiveScene().name;
                //if (sceneName=="MainMenu"){ //uncomment these two lines when the MainMenu exists
                      playerHealth = StartPlayerHealth;
                //}
                updateStatsDisplay();

                heart1.SetActive(true);
                heart2.SetActive(true);
                heart3.SetActive(true);

                Debug.Log("pHealth = " + playerHealth + ". Hearts = " + playerHearts + ". h1fill = " + heart1fill + ". h2fill = " + heart2fill + ". h3fill = " + heart3fill );
          }

          public void playerGetTokens(int newTokens){
                gotTokens += newTokens;
                updateStatsDisplay();
          }

          public void playerGetHit(int damage){
               if (isDefending == false){
                      playerHealth -= damage;
                      if (playerHealth > 0){
                            if (playerHearts == 3){
                                  heart3fill = (float)playerHealth / StartPlayerHealth;
                            }
                           else if (playerHearts == 2){
                                  heart2fill = (float)playerHealth / StartPlayerHealth;
                            }
                           else if (playerHearts == 1){
                                  heart1fill = (float)playerHealth / StartPlayerHealth;
                            }

                            Debug.Log("pHealth = " + playerHealth + ". Hearts = " + playerHearts + ". h1fill = " + heart1fill + ". h2fill = " + heart2fill + ". h3fill = " + heart3fill );
                            updateStatsDisplay();
                      }

                      if (damage > 0){
                            player.GetComponent<PlayerHurt>().playerHit();      //play GetHit animation
                      }
                }

               if (playerHealth <= 0){
                      playerHealth = 0;
                      playerHearts -=1;
                      updateStatsDisplay();
                      playerHealth = StartPlayerHealth;
                }

               if (playerHearts <= 0){
                      playerDies();
                }


               //if (playerHealth > StartPlayerHealth){
               //       playerHealth = StartPlayerHealth;
               //       updateStatsDisplay();
               //}

          }

          public void updateStatsDisplay(){
                if (playerHearts == 3){
                      heart3.GetComponent<Image>().fillAmount = heart3fill;
                }
                else if (playerHearts == 2){
                      heart3.SetActive(false);
                      heart2.GetComponent<Image>().fillAmount = heart2fill;
                }
                else if (playerHearts == 1){
                      heart2.SetActive(false);
                      heart1.GetComponent<Image>().fillAmount = heart1fill;
                }
                else if (playerHearts == 0){
                      heart1.SetActive(false);
                }


                //Text healthTextTemp = healthText.GetComponent<Text>();
                // healthTextTemp.text = "HEALTH: " + playerHealth;


                Text tokensTextTemp = tokensText.GetComponent<Text>();
                tokensTextTemp.text = "GOLD: " + gotTokens;
          }

          public void playerDies(){
                player.GetComponent<PlayerHurt>().playerDead();      //play Death animation
                lastLevelDied = sceneName;       //allows replaying the Level where you died
                StartCoroutine(DeathPause());
          }

          IEnumerator DeathPause(){
                player.GetComponent<PlayerMove>().isAlive = false;
                player.GetComponent<PlayerJump>().isAlive = false;
                yield return new WaitForSeconds(1.0f);
                SceneManager.LoadScene("EndLose");
          }

          public void StartGame() {
                SceneManager.LoadScene("Level1");
          }

          // Return to MainMenu
          public void RestartGame() {
                Time.timeScale = 1f;
                SceneManager.LoadScene("MainMenu");
                    // Please also reset all static variables here, for new games!
                playerHealth = StartPlayerHealth;
          }

          // Replay the Level where you died
          public void ReplayLastLevel() {
                Time.timeScale = 1f;
                SceneManager.LoadScene(lastLevelDied);
                 // Reset all static variables here, for new games:
                playerHealth = StartPlayerHealth;
          }

          public void QuitGame() {
                    #if UNITY_EDITOR
                    UnityEditor.EditorApplication.isPlaying = false;
                    #else
                    Application.Quit();
                    #endif
          }

          public void Credits() {
                SceneManager.LoadScene("Credits");
          }
    }




      ALTERNATE UI: HEALTHBAR SYSTEM
    The following script allows 3 hearts to be displayed instead of a text number or a health bar.

    a. Create two pieces of art:
    (1) a black and white image of a full heart, named "HeartArt".
    (2) a black silhouette of the same heart to go behind, named "HeartEmpty".

    b. Add the art:

    In your Canvas, RightClick to create a UI > Image. In the Scene hit [w] and position in the upper-left corner.
    In the Inspector name it "HeartBG1", set SourceImage = HeartEmpty.
    Click the color rectangle to set alpha = 150.

    In the Hierarchy select HeartBG1 and hit [Ctrl/Cmd] + [d] to duplicate it.
    Drag the duplicate below HeartBG1 in the Hierarchy so it appears in front of HeartBG1 in the Scene.
    In the Inspector rename the duplicate "Heart1", set SourceImage = HeartArt. Set color = green.
    IMPORTANT, for a fill effect:
          At the bottom of the Heart1 Image Component, change Image Type from Simple to "Filled".
          Set Fill Method from Radial 360 to "Horizontal".



     
    [1F]: PLAYER BATTLE SCRIPTS
    Player Melee   |   Shooting, Projectiles, and Explosions   |   Defense

      OPTION 1. PLAYER MELEE ATTACK
    a. Create a new C# script called PlayerAttackMelee.cs.
    Add the content below and drag the script onto your Player.
    Set LayerMask rolldown in Inspector to "Enemies" (may need to first add a new Layer called Enemies).
    The commented-out code is for animation, if an Animator is applied to the character art.
    This script works for punches/kicks or swords slashes.

    b. Add a new Empty GameObject.
    In the Inspector name it "HitPoint", reset Transforms.
    Drag HitPoint onto Player to make it a child.
    Position it right in front of the character art, at the center of where you want attacks to hit enemies.
    Drag HitPoint into the attackPt script slot.

    c. Create an "Attack" Input:
    Open Edit > Project Settings panel to select the Input Manager.
    Open the first Fire1 and change it to "Attack".
    Set "Positive Button" = "space" and "Alt Positive Button" = "joystick button 0".

    PlayerAttackMelee.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PlayerAttackMelee : MonoBehaviour{

          //public Animator animator;
          public Transform attackPt;
          public float attackRange = 0.5f;
          public float attackRate = 2f;
          private float nextAttackTime = 0f;
          public int attackDamage = 40;
          public LayerMask enemyLayers;

          void Start(){
               //animator = gameObject.GetComponentInChildren<Animator>();
          }

          void Update(){
               if (Time.time >= nextAttackTime){
                      //if (Input.GetKeyDown(KeyCode.Space))
                     if (Input.GetAxis("Attack") > 0){
                            Attack();
                            nextAttackTime = Time.time + 1f / attackRate;
                      }
                }
          }

          void Attack(){
                //animator.SetTrigger ("Melee");
                Collider2D[] hitEnemies = Physics2D.OverlapCircleAll(attackPt.position, attackRange, enemyLayers);
               
                foreach(Collider2D enemy in hitEnemies){
                      Debug.Log("We hit " + enemy.name);
                      enemy.GetComponent<EnemyMeleeDamage>().TakeDamage(attackDamage);
                }
          }

          //NOTE: to help see the attack sphere in editor:
          void OnDrawGizmosSelected(){
               if (attackPt == null) {return;}
                Gizmos.DrawWireSphere(attackPt.position, attackRange);
          }
    }


     OPTION 2a. PLAYER SHOOT ATTACK
    1. Create a new C# script called PlayerAttackShoot.cs. Add the content below and drag the script onto your Player. The commented-out code is for animation, if an Animator is applied to the character art.

    2. Add a new Empty GameObject.
    In the Inspector name it "FirePoint", reset Transforms.
    Drag HitPoint onto Player to make it a child.
    Position it right in front of the character art, at the center of where you want attacks to hit enemies.
    Drag HitPoint into the firePoint script slot.

    3. Create an "Attack" Input:
    Open Edit > Project Settings panel to select the Input Manager.
    Open the first Fire1 and change it to "Attack".
    Set "Positive Button" = "space" and "Alt Positive Button" = "joystick button 0".

    PlayerAttackShoot.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PlayerAttackShoot : MonoBehaviour{

          //public Animator animator;
          public Transform firePoint;
          public GameObject projectilePrefab;
          public float projectileSpeed = 10f;
          public float attackRate = 2f;
          private float nextAttackTime = 0f;

          void Start(){
               //animator = gameObject.GetComponentInChildren<Animator>();
          }

          void Update(){
               if (Time.time >= nextAttackTime){
                      //if (Input.GetKeyDown(KeyCode.Space))
                     if (Input.GetAxis("Attack") > 0){
                            playerFire();
                            nextAttackTime = Time.time + 1f / attackRate;
                      }
                }
          }

          void playerFire(){
                //animator.SetTrigger ("Fire");
                Vector2 fwd = (firePoint.position - this.transform.position).normalized;
                GameObject projectile = Instantiate(projectilePrefab, firePoint.position, Quaternion.identity);
                projectile.GetComponent<Rigidbody2D>().AddForce(fwd * projectileSpeed, ForceMode2D.Impulse);
          }
    }


     OPTION 2b. MAKE THE PROJECTILE:
    a. Create a new Empty Game Object. In the Inspector name it "PlayerProjectile", reset Transforms.

    b. With PlayerProjectile selected, [Add Component] BoxCollider2D, set isTrigger=on.
        [Add Component] Rigidbody2D.
        To shoot straight, set Rigidbody Gravity = 0 (do not set type = Kinematic, or addForce does not work).

    c. Import the projectile art .PNG and drag the image into the Hierarchy, onto PlayerProjectile to make it a child (can be a single frame or an animated spritesheet, see Option2c below for spritesheet steps). Name it "projectile_art".

    d. Create a new C# script named "PlayerProjectile". Add the following and apply to the PlayerProjectile GameObject:
    PlayerProjectile.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PlayerProjectile : MonoBehaviour{

          public int damage = 1;
          public GameObject hitEffectAnim;
          public float SelfDestructTime = 4.0f;
          public float SelfDestructVFX = 0.5f;
          public SpriteRenderer projectileArt;

          void Start(){
               projectileArt = GetComponentInChildren<SpriteRenderer>();
               selfDestruct();
          }

          //if the bullet hits a collider, play the explosion animation, then destroy the effect and the bullet
          void OnTriggerEnter2D(Collider2D other){
                if (other.gameObject.layer == LayerMask.NameToLayer("Enemies")) {
                      //gameHandlerObj.playerGetHit(damage);
                      other.gameObject.GetComponent<EnemyMeleeDamage>().TakeDamage(damage);
                }
               if (other.gameObject.tag != "Player") {
                      GameObject animEffect = Instantiate (hitEffectAnim, transform.position, Quaternion.identity);
                      projectileArt.enabled = false;
                      //Destroy (animEffect, 0.5);
                      StartCoroutine(selfDestructHit(animEffect));
                }
          }

          IEnumerator selfDestructHit(GameObject VFX){
                yield return new WaitForSeconds(SelfDestructVFX);
                Destroy (VFX);
                Destroy (gameObject);
          }

          IEnumerator selfDestruct(){
                yield return new WaitForSeconds(SelfDestructTime);
                Destroy (gameObject);
          }
    }

    e. Drag the Player Projectile into the Project panel to make it a Prefab. Delete the original from the Hierarchy.

    f. Select the Player in the Hierarchy, drag the PlayerProjectile Prefab from the Project panel into the PlayerAttackShoot script projectilePrefab slot.


      OPTION 2c. MAKE THE IMPACT EXPLOSION:
    a. Create a new Empty Game Object.
    In the Inspector name it "Boom", reset Transforms.
    Import an explosion spritesheet.


    b. Select the PNG in the Project panel.
    In the Inspector set the following and hit [Apply]:
               Sprite Mode = Multiple
               Pixels Per Unit = 16
               Filter Mode = Point (no filter)
               Compression = None

    c. Press [Sprite Editor] button in the Inspector to open the editor.
               Open the Slice button.
               Set Slice > Type = Grid by Cell Count: 2 x 2.
               Hit [Apply].

    d. Drag the first new image into the Hierarchy.
    In the Inspector name it "Boom" and set Order in Layer = 105.

    e. Create a folder under Project panel > Assets > Media called "Animation".

    f. Open Window > Animation > Animation.
    With Boom selected hit the [Create] button at the center, choose the new Animation folder, and name the new clip “boom_anim”. Hit [OK].

    g. Drag all 4 frames into the timeline top, spread them out between 1-30. Drag the last frame in a second time and place it at the end, for a total of 5 images set to frames 1, 10, 20, 30, 40. Hit the Animation Play triangle to see the Scene copy animate, and adjust timing as you desire. Close the Animation panel.

    h. Make it into a prefab: Drag the Boom object from the Hierarchy into the Project panel. Delete the original from the Hierarchy.

    i. DoubleClick the PlayerProjectile Prefab to open it. Drag the Boom object from the Project panel into the hitEffectAnim script slot.


    To make sure an impact effect does not hang around, apply the following script:
    ProjectileVFX_destroy.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class ProjectileVFX_destroy : MonoBehaviour{

          public float destroyTime = 1f;

          void Start(){
               StartCoroutine(DestroyMe());
         }

          IEnumerator DestroyMe(){
               yield return new WaitForSeconds(destroyTime);
               Destroy(gameObject);
          }
    }



     OPTION 3. PLAYER DEFEND (if you want a defense system for your player)
    a. Create a new C# script called PlayerDefend.cs. Add the content below and drag the script onto your Player. The commented-out code is for animation, when an Animator is applied to the character art.

    b. Import defense art .PNG: a semi-transparent sphere around the character, either a single sprite or a spritesheet for animation. Drag it into the Hierarchy to make it a child of the Player, and then drag it into the shieldArt script slot.

    c. Create a "Defend" Input:
    Open Edit > Project Settings panel to select the Input Manager.
    Open the first Fire2 and change it to "Defend".
    Set "Positive Button" = "Q" and "Alt Positive Button" = "joystick button 1".
    PlayerDefend.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PlayerDefend : MonoBehaviour{

          private GameHandler gameHandler;
          //public Animator animator;
          public Rigidbody2D rb2D;
          public GameObject shieldArt;
          public float defendTime = 2.0f;

          void Start(){
                gameHandler = GameObject.FindWithTag("GameHandler").GetComponent<GameHandler>();
               //animator = gameObject.GetComponentInChildren<Animator>();
               rb2D = transform.GetComponent<Rigidbody2D>();
               shieldArt.SetActive(false);           
          }

          void Update(){
                //Defense activation
               if (Input.GetAxis("Defend") > 0){
                      playerDefend();
                }
          }

          public void playerDefend(){
                gameHandler.isDefending = true;
                shieldArt.SetActive(true);
                //animator.SetBool("Defend", true);
                StartCoroutine(playerNoDefend());
          }

          IEnumerator playerNoDefend(){
                yield return new WaitForSeconds(defendTime);
                gameHandler.isDefending = false;
                shieldArt.SetActive(false);
                //animator.SetBool("Defend", false);
          }
    }

    NOTE: this defense functionality can be modified for player invisibility, to hide from enemies.
    Instead of removing damage, the Defense bool in the GameHandler would redirect the enemies, as if the player is not present.

     
    [1G]: FRAME SCENES
    MainMenu, EndWin, EndLose, and Credits. Also, how to replay the level a player died on.

    SCENE #1: Create a MAIN MENU:
    1) Create a new Scene, name it "MainMenu".
    2) RightClick the Hierarchy to add a UI > Canvas.
          In the Inspector set CanvasScaler > UI Scale Mode to "Scale with Screen Size".
          Set Reference Resolution = 1280 x 720.
    3) RightClick the Canvas to add UI > Image. In the Inspector name it "BG_Art".
          Set Position X and Positon Y = 0, set Width and Height = 1280x720.
          To the Image > Source Image slot add your 1280x720 background image when it is ready.
    4) RightClick the Canvas to add UI > Legacy > Text. In the Inspector name it TextTitle.
          In the text field type the title.
          Set width and height = 700x150, font = 80, bold. Move up near top. Replace with logo art when it is ready.
    5) RightClick the Canvas to add UI > Legacy > Text below it. In the Inspector name it "TextExplain".
          Set width and height = 500 x 300, font 40. In the text field type an introductory sentence.

    BUTTON work:
    6) Drag the GameHandler Prefab into the Hierarchy.
    7) RightClick the Canvas to add a UI > Legacy > Button.
      In the Inspector name it "button_MENU".
      Set width and height = 300 x 60.
      Set Highlighted Color as desired (replace the Image > Source image when your art is ready).
      Change the child Text objects text field to "MENU", set font size = 45 and bold.
      IMPORTANT: Change the Button > Navigation from "Automatic" to "None" (so [spacebar] won't click).
      Towards the bottom of the inspector, click the [+] icon to add an Onclick event.

    8) Make the Button a Prefab:
      Drag button_MENU into the Prefabs folder to make it a prefab.
      All menu buttons in frame Scenes, PauseMenu, etc should use this Prefab, so that changes can be made in the prefab and automatically populate to all Scenes.
      Naturally, if you are on a team project, only one person per team should make the Prefab, and everyone else should use it.

    9) Back in your MainMenu Scene, select the button and duplicate it twice [Ctrl / Cmd] + [d].
          Rename the buttons: "button_PLAY" "button_CREDITS", and "button_QUIT".
          Change child Text objects text field: "PLAY", "CREDITS", and "QUIT".
          Select the Buttons and arrange as a column or a row.

    10) Towards the bottom of each button in the Inspector, find the Onclick event.
          Drag the GameHandler from the Hierarchy into the "None" slot. Set the function per the button:
          PLAY: GameHandler > StartGame()
          CREDITS: GameHandler > Credits()
          QUIT: GameHandler > QuitGame()

    11) Add Placeholder UI Objects:
    RightClick the Canvas to create a UI > text object called "textPlaceholder".
    RightClick the Canvas to create a UI > image object called "imagePlaceholder".
    Select them both in the Hierarchy, and in the Scene view, hit [w] and drag them up significantly above the Canvas, out of view of the MainCamera.
    Use these to "plug holes" in any GameHandler scripts slots not meant to be used in the framing scenes, like textHealth and textTokens.

    12) Add the PauseMenu (see below):
    Have you created a PauseMenu prefab?
    Drag it onto the Canvas so it becomes the very bottom child.
    Set up the three Pause Menu buttons and volume slider Events with the local GameHandler.
    Drag the PauseMenu from the Hierarchy into GameHandler's GameHandler_PauseMenu.cs script slot.



    SCENE #2: Duplicate MainMenu to create an END SCENE: "EndWin".
    1) With MainMenu open: File > Save As to name the new file "EndWin" and save in the Scenes folder.
    2) Change TextTitle to say "YOU WIN".
    3) In TextExplain below it type 1-2 sentences to explain the player's fate.
    4) Delete the "Credits" button, change the text field of "PLAY" to "MAIN MENU" and keep "QUIT".
    5) Change the Onclick event for the "Main Menu" button to GameHandler > RestartGame().
    6) To the BG_Art Image > Source Image slot add your 1280x720 background "Win" image when it is ready.


    SCENE #3: Duplicate EndWin to create the other END SCENE: "EndLose".
    1) With EndWin open: File > Save As to name the new file "EndLose" and save in the Scenes folder.
    2) Change TextTitle to say "YOU LOSE".
    3) In TextExplain below it type 1-2 sentences to explain the player's fate.
    4)
    To the BG_Art Image > Source Image slot add your 1280x720 background "Lose" image when it is ready.


    SCENE #4: Duplicate EndLose to create the "Credits".
    1) With EndLose open: File > Save As to name the new file "Credits" and save in the Scenes folder.
    2) Change TextTitle to say "CREDITS".
    3) In TextExplain below it type the names of all contributers. Optionally include roles, special thanks, etc.
    4) Delete the "QUIT" button, leaving only "MAIN MENU".
    4)
    To the BG_Art Image > Source Image slot add your 1280x720 background "Credits" image when it is ready.



    ADD ALL SCENES TO Build Settings:
    Open File > Build Settings to drag all Scenes made so far into the top section. The order does not matter so long as the MainMenu is first: Move MainMenu to the top.

    Hit [Player Settings] to add your team name and game name near the top.

    Standalone build: under Resolution and Presentation set FullscreenMode to "Windowed" (1280x720) and turn on "Resizeable Window".





    WANT TO REPLAY A LEVEL INSTEAD OF GOING BACK TO START?
    If your player loses a level, do you want them to be able to replay that level right away, rather than being forced to play the entire game again up to that point?
    This is already supported in the GameHandler.cs script, and you just need to make the button.

    Here are the steps:
    Duplicate an existing button in the EndLose Scene.
    Name it "Button_Replay".
    Set the child text object text field to read "REPLAY LAST LEVEL".
    In the OnClick() (Inspector bottom) set the GameHandler.cs function to ReplayLastLevel().

    NOTE: You still need a separate "MAIN MENU" button that always goes back to the MainMenu.


     
    [1H]: MENUS
    PauseMenu  |   Shop  |   MiniMap

    Menus are added to the main Canvas HUD ("Heads Up Display").

    Creat the GameObject > UI > Canvas. In the Inspector, set Canvas Scaler > UI Scale Mode from Constant Pixel Size to Scale with Screen Size, 1280 x 720.

    Note that in the Scene view the Unity Canvas displays enormous compared to the Sprite / Tilemap content (by design), but they overlap perfectly in the Game view.


     PAUSE MENU:
    Add the following PauseMenu to the Canvas in each Scene.

    Create it once and drag it into the Project panel to make it a Prefab, to easily add to the other Scenes and update as needed.

    Use the GameHandler script for all PauseMenu methods.

    Delete the [ESC]-Quit option from Update so the Pause Menu toggles on / off when hitting [ESC].



    PAUSE MENU STEPS:
    IMPORTANT PREFAB NOTES:

    Use SCENE objects for SCRIPT SLOTS!

  • When dragging the PauseMenu into the pauseMenuUI script slot, be sure to drag in the PauseMenu from inside the Scene, NOT the Prefab from the Project panel.

  • Similarly, when dragging the GameHandler into the PauseMenu button OnClick and Slider functions, be sure to drag in the GameHandler from inside the Scene, NOT the Prefab from the Project panel.
  • 1. Create the parent Image object:
        a. Select your Canvas, RightClick to create a UI > Image.
        b. In Inspector name it "PauseMenu".
            Set PosX and PosY = 0, size 1280 x 720.
        c. Click the color rectangle to set alpha=0 (to make it invisible).

    2. Create the Background Image object:
        a. RightClick PauseMenu to create a UI > Image.
            (it should automatically be a child of PauseMenu)
        c. In Inspector name it "BG".
            Set PosX and PosY = 0, size 600 x 700.
        d. Click the color rectangle to set a dark color and alpha=150
            (to make it semi-transparent)

        Want to add custom art?
            Import a PNG image to your project (600x700).
            Add to the Source Image slot in BG.

    3. Create two Text labels: PAUSED and VOLUME:
        a. RightClick PauseMenu to create a UI > Legacy > Text.
            (because it is lower than BG in the Hierarchy, the Text should appear on top of BG in the Scene view).
        c. In Inspector rename it "text_Paused". Set PosX = 0, Y=200, and size 500 x 80.
        d. Set Text field to "PAUSED", set font bold and size=60, align centered. Set color as desired.
        e. Select textPaused in the Hierarchy and duplicate ([Ctrl/Cmd] + [D]) to make a second label.
            Name the second label "text_Volume" and Set text to "VOLUME". Drag down closer to middle.

    4. Add 1st Button: Resume:
        a. Find the button_MENU Prefab made for the frame Scenes in [1G].
        b. In the Hierarchy drag the Button onto the PauseMenu to make it a child.
        c. In Inspector rename it "button_Resume". Set Position X = 0.
        d. Set Button text to "RESUME".
    NOTE: Any style changes to button text font, button image sprite, or Button colors (Normal Color, Highlighted Color, Pressed Color) shoudl be made in the prefab, so they populate to all Scenes!

    5. Duplicate for 2nd and 3rd buttons: Restart and Quit:
        a. Select ButtonResume in the Hierarchy and duplicate ([Ctrl/Cmd] + [D]) to make a second button.
            Name the second button "ButtonRestart" and Set text to "RESTART".
       b. Select ButtonResume and duplicate ([Ctrl/Cmd] + [D]) again to make a third button.
            Name the third button "ButtonQuit" and Set text to "QUIT".

    6. Create the volume Slider:
        a. RightClick your Canvas to create a UI >Slider.
        b. In the Inspector, with anything selected, add a new Tag called "PauseMenuSlider".
        c. Select your Slider and in the Inspector add the new tag "PauseMenuSlider".
        d. Set Slider Min Value to 0.0001 (leave Max Value at 1) and Value = 1.


    7. Create the Audio Mixer:
        a. Open Window > Audio > AudioMixer.
        b. In the Mixer panel hit [+] to create a new mixer. Name it "MyMusicMixer".
        c. Select Mixer Groups > Master.
        d. In Inspector RightClick Volume to choose: "Expose 'Volume (of Master)' to Script."
        e. In UpperRight corner of Mixer click Exposed Parameters rolldown.
        f. Change name "Volume of Master" to "MusicVolume".


    8. Create a new C# script named GameHandler_PauseMenu.cs:
    Add the content below into this new script. Save the script and return to Unity.
    Apply this script to the GameHandler object.

    GameHandler_PauseMenu.cs:
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    using UnityEngine.Audio;

    public class
    GameHandler_PauseMenu : MonoBehaviour {

            public static bool GameisPaused = false;
            public GameObject
    pauseMenuUI;
            public AudioMixer
    mixer;
            public static float
    volumeLevel = 1.0f;
            private
    Slider sliderVolumeCtrl;

            void Awake (){
                    SetLevel (volumeLevel);
                    GameObject sliderTemp = GameObject.FindWithTag("PauseMenuSlider");
                    if
    (sliderTemp != null){
                            sliderVolumeCtrl = sliderTemp.GetComponent<Slider>();
                            sliderVolumeCtrl.value = volumeLevel;
                    }
            }

            void Start (){
                    pauseMenuUI.SetActive(false);
                    GameisPaused = false;
            }

            void Update (){
                    if
    (Input.GetKeyDown(KeyCode.Escape)){
                            if
    (GameisPaused){
                                    Resume();
                            }
                            else
    {
                                    Pause();
                            }
                    }
            }

            public void Pause(){
                    pauseMenuUI.SetActive(true);
                    Time.timeScale = 0f;
                    GameisPaused = true;
            }

            public void Resume(){
                    pauseMenuUI.SetActive(false);
                    Time.timeScale = 1f;
                    GameisPaused = false;
            }

            public void SetLevel (float sliderValue){
                    mixer.SetFloat("MusicVolume", Mathf.Log10 (sliderValue) * 20);
                    volumeLevel = sliderValue;
            }
    }


    Add bold-faced content to GameHandler.cs:
          public void RestartGame() {
                Time.timeScale = 1f;
                GameHandler_PauseMenu.GameisPaused = false;
                SceneManager.LoadScene("MainMenu");
                    // Please also reset all static variables here, for new games!
                playerHealth = StartPlayerHealth;
          }

          // Replay the Level where you died
          public void ReplayLastLevel() {
                Time.timeScale = 1f;
                GameHandler_PauseMenu.GameisPaused = false;
                SceneManager.LoadScene("lastLevelDied");
                 // Reset all static variables here, for new games:
                playerHealth = StartPlayerHealth;
          }


    9. Fill Hierarchy GameHandler script slots:
        a. Select the GameHandler in the Hierarchy (NOT the prefab in the Project panel).
        b. In the Inspector, into the "pauseMenuUI" script slot drag your Hierarchy PauseMenu object.
        c. For the "mixer" script slot hit the hotspot to load your MyMusicMixer object.

    10. Set up Buttons functionality:
        a. Select each Button, in Inspector find OnClick field, hit [+].
        b. Drag Hierarchy GameHandler object into the "None" slot.
        c. Click [No Function] to find the GameHandler_PauseMenu() script and choose the corresponding function:
            Resume() for Resume

    For these buttons find the functions under basic GameHandler():
            QuitGame() for Quit
            RestartGame() for Restart


    11. Set up Slider functionality:
        a. Select the Slider.
        b. In Inspector find the OnValueChanged field and hit [+].
        c. Drag GameHandler into None,
        d. Click [No Function] to find the GameHandler() script.
        At top of options under Dynamic floatchoose "SetLevel"
        (use the top one, NOT the bottom one).


    12. Add an "Audio Manager" to your Scene:
        a. Create an Empty GameObject, name it "AudioManager"
        b. Drag an audio file onto this object to add it as an Audio Source Component (music or other ambient sounds, as .MP3 or .WAV).
        c. Under the AudioSource Component > Output, hit the hotspot to add the MyMusicMixer Master.
        d. Drag the AudioManager object into the Project panel to make it a Prefab so you can easily add it to other Scenes, where the music file can be changed.

    13. IMPORTANT: The default Update() function in the GameHandler.cs script uses the [ESC] key to quite the game. This needs to be deleted or commented out this code, or otherwise every time you try to hit [ESC] to pause the game in the actual build, it will instead close the game.

    To playtest, hit Play and at any time tap the [Esc] key to toggle your Pause Menu!
    Turn off Play for the final step.


    Want a BUTTON to open the Pause Menu (because [esc] breaks out of full-screen mode in itch.io)?
    RightClick your Canvas to create a UI > Button.
    In the Inspector set width and height to a square (try 100x100). Name it "Button_PAUSE".
    In the Hierarchy open the button to select the Text object. In the Inspector set text to [ | | ], size 80 and bold.
    Select the the button and move it to the upper-left or lower-right corner of your Canvas.
    In the Inspector add an OnClick event, drag the GameHandler object from the Hierarchy into the "None"-slot, and select the function GameHandler_PauseMenu > Pause.


    14. FINALLY, REPLICATE PAUSE MENU AS PREFAB:
        a. Drag the PauseMenu into the Project panel to make a Prefab.
        b. Then, drag this PauseMenu Prefab into all Scenes, onto the existing Canvas object (to parent).
            The GameHandler Prefab should be in each Scene, as well.
        c. Redo steps 8, 9, and 10 for each Scene:
            Fill the GameHandler PauseMenu script slots in the Inspector.
            Connect the Buttons to their functions.
            Connect the Slider to the top dynamic float SetLevel function.

    SCRIPT SLOT REMINDER: The only time we drag a Prefab from the Prefabs folder into an active Scene script slot is when we are instantiating (spawning) a bullet or enemy, etc. All BUTTON ONCLICK slots should be populated with a LOCAL SCENE COPY of the script holder (the GameHandler).


      MAKING A SHOP:
    Create a menu to display Player options for purchasing upgrades or in support of other mechanics.

    a. Select the Canvas and create a button to open the Shop menu: GameObject > UI > Legacy > Button.
    In the Inpector name it "ButtonOpenShop", size 200x60.
    In the child Text object, set font size = 40, and text field to "SHOP".
    Move the button to the upper right-corner of the Canvas.
    NOTE: This Shop button can be always visible in game, or only visible when reaching milestones.

    b. Select the Canvas and create a GameObject > UI > Image. In the Inspector name it "ShopMenu".
    Set Position X and Y = 0, Width and Height = 1280 x 720, and color alpha = 0 (fully transparent).

    c. Select ShopMenu and add a UI > Image. In the Inspector name it "ShopBG". Set Position X and Y = 0, Width and Height = 700 x 650, and a fun color (if you are adding a custom sprite image, set color to white).
    If it was not automatically parented to ShopMenu, drag ShopBG onto ShopMenu to make it a child.

    d. Add title text: add a UI > Legacy > Text.
    In the Inspector name it "TextTitle", set Positon X and Y =0, set Width and Height = 650 x 100.
    Enter the title of the shop into the text field, set the font size = 60, bold, and align center.
    Drag TextTitle onto ShopMenu to make it a child (must sit below ShopBG in the Hierarchy to be visible).

    e. Create a UI > Legacy > Button, name it "ButtonCloseShop", size 60x60. In the child Text object, set font size=40, and text field to just "X". Drag onto ShopBG and locate in the upper right-corner of the Shop.

    f. For a purchase-able element create the following four objects:
  • GameObject > UI > Image, name it "Item1Frame", size 250x300. This can display a simple frame Sprite.
  • GameObject > UI > Image, name it "Item1Art", size 220x220. This will display your item Sprite.
  • GameObject > UI > Legacy > Text, name it "Item1NameCost", size 250 x 60, Font Size 40, Bold, align=center.
  • GameObject > UI > Legacy > Button, name it "Item1BuyButton", size 250 x 60, Text Font Size 40, field= "BUY".
    Parent Item1Frame onto ShopMenu, and the other 3 onto Item1Frame.
    Arrange the elements of item 1 in the frame: Image top, then name/cost, buy-button at bottom.
    Duplicate Item1Frame (select, hit [Ctrl/Cmd]+[d]) for 2 more items, rename elements for item2 and item3.

    g. Create a new C# script named "ShopMenu.cs" with the content below.
    Save and add this script to your GameHandler prefab.
    Fast-Drag all elements into their respective script slots.
    Set item costs as desired (make sure name texts match the variables in the ShopMenu.cs script).
    AUDIO NOTE: Want to play a happy ka-ching sound with every purchase? Un-comment all relevant lines.


    IMPORTANT:
  • Create this menu once, drag into Project panel to make it a Prefab, add the Prefab to the other Scenes.
  • When dragging the ShopMenu into the shopMenuUI script slot, be sure you are dragging the ShopMenu prefab from inside the Scene, NOT the Prefab from the Project panel!
  • Similarly, when dragging the GameHandler into the ShopMenu buttons OnClick functions, be sure you are dragging in the GameHandler from inside the Scene, NOT the Prefab from the Project panel!
  • GAMEHANDLER NOTE: the gameHandler reference allows this script to access the local instance of the GameHandler object and script to run the GameHandler function playerGetTokens().
    The direct references to the GameHandler class are accessing static variables, like gotTokens.


    ShopMenu.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.UI;
    //using UnityEngine.Audio;

    public class ShopMenu : MonoBehaviour{

          public GameHandler gameHandler;
          public static bool ShopisOpen = false;
          public GameObject shopMenuUI;
          public GameObject buttonOpenShop;

          public GameObject item1BuyButton;
          public GameObject item2BuyButton;
          public GameObject item3BuyButton;

          public int item1Cost = 3;
          public int item2Cost = 4;
          public int item3Cost = 5;
          //public AudioSource KaChingSFX;

          void Start (){
                shopMenuUI.SetActive(false);
                gameHandler = GameObject.FindWithTag("GameHandler").GetComponent<GameHandler>();
          }

          void Update (){
                if ((GameHandler.gotTokens >= item1Cost) && (GameHandler.gotitem1 == false)) {
                            item1BuyButton.SetActive(true);}
                else { item1BuyButton.SetActive(false);}

                if ((GameHandler.gotTokens >= item2Cost) && (GameHandler.gotitem2 == false)) {
                            item2BuyButton.SetActive(true);}
                else { item2BuyButton.SetActive(false);}

                if ((GameHandler.gotTokens >= item3Cost) && (GameHandler.gotitem3 == false)) {
                            item3BuyButton.SetActive(true);}
                else { item3BuyButton.SetActive(false);}
          }

          //Button Functions:
          public void Button_OpenShop(){
               shopMenuUI.SetActive(true);
               buttonOpenShop.SetActive(false);
               ShopisOpen = true;
               Time.timeScale = 0f;
          }

          public void Button_CloseShop() {
               shopMenuUI.SetActive(false);
               buttonOpenShop.SetActive(true);
               ShopisOpen = false;
               Time.timeScale = 1f;
          }

          public void Button_BuyItem1(){
                gameHandler.playerGetTokens((item1Cost * -1));
                GameHandler.gotitem1 = true;
                //KaChingSFX.Play();
          }

          public void Button_BuyItem2(){
                gameHandler.playerGetTokens((item2Cost * -1));
                GameHandler.gotitem2 = true;
                //KaChingSFX.Play();
          }

          public void Button_BuyItem3(){
                gameHandler.playerGetTokens((item3Cost * -1));
                GameHandler.gotitem3 = true;
                //KaChingSFX.Play();
          }
    }


    h. Finally, adjust the GameHandler.cs script to include static bools that acknowledge the purchased item/s (which can be used by other scripts to determine playerbuffs or other abilities). The GameHandler also needs a static int variable for the accumulated, spend-able tokens (like gold, etc) and the function to adjust that value ():

    add to GameHandler script:

          public static bool gotitem1 = false;
          public static bool gotitem2 = false;
          public static bool gotitem3 = false;


    Hit [Play] to test, turn off play to resume editing.





      MAKING A MINIMAP:
    (This tutorial is a work-in-progress, sourced here: 01. Alternate system: 02)
    Have a large complex space for the player to traverse, and want to support player navigation?
    One method is to create a menu to display Player position in a corner mini-map.

    a. Make the MAP:
    Take a top-down screenshot of the map layout, and in Photoshop prepare it as a 512x512 PNG.
    You can leave all the details of the screenshot, or stylize it as a white cut-out of the same shape, etc.
    Import into Unity.
    RightClick the Scene (or prefab) Canvas to create a UI > Image.
    In the Inspector name the image "MiniMap", locate in a corner of the Canvas.

    b. Make the PLAYER ICON (Canvas Image):
    RightClick the MiniMap in the Canvas to create another UI > Image.
    In Inspector name it "playerInMap", scale it small, and color it to be easy to see against the map (red, etc)
    Create a new tag called "mm_playerInMap, " and apply it to playerInMap.

    c. Make the MAP END(Canvas Image):
    RightClick the MiniMap in the Canvas to create a third UI > Image.
    In Inspector name it "miniMapEnd", scale it small, and color it transparent.
    Move it to the upper-right corner of the MiniMap in the Canvas.
    Create a new tag called "mm_miniMapEnd" and apply it to miniMapEnd.

    d. Make the MAP PARENT (Empty GO):
    RightClick the Hierarchy to make an Empty Game Object.
    In Inspector name it "mapParent".
    IMPORTANT: Move to the exact center of the ACTUAL gameplay map (in a 2D game, Positon Z = 0).
    Create a new tag called " mm_mapParent" and apply it to mapParent.

    e. Make the MAP PARENT END (Empty GO):
    RightClick the mapParent to make another Empty Game Object.
    In Inspector name it "mapParentEnd".
    IMPORTANT: Move it to the upper-right corner of the ACTUAL gameplay map.
    Create a new tag called "mm_mapParentEnd" and apply it to mapParentEnd.

    f. Create a new C# script named "MiniMap.cs" with the content below.
    Save and add this script to your PLAYER prefab.
    Fast-Drag all 4 elements into their respective script slots.


    MiniMap.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class MiniMap : MonoBehaviour{

          public RectTransform playerInMap;
          public RectTransform miniMapEnd;
          public Transform mapParent;
          public Transform mapParentEnd;
          private Vector3 normalized, mapped;

          void Start(){
                playerInMap = GameObject.FindWithTag("mm_playerInMap").GetComponent<RectTransform>();
                miniMapEnd = GameObject.FindWithTag("mm_miniMapEnd").GetComponent<RectTransform>();
                mapParent = GameObject.FindWithTag("mm_mapParent").GetComponent<Transform>();
                mapParentEnd = GameObject.FindWithTag("mm_mapParentEnd").GetComponent<Transform>();
          }

          private void Update(){
                normalized = Divide(
                      mapParent.InverseTransformPoint(this.transform.position),
                      mapParentEnd.position - mapParent.position
                );
                normalized.y = normalized.z;
                mapped = Multiply(normalized, miniMapEnd.localPosition);
                mapped.z =0;
                playerInMap.localPosition = mapped;
          }

          privatestatic Vector3 Divide(Vector3 a, Vector3 b){
                return new Vector3(a.x / b.x, a.y / b.y, a.z / b.z);
          }

          private static Vector3 Multiply(Vector3 a, Vector3 b){
                return new Vector3(a.x * b.x, a.y * b.y, a.z * b.z);
          }
    }

    OPTIONALLY, ADD A MASK (source 01   |   02 ):
    Want to mask the map in this Circle PNG?
    RightClick the Canvas to create a new UI > Image.
    In the Inspector name it "MiniMap_Mask" and add the Cicle PNG to the Source Image slot.
    [Add Component] UI > Mask.
    Drag MiniMap onto MiniMap_Mask to make it a child. The Mask automatically applies!
    [Add Component] UI > ScrollRect. Drag the MiniMap object into the Content script slot.
    This allows the player to click and drag the map.

    A Border Ring PNG can be added as an image below the masked MiniMap.
    Create a UI > Image, size the same as the Mask, add the PNG to Source Image.
    Turn off Raycast Target so the ring image does not block interactivity (clicking and dragging the ScrollRect)


  •  
    [1I]: LEVEL CHANGES
    1. Simple Door  |   2. Two-Step Door with a pop-up message  |   3. Door opens by flipping a Switch
      4. Door opens by getting enough objects 
      |   5. Optional Transitons  (cross fade, wipe, etc)


      OPTION 1. Keep it simple:
    SUMMARY:
    This script creates no entry requirements, and is always open.
    a. Create an Empty GameObject. In the Inspector name it "Door" and reset Transforms.
    b. [Add Component] a Physics2D > BoxCollider2D, turn on isTrigger.
    c. Drag a door/portal image sprite onto Door to make it a child. .
    d. In the Inspector type the name of the destination Scene into the NextLevel script slot (defautls to MainMenu).
    e. Make sure the destination Scene is loaded into the Build Settings:
        Open File menu > Build Settings, drag the destination Scene into the top area.

    When the Player touches it they will be transported to the Scene typed into the NextLevel script slot.

    Note the critical namespace library at top of the script that allows Scene switching to happen:
                using UnityEngine.SceneManagement


    DoorExitSimple.cs script: (simple, no conditions)
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.SceneManagement;

    public class DoorExitSimple : MonoBehaviour{

          public string NextLevel = "MainMenu";

          public void OnTriggerEnter2D(Collider2D other){
                if (other.gameObject.tag == "Player"){
                      SceneManager.LoadScene (NextLevel);
                }
          }

    }


     OPTION 2. Two-Step Simple Door:
    With this script the player (1) enables the door by colliding with its isTrigger Collider, and then (2) activates the door with a key-press.

    a. Create an Empty Game Object. In the Inspector name it "Interactable_Door" and reset Transforms.
    [Add Component] Physics2D > BoxCollider2D and turn on isTrigger.

    b. Create or download this press-e message, import into the Project. Set desired Properties in Inpector and drag into the Hierarchy onto Interactable_Door to make it a child.

    c. For the Door visual, either use a Door Sprite (drag into the Hierarchy and onto Interactable_Door) OR choose a Door image in the Background art. Move Interactable_Door into position.

    d. Select Interactable_Door and under the BoxCollider2D hit Edit Collider to surround the door and the space below and around it.

    e. Create a new C# script "InteractableDoor" with the following content. Apply this script to Interactable_Door. Drag the press-e message sprite into the script slot.

    When the Player touches the collider they will see the message and when they press the key they will be transported to the Scene typed into the NextLevel script slot.


    InteractableDoor.cs script: (Collider enables door, key-press activates it)
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.SceneManagement;

    public class InteractableDoor : MonoBehaviour{

            public string NextLevel = "MainMenu";
            public GameObject msgPressE;
            public bool canPressE =false;

           void Start(){
                  msgPressE.SetActive(false);
            }

           void Update(){
                  if ((canPressE == true) && (Input.GetKeyDown(KeyCode.E))){
                         EnterDoor();
                  }
            }

            void OnTriggerEnter2D(Collider2D other){
                  if (other.gameObject.tag == "Player"){ ;
                         msgPressE.SetActive(true);
                         canPressE =true;
                  }
            }

            void OnTriggerExit2D(Collider2D other){
                  if (other.gameObject.tag == "Player"){
                         msgPressE.SetActive(false);
                         canPressE = false;
                  }
            }

          public void EnterDoor(){
                SceneManager.LoadScene (NextLevel);
          }

    }


     OPTION 3a. Open Door Depending on a Flipped Switch:

    a. Create an Empty GameObject. Name it "DOOR_SWITCH_SYSTEM", reset Tranforms.

    b. Create a second Empty GameObject.
  • In Inspector name it "DoorExit_Switch", reset Transforms.
  • [Add Component] Physics2D > BoxCollider2D, turn on isTrigger.
  • Create and apply the tag "Door" to this object (so DoorSwitch can find and communicate with it).
  • Drag DoorExit_Switch onto DOOR_SWITCH_SYSTEM to make it a child.

    c. Create or download 2 PNG images for the Door: one open and one closed (64 x 64).
    Import them into the Unity project panel (RightClick the Project panel, choose Import New Asset, browse to the asset/s on your computer to select them).

    To use the provided provided spritesheets.
    In the Inspector change the following settings and hit [Apply]:
          Sprite Mode = Multiple
          Pixels per Unit = 16
          Filter Mode = Point (no filter)
          Compression = none.
    Hit [Apply]. In the [Sprite Editor] Slice by cell count, drag the specific Sprites for each door into the Hierarchy.

    d. Drag the 2 door images into the Hierarchy, and make them children of DoorExit_Switch. They should appear directly on top of each other. In the Inspector name the Hierarchy copies "Door_open" and "Door_closed" and set their Order In Layer to 90.

    e. Create a new C# script "DoorExit_Switch.cs" with the following code. This script enables the collider to allow passage (and swap the door dsplay from closed to open) IF the Player has found and hit the associated switch.
  • Add this script to the DoorExit_Switch object.
  • In the Inspector, drag the 2 images from the Hierarchy into the DoorClosedArt and DoorOpenArt script slots.
  • Also set the Next Scene text to the name of the level this door is meant to allow the Player to enter.

    DoorExit_Switch.cs script: (with switch-flipping condition)
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.SceneManagement;

    public class DoorExit_Switch : MonoBehaviour{

          public GameObject DoorClosedArt;
          public GameObject DoorOpenArt;
          public string NextScene = "MainMenu";

          void Start(){
                DoorClosedArt.SetActive(true);
                DoorOpenArt.SetActive(false);
                gameObject.GetComponent<Collider2D>().enabled = false;
          }

          public void DoorOpen(){
                DoorClosedArt.SetActive(false);
                DoorOpenArt.SetActive(true);
                gameObject.GetComponent<Collider2D>().enabled = true;
          }

          void OnTriggerEnter2D(Collider2D other){
                if (other.gameObject.tag == "Player"){
                      SceneManager.LoadScene(NextScene);
                }
          }
    }

    Temporarily hide the DoorExit_Switch object and its children by selecting DoorExit_Switch and turnign off the checkbox in the Inspector upper-left corner.


      OPTION 3b. Make the Switch:

    a. Create a third Empty GameObject.
    In the Inspector name it "DoorSwitch", reset Transforms.
    [Add Component]
    Physics2D > BoxCollider2D, turn on isTrigger.
    Drag DoorSwitch onto DOOR_SWITCH_SYSTEM to make it a second child.

    b. Create or download two PNG images for the Switch: one left and one right (32 x 32).
    Set and Apply Inspector Sprite properties (see (c) above).
    Import them into the Unity project panel, drag them onto DoorSwitch.

    c. Create a new C# script named "DoorSwitch.cs" with the following code (This script, on collision with the Player, swaps art to "flip" the switch, and sends a message to DoorExit_Switch on the object with tag "Door").
    Add this script to the DoorExit_Switch object.
    In the Inspector, drag the 2 images from the Hierarchy into DoorClosedArt and DoorOpenArt script slots.
    Also set the Next Scene text to the name of the level this door is meant to allow the Player to enter.

    DoorSwitch.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class DoorSwitch : MonoBehaviour{

          public GameObject SwitchOffArt;
          public GameObject SwitchOnArt;
          public GameObject DoorObj;

          void Start() {
                SwitchOffArt.SetActive(true);
                SwitchOnArt.SetActive(false);
                DoorObj = GameObject.FindWithTag("Door");
          }

          void OnTriggerEnter2D(Collider2D other){
                if (other.gameObject.tag == "Player"){
                      SwitchOffArt.SetActive(false);
                      SwitchOnArt.SetActive(true);
                      DoorObj.GetComponent<DoorExit_Switch>().DoorOpen();
                }
          }
    }




     OPTION 4. Open Door Depending on Acquired Pickups:
    This script keeps the door closed (disables its collider) until the player has managed to acquire a certain number of items (like gems or a key), at which point the collider is activated, the art switches to the open door, and the player can "enter".

       Apply this script to a Door object with a BoxCollider2D and two child images, one open and one closed.
       Enter the intended destination Scene name in the Inspector in the NextLevel script slot.

     
    DoorExit_Items.cs script: (with item collection condition)
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.SceneManagement;

    public class DoorExit_Items : MonoBehaviour{
          // NOTE: This script depends on the GameHandler having a public int "thePieces"
          // that is updated with each pickup collected.
          public GameHandler gameHandler;
          public string NextLevel = "MainMenu";
          public GameObject exitClosed;
          public GameObject exitOpen;
          public int piecesCollected;
          public int piecesNeeded = 1;

          void Start(){
                gameHandler = GameObject.FindWithTag("GameHandler").GetComponent<GameHandler>();
                exitClosed.SetActive(true);
                exitOpen.SetActive(false);
                gameObject.GetComponent<Collider2D>().enabled = false;
          }

          void Update(){
                piecesCollected = gameHandler.thePieces;
                if (piecesCollected >= piecesNeeded){
                      exitClosed.SetActive(false);
                      exitOpen.SetActive(true);
                      gameObject.GetComponent<Collider2D>().enabled = true;
                }
                else {
                      gameObject.GetComponent<Collider2D>().enabled = false;
                }
          }

          public void OnTriggerEnter2D(Collider2D other){
                if (other.gameObject.tag == "Player"){
                      SceneManager.LoadScene (NextLevel);
                }
          }
    }


       For this script to function, the GameHandler.cs script needs two additions:
          a) Add a new variable to the top variables:
                public int thePieces =0; // if not static, will reset with each scene.

          b) Add a new function to increase this number:
                public void AddPiece(){
                      thePieces += 1;
                }

       Also, your Pickup objects script (see [2a]) needs to reference the GameHandler, so thePieces can increase:
          a) Add to top variables:
                public bool isPieces = false; //an option the level designer can turn on in the inspector.

          b) Add to OnTriggerEnter2D() function:
                    if (isPieces == true){
                          gameHandler.AddPiece();
                    }


     VISUAL EFFECT. Add Scene Transitions:
    Want to create a Scene Transition, like a cross fades or wipe?
    Try this video tutorial


  •  
    [1J]: MULTIPLAYER: LOCAL vs NETCODE
    LOCAL MULTIPLAYER (same computer)
    1. Multi-player controls with the Legacy Input System
    2. Split Screen: Multi-Camera Set-Up
    3. Make the Prefab
    4. Scoring for multiple players (TO COME)
    5. Notes on enemy targeting for multi-player (TO COME)
    NETCODE (different computers)
    1. Netcode for Game Objects to transmit data
    2. Relay for connecting players




      LOCAL MULTIPLAYER PART 1. Multi-Player Inputs and Movement Controls:
    SUMMARY: Create two player characters and apply the same movement script with unique Input Axis.
    (This is also how to set your game functions to alternate inputs, like an XBox controller)


    a. Create your custom Input Axis:
    Open the Input Manager:
        on a PC: Edit > Project Settings > Input Manager.

    Open the > Axes twirly to view all current Axes.
        Increase the Size by 4 to add four more at the bottom.

    Open the new 4 Axes (click their twirlies)

        Set the following parameters (Snap = on, Invert = off):



    NAME: p1Horiz p1Vert p2Horiz p2Vert
    NEGATIVE BUTTON: s a left down
    POSITIVE BUTTON: w d right up
    GRAVITY: 3 3 3 3
    DEAD: 0.001 0.001 0.001 0.001
    SENSITIVITY: 3 3 3 3
    TYPE: Key or Mouse Button
    AXIS: X axis Y axis X axis Y axis
    JOY NUM: Get Motion from all Joysticks

    To use Xbox controllers also, create an additional 4 Axis with the same axis names, but these settings:
    NAME: p1Horiz p1Vert p2Horiz p2Vert
    NEGATIVE BUTTON:        
    POSITIVE BUTTON:        
    GRAVITY: 0 0 0 0
    DEAD: 0.19 0.19 0.19 0.19
    SENSITIVITY: 1 1 1 1
    TYPE: Joystick Axis
    AXIS: X axis Y axis X axis Y axis
    JOY NUM: Joystick 1 Joystick 1 Joystick 2 Joystick 2


    NOTE: For attack or jump buttons, etc, two new Axis need to be made for each player:
    a keyboard/Mouse input and an XBox controller button input.
    Open existing examples, like the Jump or Fire Axis, to see settings.







    b. Make a Player Character parent object:
    Create a GameObject > Empty GameObject.
    In the Inpector name it "Player1" and Reset Transforms.
    [Add Component] Physics2D > Rigidbody2D. Set type = Kinematic and disable Constraints > Rotation Z.
    [Add Component] Physics2D > BoxCollider2D. Set size to surround lower half.

    c. Add Player Art:
    Drag your Sprite 2D image from the Project panel into the Hierarchy (or create GameObject > 2D > Square).
    In Inspector: name it "player1_art" and Reset Transforms, then set Scale as needed. Order in layer = 100.
    Drag player1_art onto Player1 to make it a child.
    NOTE: To move Player1, carefully select Player1 in the Hierarchy. Clicking in the Scene view only selects art.

    d. Create a new C# script "MultiPlayerMove.cs" with the following content.
    Apply this script to the Player1 parent object (below the Rigidbody2D and BoxCollider2D).
    Enable "isPlayer1" (turn on checkbox in Inspector, if it is off).


    MultiPlayerMoveAround.cs script:
    (modifes the top-down move script, changes in bold. Platformer move / jump scripts can work, too)
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class MultiPlayerMoveAround : MonoBehaviour{

          //public Animator anim;
          //public AudioSource WalkSFX;
          public Rigidbody2D rb2D;
          private bool FaceRight = true; // determine which way player is facing.
          public static float runSpeed = 10f;
          public float startSpeed = 10f;
          public bool isAlive = true;
          public bool isPlayer1 =false;

          private Vector3 hvMove;

          void Start(){
               //anim = gameObject.GetComponentInChildren<Animator>();
               rb2D = transform.GetComponent<Rigidbody2D>();
          }

          void Update(){
             //NOTE: Horizontal axis: [a] / left arrow is -1, [d] / right arrow is 1
             //NOTE: Vertical axis: [w] / up arrow, [s] / down arrow
            if (isPlayer1 == true){
           
          hvMove = new Vector3(Input.GetAxis("p1Horiz"), Input.GetAxis("p1Vert"), 0.0f);
            }
            else {
                  hvMove = new Vector3(Input.GetAxis("p2Horiz"), Input.GetAxis("p2Vert"), 0.0f);
            }


     
           if (isAlive == true){
                      transform.position = transform.position + hvMove * runSpeed * Time.deltaTime;

                      if ((Input.GetAxis("Horizontal") != 0) || (Input.GetAxis("Vertical") != 0)){
               
          //     anim.SetBool ("Walk", true);
               
          //     if (!WalkSFX.isPlaying){
               
          //           WalkSFX.Play();
               
          //     }
                      } else {
               
          //     anim.SetBool ("Walk", false);
               
          //     WalkSFX.Stop();
                     }

                      // Turning. Reverse if input is moving the Player right and Player faces left.
                     if ((hvMove.x <0 && !FaceRight) || (hvMove.x >0 && FaceRight)){
                            playerTurn();
                      }
                }
          }

          private void playerTurn(){
                // NOTE: Switch player facing label
                FaceRight = !FaceRight;

                // NOTE: Multiply player's x local scale by -1.
                Vector3 theScale = transform.localScale;
                theScale.x *= -1;
                transform.localScale = theScale;
          }

    }

    e. Duplicate for Player2:
    Select Player1 and drag it into the Project panel > Assets folder to make it a Prefab. Rename Player_Prefab.
    Back in the Hierarchy, select Player1 and hit [Ctrl / Cmd] + [d] to duplicate.
    In the Inspector name it "Player2" and the art "player2_art". Replace the source image if available.
    Disable "isPlayer1" (turn off checkbox in Inspector).

    Move Player1 and Player2 away from each other in the Scene, so they do not overlap.
    REMEMBER!:
    To move a Player, carefully select the Player parent in Hierarchy. Clicking in Scene only selects art.

    Hit Play to test the separate movement controls (WASD and arrows). Turn off Play to resume.

    NOTE: Please see section [1C] of this tutorial for instructions on implementing animation for each Player.



      LOCAL MULTIPLAYER PART 2. Multi-Camera Set-Up:
    SUMMARY: Create a split-screen view to separately display each character, and then dynamically switch to a shared full-screen view when the players are in close enough proximity.

    a. Create two new GameObject > Camera objects.
    In the Inspector name them "Camera_P1" and "Camera_P2".
    Be sure to Reset Transforms, and set Position Z = -10.
    Find the AudioListener component on each and disable it (turn off the component check box).
    Temporarily turn off the MainCamera by clicking the disable checkbox in the Inspector upper-left.

    b. Change the Camera display settings:
    The "Viewport Rect" parameters set the camera size relative to the full screen (W = Width, H = Height) and the position on the screen (X = Horizontal, Y = Vertical).
    Select Camera_P1, find Viewport Rect and set W = 0.5 (this sets width to 50%).
    Select Camera_P2, set W = 0.5 and also X = 0.5 (sets position to start at half screen).
    Look in the Game view. These cameras now each occupy half the screen, displaying a split screen

    c. Create a new C# script "CameraFollow.cs" with the following content.
    Apply this script to each of the three cameras in the Scene.
    Select Camera_P1, quick-drag Player1 into the "target" script slot.
    Select Camera_P2, quick-drag Player2 into the "target" script slot (CameraMain target is step (d)).


    CameraFollow.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class CameraFollow : MonoBehaviour{

          public GameObject target;
          public float camSpeed = 4.0f;

          void FixedUpdate () {
                Vector2 pos = Vector2.Lerp ((Vector2)transform.position, (Vector2)target.transform.position, camSpeed * Time.fixedDeltaTime);
                transform.position = new Vector3 (pos.x, pos.y, transform.position.z);
          }
    }


    d. Create an Empty GameObject for the MainCamera target:
    In the Inspector name it "CameraManager".
    Be sure to Reset Transforms.
    Select CameraMain in the Hierarchy. Re-enable in Inspector. Quick-drag CameraManager into "target" slot.
     
    e. Create a new C# script "CameraManager_SplitScreens.cs" with the following code.
    Apply this script to the CameraManager.
    In the Inspector, quick-drag objects to fill the five script slots: all three cameras and both Players.

    CameraManager_SplitScreens.cs script: (with switch-flipping condition)
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class CameraManager_SplitScreen : MonoBehaviour{

          public GameObject cameraMain;
          public GameObject cameraPlayer1;
          public GameObject cameraPlayer2;
          public Transform player1;
          public Transform player2;
          public float cameraDistance = 5f;

          void Start(){
                cameraMain.SetActive(true);
                cameraPlayer1.SetActive(true);
                cameraPlayer2.SetActive(true);
          }

          void Update(){
                FindPlayerCenter(); //the GameHandler acts as the target for the MainCamera

                float playerDistance = Vector3.Distance(player1.position, player2.position); //get distance
                //if distance is within threshold, use fullscreen MainCamera
                if (playerDistance < cameraDistance){
                      cameraPlayer1.GetComponent<Camera>().enabled = false;
                      cameraPlayer2.GetComponent<Camera>().enabled = false;
                      cameraMain.GetComponent<Camera>().enabled = true;
                }
                //else, use splitscreen
                else {
                      cameraPlayer1.GetComponent<Camera>().enabled = true;
                      cameraPlayer2.GetComponent<Camera>().enabled = true;
                      cameraMain.GetComponent<Camera>().enabled = false;
                }
          }

          // set this object's position to the center of the two players
          void FindPlayerCenter(){
                Vector3 pos;
                pos.x = player1.position.x + (player2.position.x - player1.position.x) / 2;
                pos.y = player1.position.y + (player2.position.y - player1.position.y) / 2;
                pos.z = player1.position.z + (player2.position.z - player1.position.z) / 2;
                transform.position = new Vector3(pos.x, pos.y, 0);
          }
    }


    NOTE: We disable the Camera Component instead of just using SetActive(false) on camera GameObjects so that other components stay active, like the MainCamera Audio Listener.


    Hit Play to test Camera follow and to test the screens switching to a single full screen when players move close enough, and split back into two views when they move apart. Turn off Play to continue editing.


    CHALLENGE: What adjustments are needed for the right-split camera to always follow the player on the right, and the left-split Camera to follow the player on the left, regardless of which character it is?



      LOCAL MULTIPLAYER PART Part 3. Make the Player System into a Prefab:
    SUMMARY: To easily add all of this work to multiple Scenes in your game, save it as a single Prefab!

    a. Create a new GameObject > Empty GameObject.
    In the Inspector name it "MultiPlayerSystem". Make sure to Reset Transforms (this is critical!).

    b. Drag all three Cameras, the CameraManager, and both Players onto MultiPlayerSystem to make all six objects into children of MultiPlayerSystem.

    c. Drag the MultiPlayerSystem object down into the Project panel > Assets folder, into your Prefabs folder.
    The object should now be blue in both the Hierarchy and the Project panel, indicating it is a Prefab.
    From this point on, any critical changes to this Player and Camera system should be made in the Prefab, not in the Scene, to be sure those changes appear in all Scenes.



         


      NETWORKED MULTIPLAYER:
    SUMMARY: In 2022 Unity released a new 2-4 player networked game system: Netcode for Game Objects

    UNITY NETCODE:

    Start: Netcode documentation and tutorial: "Hello World"
    Then explore the Netcode Learning Hub
    See this example Unity 2D project: "Galactic Kittens"

    WATCH these Code Monkey Videos:
    Galactic Kittens video breakdown (40 min)
    Try this single 1-hour tutorial for move matching
    Watch this FREE, full tutorial course on Unity Network Games (2023, about 6 hours).




     
    [1K]: READ and WRITE EXTERNAL FILES
    BASIC reading JSON files   |   Read a JSON file from a WEB URL
    Intro to JSON files   |   Read from a JSON file   |   Write to a JSON file


      EXAMPLE 1. Reading a JSON file in the project (source):
    SUMMARY:
    JSON (JavaScript Object Notation) is a data interchange language for Object, Array, and Values.
    The file contains name / value pairs.
    The basic Unity JSON package can handle most uses, but for more complicated uses of JSON consider importing the free "JSON .NET for Unity" package from the asset store.

    STEP 1: In your Unity Project panel, make a JSON text file:
    RightClick the Assets folder.
    Choose "Show in Explorer" to see the computer folder structure where your Unity project lives.
    RightClick in the Assets folder to create a Text Document file, name it "JSONtext".

    Open in your script editor, add the following, Save, close the Exploerer window, and return to Unity:

    JSONtext.txt script:
    {
          "player": [
              {
                   "name" : "Grunt",
                   "health" : 100,
                   "mana" : 20
              },
              {
                   "name" : "Wizard",
                   "health" : 40,
                   "mana" : 80
              },
              {
                   "name" : "Cleric",
                   "health" : 80,
                   "mana" : 50
              }
         ]

    }


    NOTE: "player" is the class name. Multiple classes can be created for information to be listed / updated.
    Also note the colon after the class name and between each name / value pair, and the comma after each item pair (except the last) and between each entry (outside the curly braces). Missing any of these will cause an error.


    STEP 2: In your Unity Project panel, make a new C# file to communicate with the JSON file:
    RightClick the Unity Editor Assets folder, create > C# script, name it "JSONreader.cs".

    DoubleClick to open in your script editor, add the following, Save, and return to Unity:

    JSONreader.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class JSONreader : MonoBehaviour{

          public TextAsset textJSON;

          [System.Serializable]
          public class Player {
                      public string name;
                      public int health;
                      public int mana;
                // these variables must be identical to the items in the JSONtext file
          }

          [System.Serializable]
          public class PlayerList {
                      public Player[] player;
                // this variable must be identical to the class name in the JSONtext file
          }

          public PlayerList myPlayerList = new PlayerList();

          // Write the external file to the Assets folder (need a different location to work in Build)
          public void Start(){
               myPlayerList = JsonUtility.FromJson<PlayerList>(textJSON.text);
          }
    }


     

    STEP 3: Test in the Unity Editor:
    Add JSONreader to a Scene object.
    Drag the JSONtext from the Project panel to the JSONreader textJSON script slot in the Inspector.
    Note the empty array in the Inspector.
    Hit Play, and see the full array as the JSON file is read into the object script. Turn off Play.


         

    To further test in a webGL build, try this:
    Create a new Empty Game Object. In the Inspector name it "playerObj", reset transforms.
    In the Hierarchy, RightClick playerObj to create a 2D > Sprite child (just the make the playerObj visible).
    RightClick playerObj to create a Canvas child. In the Inspector name it "playerCanvas", and set the following: Render Mode = World Space, PosY = 1.1, Width = 2, Height = 1.4.
    RightClick playerCanvas to create a UI > Legacy > Text. In Inspector name it "Text1", and set the following: Width = 270, Height = 60, and Scale X and Y = 0.007. Type temp text into ther Text Field. Set Font Size = 35.
    Duplicate twice, spread the three Text objects vertically, name them Text1, Text2, Text3.
    Create a new C# script "playerScript.cs". Add this script to playerObj, to manage the population of these text fields. Add the content below, save and return to Unity. Drag the 3 Text objects into the 3 script slots.
    Make a Prefab: drag playerObj into the Project panel, then delete it from the Hierarchy.

    playerScript.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.UI;

    public class playerScript : MonoBehaviour{

          public Text textName;
          public Text textHealth;
          public Text textMana;

          public void SetText(string name, int health, int mana){
                Text textNameB = textName.GetComponent<Text>();
                textNameB.text = "NAME: " + name;

                Text textHealthB = textHealth.GetComponent<Text>();
                textHealthB.text = "Health: " + health;

                Text textManaB = textMana.GetComponent<Text>();
                textManaB.text = "Mana: " + mana;
          }
    }


     
    Next, revise the JSONreader.cs script to create these player objects and populate their Text fields:
    Add the content below.
    Drag the playerObj Prefab into the new script slot.

    add to JSONreader.cs script (new content in bold face):
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class JSONreader : MonoBehaviour{

          public TextAsset textJSON;

          [System.Serializable]
          public class Player {
                      public string name;
                      public int health;
                      public int mana;
                // these variables must be identical to the items in the JSONtext file
          }

          [System.Serializable]
          public class PlayerList {
                      public Player[] player;
                // this variable must be identical to the class name in the JSONtext file
          }

          public PlayerList myPlayerList = new PlayerList();

          // Added for webGL test #1
          public GameObject playerPrefab;

          // Write the external file to the Assets folder (need a different location to work in Build)
          void Start(){
               myPlayerList = JsonUtility.FromJson<PlayerList>(textJSON.text);

               // Added for webGL test #2
               myPlayerPopulator();
          }


          // Added for webGL test #3.
          // Note the Class Player in the array of JSON class player (lowercase).
          public void myPlayerPopulator(){
                foreach (Player thisPlayer in myPlayerList.player){
                      float newX = Random.Range(-2,2);
                      float newY = Random.Range(-3,3);
                      Vector3 newLoc = new Vector3(newX, newY, 0);
                      GameObject newPlayer = Instantiate (playerPrefab, (transform.position + newLoc), Quaternion.identity);
                      newPlayer.GetComponent<playerScript>().SetText(thisPlayer.name, thisPlayer.health, thisPlayer.mana);
                }
          }


    }


     
    Create a webGL build to see the characters populate with the individual stats.


         

      EXAMPLE 2. Reading a JSON file from a web URL (source):
    SUMMARY:
    If the JSON file is outside of the build, it can be upadted without having to re-export the game.
    Unity can use a web URL to populate the values of a JSON file.















         


      PART 1. Understanding JSON files (sources: 01   |   02):
    SUMMARY:
    JavaScript Object Notation: Object, Array, and Values.






      PART 2. Reading from a JSON file (source):
    SUMMARY: Access the data from a JSON text file.







      PART 3. Writing to a JSON file (source):
    SUMMARY: Generate a JSON text file. Must add the namespace: using System.IO
    NOTE: Application.dataPath goes to the Assets folder holding this script. This will only work for testing in the Editor. To work in a build, try directing to the documents folder.

    STEPS:
    1. Create a Canvas UI > Button and an Empty GameObject named "PlayerObj".

    2. Create a new C# script, name it "JSONWriter.cs". Add the content below, apply to PlayerObj.
    Fill out the script slots in the inspector.

    3. Apply to the Button: Select the Button. At the bottom of the Inspector add an OnClick Function.
    Drag PlayerObj into the "None" slot, and set the button to the outputJSON() function.

    Hit [Play], and click the button to see the .txt file get created in the Assets folder (may need to hit refresh).

    JSONWriter.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using System.IO;

    public class JSONWriter : MonoBehaviour{

          [System.Serializable]
          public class Player{
                      public string name;
                      public int health;
                      public int energy;
          }

          public Player myPlayer = new Player();

          // Write the external file to the Assets folder (need a different location to work in Build)
          public void outputJSON(){
                string strOutput = JsonUtlity.ToJson(myPlayer);
                File.WriteAllText(Application.dataPath + "/text.txt", strOutput);
          }
    }


     

         

     
    [2A]: PICKUPS
    [A]. BASIC PICKUP:
       1. PickUp Objects
       2. Up-Down Movement or Color Pulse
       3. Player Boosts
       4. Randomization (Top Down Games)

       5. Keys / Locked Doors (level interior)
        [B]. INVENTORY SYSTEM:
       1. Canvas Set-Up
       2. Game Inventory Script
       3. Crafting or Cooking
       4. Change Player Abilities
       5. Inventory PickUp


    [A]. BASIC PICK-UP OBJECTS FOR STAT CHANGES / BOOSTS:
    SUMMARY: This is a multi-purpose Pick-Up script that can be used for health or speed power-up boosts, etc.

     1. PICK-UP OBJECTS
    a. Create an Empty Game Object (RightClick the Hierarchy or open the GameObject menu).
    In the Inspector, set name to "PickUpBoost" and reset Transforms.
    Add Component BoxCollider2D, set to isTrigger

    b. Create art for a pickup, like a key or gem (32 x 32 if pixel art).
    Import into the Project, set Inspector settings as needed:
          If it is pixel art, set Pixels Per Unit = 16, Filter Mode = Point (no filter).
          If it is a spritesheet, set Sprite Mode = Multiple, hit [Apply], slice up in the [Sprite Editor].

    c. Drag the art into the Hierarchy, drag onto the PickUpBoost object to make it a child.
    In the Inpector name it "pickup_art" and set Order in Layer = 90.

    d. If the art is a spritesheet meant to be animated (like a turning coin or a flashing gem):
          Select the Hiearchy art file, open Window > Animation > Animation.
          Hit [create] and name it objectname_anim.
          Drag the frames into the timeline, then drag the first frame in again to place at the end, for a loop.
          Stretch the timing to 30-60 frames.

    e. Add a soundFX file to the Project (WAV is best for short clips, MP3 is also an option).
          Drag the audio file onto the PickUpBoost object in the Hierarchy to add an Audio Source component.
          In the Inspector, turn off "Play on Awake".

    f. Create a C# script called "PickUp.cs", add content below. Save, and in Unity drag onto PickUpBoost.

    PickUp.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PickUp : MonoBehaviour{

          public GameHandler gameHandler;
          //public playerVFX playerPowerupVFX;
          public bool isHealthPickUp = true;
          public bool isSpeedBoostPickUp = false;

          public int healthBoost = 50;
          public float speedBoost = 2f;
          public float speedTime = 2f;

          void Start(){
                gameHandler = GameObject.FindWithTag("GameHandler").GetComponent<GameHandler>();
                //playerPowerupVFX = GameObject.FindWithTag("Player").GetComponent<playerVFX>();
          }

          public void OnTriggerEnter2D (Collider2D other){
                if (other.gameObject.tag == "Player"){
                      GetComponent<Collider2D>().enabled = false;
                      //GetComponent< AudioSource>().Play();
                      StartCoroutine(DestroyThis());

                      if (isHealthPickUp == true) {
                            gameHandler.playerGetHit(healthBoost * -1);
                            //playerPowerupVFX.powerup();
                      }

                      if (isSpeedBoostPickUp == true) {
                            other.gameObject.GetComponent<PlayerMove>().speedBoost(speedBoost, speedTime);
                            //playerPowerupVFX.powerup();
                      }
                }
          }

          IEnumerator DestroyThis(){
                yield return new WaitForSeconds(0.3f);
                Destroy(gameObject);
          }

    }

    To add the playerPowerupVFX, please see the GameFeel final section of this Tutorial, and then un-comment the playerPowerupVFX lines of this script!



     2a. PICKUP MOVEMENT:
    Optionally Create a new C# script named "PickUp_UpDown.cs" with the following content.
    Add to the PickUpBoost object to get up-and-down movement.


    PickUp_UpDown.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PickUp_UpDown : MonoBehaviour{

          //initial values, can be made public
          private Vector3 startPos;

          public float speedS = 2.5f;
          public float distS = 0.5f;

          //extra parameters for randomizing movement
          private float randBottom = 0.6f;
          private float randTop = 1f;

          private bool SinWaveMove = true;
          private bool randomizeSin = true;

          void Start(){
                startPos = transform.position;

                //randomize distance
                if (randomizeSin==true){
                      speedS = (speedS * Random.Range(randBottom, randTop));
                }
          }

          void Update (){
                //use Mathf.Sin function to move up and down
                if (SinWaveMove == true){
                      transform.position = startPos + new Vector3(0.0f, (Mathf.Sin(Time.time * speedS) * distS), 0.0f);
                }
          }
    }




      2b. PICKUP COLOR PULSE:
    Optionally Create a new C# script named "PickUp_Pulse.cs" with the following content.
    Add this script to the PickUpBoost object created above.
    Duplicate pickup_art, name it "pickup_art_highlight".
    Set Order in Layer = 92 (slightly higher than pickup_art).
    Drag the new art into the fadeHighlight script slot.

    PickUp_Pulse.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PickUp_Pulse : MonoBehaviour{

          public GameObject fadeHighlight;
          private float pulseHold = 0.4f;
          private float pulseDelay = 2f;
          private float pulseSpeed = 0.02f;
          private Renderer fadeHighlightRend;
          private bool timeToFadeOut = false;
          private bool timeToFadeIn = false;
          private float fadeAlpha = 0f;

          void Awake(){
                fadeHighlightRend = fadeHighlight.GetComponent<SpriteRenderer> ();
                fadeHighlightRend.material.color = new Color(2.5f, 2.2f, 0.3f, 0f);
          }

          void Start(){
                StartCoroutine(RandomDelay());
          }

          void FixedUpdate(){
                if (timeToFadeIn){
                      fadeAlpha += pulseSpeed;
                      fadeHighlightRend.material.color = new Color(2.5f, 2.2f, 0.3f, fadeAlpha);
                      if (fadeAlpha >= 0.5f){
                           fadeAlpha = 0.5f;
                            StartCoroutine(PulseFull());
                      }
                }
               else if (timeToFadeOut){
                      fadeAlpha -= pulseSpeed;
                      fadeHighlightRend.material.color = new Color(2.5f, 2.2f, 0.3f, fadeAlpha);
                      if (fadeAlpha <= 0f){
                            fadeAlpha = 0f;
                            StartCoroutine(PulsePause());
                      }
                }
          }

          //delay start of pulsing, so neighboring pickups do not pulse the same
          IEnumerator RandomDelay(){
                float randDelay = Random.Range(0.1f, 2.0f);
                yield return new WaitForSeconds(randDelay);
                timeToFadeIn = true;
          }

          //stay at full strength before fading away
          IEnumerator PulseFull(){
                yield return new WaitForSeconds(pulseHold);
                timeToFadeIn = false;
                timeToFadeOut = true;
          }

          //pause before next pulse
          IEnumerator PulsePause(){
                yield return new WaitForSeconds(pulseDelay);
                timeToFadeOut = false;
                timeToFadeIn = true;
          }

    }





      3. POWER BOOSTS ON PLAYER:
    Power Boosts are simply a change to player stats based on the acquisition or the use of a powerup.

    ACTIVATE BOOST ON PICK-UP TOUCH:
    The best place to put the code for a power-up that activates as soon as a PickUp is touched is on the PickUp.cs itself (see "isSpeedBoostPickUp" option, above), which would then activate the Player stat change in the PlayerMove.cs script.

    optionally add to PlayerMove.cs script (if you do not, comment out the line in PickUp):
          public void speedBoost(float speedBoost, float speedLength){
                runSpeed = runSpeed * speedBoost;
                StartCoroutine(normalSpeed(speedLength));
          }

          IEnumerator normalSpeed(float speedLength){
                yield return new WaitForSeconds(speedLength);
                runSpeed = startSpeed;       //NOTE: returns this stat to normal
          }



    ACTIVATE BOOST ON LATER USE:
    The best place to put the code for an acquired power-up to be used when the player chooses is usually on the GameHandler.cs, which would then spend the item in thw inventory (see inventory notes, above) and activate the Player stat change in the PlayerMove.cs script, above.
    optionally add to GameHandler.cs script in the Update function:
          void Update(){


                if (Input.GetKeyDown(KeyCode.P)){
                      if (gameObject.GetComponent<GameInventory>().item1bool == true){
                            gameObject.GetComponent<GameInventory>().InventoryRemove(item1);
                            player.speedBoost(2f, 5f);
                      }
                }

          }



      STEP 4. PICKUP RANDOMIZATION (for top-down games):
    Want pickups in a top-down level to be placed with some variety each time the level is played?
    There are multiple ways to do this. Here is one:

    RANDOMNESS IN A RECTANGLE:
    a. Create a new C# script called "Pickup_Random.cs". Add the content below and save.

    b. Add this script to a pickup object (see above).

    c. Note the rectangle that appears surrounding the pickup.
    In the Inspector this rectangle is defined by public "width" and "height" parameters that you can change.

    When you hit [Play], the pickup will be positioned at a random spot within that rectangle.

    Be sure that the area of the rectangle does not include any colliders that would prevent the player from touching the pickup, if it landed in that spot.


    Pickup_Random.cs:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class Pickup_Random : MonoBehaviour  {

          //define rectangle for random positon
          public float width = 1;
          public float height = 2;

          private Vector2 rect1;
          private Vector2 rect2;

          void Start(){
                Vector3 pos = transform.position;
                rect1 = new Vector2(pos.x - width/2, pos.y + height/2);
                rect2 = new Vector2(pos.x + width/2, pos.y - height/2);
                RandomPos();
          }

          void Update(){
                //test position change
                if (Input.GetKeyDown("p")){
                      RandomPos();
                }
          }

          public void RandomPos(){
                float xRand = Random.Range(rect1.x, rect2.x);
                float yRand = Random.Range(rect1.y, rect2.y);

                transform.position = new Vector2(xRand, yRand);
          }

          void OnDrawGizmos(){
                Gizmos.DrawWireCube(transform.position, new Vector3(width,height,0));
          }
    }



      KEYS (for locked doors inside a level. Depends on the inventory scripts, below):
    Want a key pickup that will open a door inside of your level, to gain access to another part of the same level?:

    a. Add to the PickUp.cs script with the following, to add the Key to the inventory (need inventory scripts).

    // #1. add this isKey bool to the top Class variables
          public bool isKey = false;

    // #2. add this if-option to the OnTriggerEnter2D() function
                      if (isKey == true) {
                            //Add the key to the inventory. Touch a door to remove a key and open the door.
                            gameHandler.GetComponent<GameInventory>().InventoryAdd("item1");
                      }

    b. Create a Locked Door object:
      In the Hierarchy create an Empty Game Object.
      In the Inspector name it "LockedDoor" and reset Transforms.
      [Add Component] Physics2D > BoxCollider2D.
          Turn on isTrigger.
          Set collider size larger than the door art.
      Add three sprites as children: "doorLocked_art", "doorOpened_art", and "msg_needKey" (text on background to say "Need a key") when the player does not have one.
      Select doorLocked_art and [Add Component] Physics2D > BoxCollider2D.
          Set collider size wider than the door art, to be sure to block movement through it.

    c. Create a new C# script named "LockedDoor.cs".
      Add the following content, save, go back to Unity.
      Add the script to the LockedDoor parent object.
      Drag the three sprite childen into the GameObject script slots.

    LockedDoor.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class LockedDoor : MonoBehaviour{

          public GameHandler gameHandler;
          public GameObject doorLocked;
          public GameObject doorOpened;
          public GameObject msgNeedKey;
          private bool isLocked = true;

          void Start(){
                gameHandler = GameObject.FindWithTag("GameHandler").GetComponent<GameHandler>();
                doorLocked.SetActive(true);
                doorOpened.SetActive(false);
                msgNeedKey.SetActive(false);
          }

          public void OnTriggerEnter2D (Collider2D other){
                if ((other.gameObject.tag == "Player")&& (isLocked==true)){
                      if (GameInventory.item1num > 0) {
                            doorLocked.SetActive(false);
                            doorOpened.SetActive(true);
                            isLocked = false;
                            gameHandler.GetComponent<GameInventory>().InventoryRemove("item1", 1);
                      }
                      else {
                            msgNeedKey.SetActive(true);
                            Debug.Log("You need a key to enter.");
                      }
                }
          }

          public void OnTriggerExit2D (Collider2D other){
                if(other.gameObject.tag == "Player"){
                     msgNeedKey.SetActive(false);
                }
          }

    }
     

         


     [B]. PERSISTENT INVENTORY SYSTEM OF PICK-UP OBJECTS:

    SAMPLE IMAGES:
    This tutorial uses PNG Sprites for 5 unique pickup items, 1 container, and 1 player.
    Click here to download a zip of all 7 images.
       
     
     

     1. CANVAS INVENTORY SET UP:
    a. If you do not already have a GameObject > UI > Canvas object in your Hierarchy, create one.
      In Inspector, set Canvas Scaler > UI Scale Mode from "Constant Pixel Size" to "Scale With Screen Size" and Reference Resolution to 1280 x 720.

    b. Add a GameObject > UI > Image.
      Name it "Inventory". Size into a short, long rectangle (try 900 x100). Position near Canvas bottom.
      In Inspector set Opacity to medium (try 150) and choose a color.

    c. RightClick Inventory to add another UI > Image.
      Name it "InvItem". Size and position as an Item-slot in the Inventory (try 90 x 90).

    d. RightClick InvItem to add a third UI > Image.
      Name it "InvImage" (for the icon). Size smaller (try 80 x 80).

    e. RightClick InvItem to add a fourth UI > Image.
      Name it "InvNum".
      Set image to default Knob. Size small (try 40 x 40).
      Position at lower right corner of InvItem (extend half outside).

    f. RightClick InvNum to add a GameObject > UI > Legacy > Text (to track the number of the item).
      Size 30 x 30, bold. Set text field = "?".

    (NOTE: Canvas item visibility is reverse of Hierarchy order: lower items in Hierarchy display in Scene in front).



     2. GAME INVENTORY SCRIPT:
    If you do not already have a GameHandler object: create a GameObject > Empty GO, name it "GameHandler".
    In Inspector create the tag "GameHandler" and add it to the GameHandler object.
    Drag this Object into the Project panel to make a Prefab.

    For clarity, this script is separate from the GameHandler script that manages other player / game stats.
      Create a new C# script named "GameInventory.cs".
      Add the content below, save, and in Unity drag the script onto the GameHandler Prefab.

    GameInventory.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.UI;

    public class GameInventory : MonoBehaviour {
          public GameObject InventoryMenu;
          //public GameObject CraftMenu;
          public bool InvIsOpen = false;

          //5 Inventory Items:
          public static bool item1bool = false;
          public static bool item2bool = false;
          public static bool item3bool = false;
          public static bool item4bool = false;
          public static bool item5bool = false;

          public static int item1num = 0;
          public static int item2num = 0;
          public static int item3num = 0;
          public static int item4num = 0;
          public static int item5num = 0;
          //public static int coins = 0;

          [Header("Add item image objects here")]
          public GameObject item1image;
          public GameObject item2image;
          public GameObject item3image;
          public GameObject item4image;
          public GameObject item5image;
          //public GameObject coinText;

          // Item number text variables. Comment out if each item is unique (1/2).
          [Header("Add item number Text objects here")]
          public Text item1Text;
          public Text item2Text;
          public Text item3Text;
          public Text item4Text;
          public Text item5Text;

          // Crafting buttons. Uncomment and add for each button:
          // public GameObject buttonCraft1; // weapon1 creation
     
          void Start(){
                InventoryMenu.SetActive(false);
                //CraftMenu.SetActive(false);
                InventoryDisplay();
          }

          void InventoryDisplay(){
                if (item1bool == true) {item1image.SetActive(true);} else {item1image.SetActive(false);}
                if (item2bool == true) {item2image.SetActive(true);} else {item2image.SetActive(false);}
                if (item3bool == true) {item3image.SetActive(true);} else {item3image.SetActive(false);}
                if (item4bool == true) {item4image.SetActive(true);} else {item4image.SetActive(false);}
                if (item5bool == true) {item5image.SetActive(true);} else {item5image.SetActive(false);}

                //Text coinTextB = coinText.GetComponent<Text>();
                //coinTextB.text = ("COINS: " + coins);

                // Item number updates. Comment out if each item is unique (2/2).
                Text item1TextB = item1Text.GetComponent<Text>();
                item1TextB.text = ("" + item1num);

                Text item2TextB = item2Text.GetComponent<Text>();
                item2TextB.text = ("" + item2num);

                Text item3TextB = item3Text.GetComponent<Text>();
                item3TextB.text = ("" + item3num);

                Text item4TextB = item4Text.GetComponent<Text>();
                item4TextB.text = ("" + item4num);

                Text item5TextB = item5Text.GetComponent<Text>();
                item5TextB.text = ("" + item5num);
          }

          public void InventoryAdd(string item){
                string foundItemName = item;
                if (foundItemName == "item1") {item1bool = true; item1num ++;}
                else if (foundItemName == "item2") {item2bool = true; item2num ++;}
                else if (foundItemName == "item3") {item3bool = true; item3num ++;}
                else if (foundItemName == "item4") {item4bool = true; item4num ++;}
                else if (foundItemName == "item5") {item5bool = true; item5num ++;}
                else { Debug.Log("This item does not exist to be added"); }
                InventoryDisplay();

                if (!InvIsOpen){
                      OpenCloseInventory();
                }
          }

          public void InventoryRemove(string item, int num){
                string itemRemove = item;
                if (itemRemove == "item1") {
                      item1num -= num;
                      if (item1num <= 0) { item1bool =false; }
                      // Add any other intended effects: new item crafted, speed boost, slow time, etc
                 }
                else if (itemRemove == "item2") {
                      item2num -= num;
                      if (item2num <= 0) { item2bool =false; }
                      // Add any other intended effects
                 }
                else if (itemRemove == "item3") {
                      item3num -= num;
                      if (item3num <= 0) { item3bool =false; }
                        // Add any other intended effects
                }
                else if (itemRemove == "item4") {
                      item4num -= num;
                      if (item4num <= 0) { item4bool =false; }
                        // Add any other intended effects
                }
                else if (itemRemove == "item5") {
                      item5num -= num;
                      if (item5num <= 0) { item5bool =false; }
                        // Add any other intended effects
                }
                else { Debug.Log("This item does not exist to be removed"); }
                InventoryDisplay();
          }

          //public void CoinChange(int amount){
                //coins +=amount;
                //InventoryDisplay();
          //}


          // Open and Close the Inventory. Use this function on a button next to the inventory bar.
          public void OpenCloseInventory(){
                if (InvIsOpen){ InventoryMenu.SetActive(false); }
                else { InventoryMenu.SetActive(true); }
                InvIsOpen = !InvIsOpen;
          }

          //Open and Close the Cookbook
          //public void OpenCraftBook(){CraftMenu.SetActive(true);}
          //public void CloseCraftBook(){CraftMenu.SetActive(false);}

          // Reset all static inventory values on game restart.
          public void ResetAllInventory(){
                item1bool = false;
                item2bool = false;
                item3bool = false;
                item4bool = false;
                item5bool = false;

                item1num = 0; // object name
                item2num = 0; // object name
                item3num = 0; // object name
                item4num = 0; // object name
                item5num = 0; // object name
          }

    }

    GameInventory.cs Explanation:
    This hard-coded Inventory system requires at least two variables for each item (a more elegant solution could store all items in an array):
         
    A bool variable for each inventory item, to set the state (whether they are collected or not: on or off)
          A GameObject variable for each inventory image, to make them invisible/visible.


    To support a game that could have multiple copies of an item to be picked up, like for a crafting or cooking system, we have two more variables per item:

          An int variable for each inventory item, for the current number of that item collected and un-spent
          A Text variable to display the current number.

    In the Start() function note the InventoryMenu is set visible and all inventory items are set not visible.
    We also run the InventoryDisplay() function to set the Inventory Image Items to not visible.

    In addition to the InventoryDisplay() function, we have:
          An InventoryAdd() function to call by the PickUp_Inventory.cs script (see below)
          An
    InventoryRemove() function to call if an item is "spent" (by a button or a key press)
          A CoinChange() function to add coins when found or subtract coins for a purchase
    Any action to spend an item or coins should check if their bool is true or amount sufficient for purchase.



      3. CRAFTING or COOKING using the GameInventory:
    To use the InventoryRemove() function for CRAFTING or COOKING functionality, add the function to a new Button that uses the item/s.

    Manage visibility in the GameInventory.cs:
          An inventory "Craft" Button should only be visible if the item exists in the inventory.
          When all of that item are spent the Button should be SetActive(false);

    For each craft-button:
    1. Add a public GameObject variable to the top of GameInventory.cs script:
          // Crafting buttons:
          public GameObject buttonCraft1; // weapon1 creation

    2. Add a line to the Start() function:
          void Start(){
                buttonCraft1.SetActive(false);

    3. Add a condition to InventoryDisplay() function, to test for sufficient ingredients to display the craft button:
          if ((item1num >= 2) && (item4num >= 1)){       // sample inventory items to be used
                buttonCraft1.SetActive(true);
          }
          else { buttonCraft1.SetActive(false); }
     
    4. Add a new CraftObject#() function to the bottom of GameInventory.cs script, for the Button to access:
          public void CraftObject1(){
                InventoryAdd("weapon1"); // sample inventory item to be added, needs supporting UI images
                InventoryRemove("item1", 2); InventoryRemove("item4",1); // sample inventory items to be removed
          }
     
    Craft Button activation steps:
       a) Select the Button. In the Inspector bottom, add an OnClick event.
       b) Drag the GameHandler into the OnClick "None" slot.
       c) Set the function to GameInventory > CraftObject#().
       d) Select the GameHandler. In the Inspector, find the GameInventory > buttonCraft# script slot.
            Drag the Button object from the Hierarchy to the Inspector, into that buttonCraft# script slot.

    See this example GameInventory.cs script for a game with cooking (crafting with food!)



      4. USE AN INVENTORY TO CHANGE PLAYER ABILITIES:

    a. Health Potions:
    Want your player to use a health potion in the Inventory?
    In a new AddHealth() function in GameHandler.cs: Wrap the action of increasing player heath in a condition to check if the potion exists in the Inventory:
       if (GameInventory.item#bool == true){ }
    Then, after using:
       GameObject.FindWithTag("GameHandler").GetComponent<GameInventory>().InventoryRemove("item#",1);

    b. Keys:
    Want your player to need a key to enter a door?
    Add an Empty Game Object to the Scene doorway with a BoxCollider to prevent player passage.
    On a player action (like a specific keypress), check if there is a key.
    If there is a key, turn off the collider and optionally remove the key (InventoryRemove).



     5. CREATE A NEW PICKUP OBJECT FOR INVENTORY ITEMS:

    a. Make a copy of the PickUpBoost object created above: Select it in the Hierarchy and hit [Ctrl/Cmd]+[d].
    In the Inspector name this object "PickupInventory" and in the PickUp.cs component set all boosts to "false".

    b. Create a new C# script "PickUp_Inventory.cs".
      Add the content below.
      Save, and in Unity add the script to the PickUpInventory object
         See the reference in the OnTriggerEnter2D() function to the GameInventory.cs script.
        (NOTE: This script is meant to be added in ADDITION to the PickUp.cs script).

    PickUp_Inventory.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PickUp_Inventory: MonoBehaviour {

          private GameInventory gameInventory;
          public string ItemName = "item1";

          void Awake(){
                if (GameObject.FindWithTag("GameHandler") != null) {
                      gameInventory = GameObject.FindWithTag("GameHandler").GetComponent<GameInventory>();
                }
          }

          void OnTriggerEnter2D(Collider2D other){
                if (other.gameObject.tag == "Player"){
                      //Debug.Log("You found an" + ItemName);
                      gameInventory.InventoryAdd(ItemName);
                }
          }
    }

    //NOTE: This script is meant to co-exist on the pickup object with PickUp.cs,
    // which handles SFX and destruction (turn off boosts)



    c. Once the script is applied, add to the Inspector script slot the desired "Item Name".
    The GameInventory.cs script above assumes these names (you can make them more specfic to your game):
    "item1", "item2", "item3", "item4", "item 5"

    d. Change the image to the PNG art for this pick-up-able object.
    Select the child Image to find the Source Image slot in the Inspector.
    Drag in the art from the Project panel.

    e. 
    Drag this PickupInventory object into the Project panel to create a Prefab.
    Change the item name and image for other pickup-able objects.




     
    [2B]: PLAYER RESPAWN AND CHECKPOINTS
    1. Player Respawn  |   2. Checkpoints  |   3. Limited Lives  |   4. Exploration Game: Map Return to Door

     PLAYER RESPAWN SYSTEM
  • Here is a way for the player to respawn back at the start if they fall off a platform (but still cost them damage):
  • Create two Empty Game Objects: PlayerSpawnStart and PlayerBottomRespawn.
    Put PlayerSpawnStart at the desired player start location.
    Put PlayerBottomRespan below the visible game level content, and add the following script to it.
    Drag the PlayerStartSpawn object into the pSpawn script slot.
    Be sure Player and GameHandler have tags "Player" and "GameHandler":


    PlayerBottomRespawn.cs
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PlayerBottomRespawn : MonoBehaviour {

           public GameHandler gameHandler;
           public Transform playerPos;
           public Transform pSpawnFall;
           public int damage = 10;

           void Start() {
                  playerPos = GameObject.FindWithTag("Player").GetComponent<Transform>();
                  gameHandler = GameObject.FindWithTag("GameHandler").GetComponent<GameHandler>();
           }

           void Update() {
                  if (playerPos != null){
                         //Capture Checkpoint changes:
                         pSpawnFall = playerPos.gameObject.GetComponent<PlayerRespawn>().pSpawn;

                         if (transform.position.y >= playerPos.position.y){
                                //instantiate a particle effect
                                Debug.Log("I am going back to the start");
                                gameHandler.playerGetHit(damage);
                                Vector3 pSpn2 = new Vector3(pSpawnFall.position.x, pSpawnFall.position.y, playerPos.position.z);
                                playerPos.position = pSpn2;
                         }
                  }
           }
    }


    NOTE: Player script PlayerRespawn is added below, and GameHandler script playerGetHit() is explained / applied in [1E].


      CHECKPOINTS: Adapt this system for Checkpoints (placed throughout a level, player can touch these objects to make each the respawn point if the player dies).
    a. Create a Game Object > Empty Game Object, name it "checkPoint". Reset Transforms.

    b. Add a BoxCollider2D Component, turn on isTrigger.

    c. Add a new Tag called "Checkpoint" and add the tag to the checkPoint object.

    d. Drag in desired art Sprite, name it "CheckPointArt".

    e. Add a new script PlayerRespawn.cs to the Player, and drag the starting point into pSpawn script slot:


    PlayerRespawn.cs
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PlayerRespawn : MonoBehaviour {

           public GameHandler gameHandler;
           public Transform pSpawn;       // current player spawn point

           void Start() {
                  gameHandler = GameObject.FindWithTag("GameHandler").GetComponent<GameHandler>();
           }

           void Update() {
                  if (pSpawn != null){
                         if (GameHandler.playerHealth <= 0){
                                //comment out lines from GameHandler about EndLose screen
                                Debug.Log("I am going back to the last spawn point");
                                Vector3 pSpn2 = new Vector3(pSpawn.position.x, pSpawn.position.y, transform.position.z);
                                gameObject.transform.position = pSpn2;
                         }
                  }
           }

          
    public void OnTriggerEnter2D(Collider2D other) {
                  if (other.gameObject.tag == "Checkpoint"){
                                pSpawn = other.gameObject.transform;
                                GameObject thisCheckpoint = other.gameObject;
                                StopCoroutine(changeColor(thisCheckpoint));
                                StartCoroutine(changeColor(thisCheckpoint));
                  }
           }

           IEnumerator changeColor(GameObject thisCheckpoint){
                  Renderer checkRend = thisCheckpoint.GetComponentInChildren<Renderer>();
                  checkRend.material.color = new Color(2.4f, 0.9f, 0.9f, 0.5f);
                  yield return new WaitForSeconds(0.5f);
                  checkRend.material.color = Color.white;
           }
    }


     LIMITED LIVES:
    SUMMARY: A respawn and checkpoint system typically go hand-in-hand with a limited lives system.

    a. In the Canvas create a display of the remaining Player lives.
    OPTION 1: A simple UI > Legacy > Text object on a UI > image background
    OPTION 2: a Zelda- style row of hearts (needs art assets for bright heart + dark heart slot when lives are lost).
    In the Canvas, each bright heart is set below its dark heart slot, to appear in front.

    b. For either option, add 2 new class variables to the Gamehandler.cs script (see script boxes below):
            public static int Lives;
            public int maxLives = 5;
    OPTION 1: Also a variable to hold the canvas Text element:
            public GameObject textLives;

    OPTION 2: Also variables to hold all the bright hearts:
            public GameObject lifeHeart1;
            public GameObject lifeHeart2;
            public GameObject lifeHeart3;
            public GameObject lifeHeart4;
            public GameObject lifeHeart5;

    Also add a public function UpdateLives() that is called by the playerGetHit() function if health goes <= 0 or if a life-pickup is collected.
    OPTION 1: UpdateLives() just updates the Lives variable and a text display
    OPTION 2: UpdateLives() updates the Lives variable and updates the SetActive status of one bright heart, checking gameObject.activeInHierarchy from last to first to lose a heart, or from first to last if gaining a heart.

    c. Populate all new script slots.

    ADDING OPTION 1: Variables and UpdateLives() function to the GameHandler.cs (Add Bold Content):
           public static int Lives = 5;
           public int maxLives = 5;
           public GameObject textLives;

           public void playerGetHit(int damage){
                  playerHealth -= damage;
                  UpdateHealth();
                  sceneName = SceneManager.GetActiveScene().name;
                  if (playerHealth >= MaxHealth){playerHealth = MaxHealth;}
                  if ((playerHealth <= 0) && (sceneName != "EndLose") && (Lives <= 0)){
                         SceneManager.LoadScene("EndLose");
                  } else if (playerHealth <= 0){ UpdateLives(-1); }
           }

           public void UpdateLives(int lifeChange, string changeDir){
                  Lives += lifeChange;
                  Text livesTextB = textLives.GetComponent<Text>();
                  livesTextB.text = "Lives: " + Lives + " / " + MaxLives;
           }

    ADDING OPTION 2: Variables and UpdateLives() function to the GameHandler.cs (Add Bold Content):
           public static int Lives = 5;
           public int maxLives = 5;
            public GameObject lifeHeart1;
            public GameObject lifeHeart2;
            public GameObject lifeHeart3;
            public GameObject lifeHeart4;
            public GameObject lifeHeart5;

           public void playerGetHit(int damage){
                  playerHealth -= damage;
                  UpdateHealth();
                  sceneName = SceneManager.GetActiveScene().name;
                  if (playerHealth >= MaxHealth){playerHealth = MaxHealth;}
                  if ((playerHealth <= 0) && (sceneName != "EndLose") && (Lives <= 0)){
                         SceneManager.LoadScene("EndLose");
                  } else if (playerHealth <= 0){ UpdateLives(-1, "down"); }
           }

           public void UpdateLives(int lifeChange, string changeDir){
                  Lives += lifeChange;
                  if (changeDir == "down"){
                         if (lifeHeart5.activeInHierarchy){lifeHeart5.SetActive(false);}
                         else if (lifeHeart4.activeInHierarchy){lifeHeart4.SetActive(false);}
                         else if (lifeHeart3.activeInHierarchy){lifeHeart3.SetActive(false);}
                         else if (lifeHeart2.activeInHierarchy){lifeHeart2.SetActive(false);}
                         else if (lifeHeart1.activeInHierarchy){lifeHeart1.SetActive(false);}
                  } else if (changeDir == "up"){
                         if (!lifeHeart2.activeInHierarchy){lifeHeart2.SetActive(true);}
                         else if (!lifeHeart3.activeInHierarchy){lifeHeart3.SetActive(true);}
                         else if (!lifeHeart4.activeInHierarchy){lifeHeart4.SetActive(true);}
                         else if (!lifeHeart5.activeInHierarchy){lifeHeart5.SetActive(true);}
                  }
           }

    e. Add a condition to PlayerRespawn.cs to only respawn if Lives > 0:

    ADD to PlayerRespawn.cs Update() Function (Add Bold Content)

           void Update() {
                  if (pSpawn != null){
                         if ((GameHandler.CurrentHealth <= 0) && (GameHandler.Lives > 0)){
                                //comment out lines from GameHandler abotu EndLose screen
                                Debug.Log("I am going back to the last spawn point");
                                Vector3 pSpn2 = new Vector3(pSpawn.position.x, pSpawn.position.y, transform.position.z);
                                gameObject.transform.position = pSpn2;
                         }
                  }
           }



    f. Optionally add to a PickUpHeart.cs script to a heart pickup, to increase player lives:

    PickUpHeart.cs:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PickUpHeart: MonoBehaviour {
          
    public GameHandler gameHandler;

           void Start() {
                  gameHandler = GameObject.FindWithTag("GameHandler").GetComponent<GameHandler>();
           }


           void OnTriggerEnter2D(Collider2D other) {
                  if ((other.gameObject.tag == "Player") && (GameHandler.Lives < gameHandler.maxLives)){
                       gameHandler.UpdateLivesLives (1, "up");
                      //playerPowerupVFX.powerup();
                      Destroy(gameObject);
                  }
           }
    }




     EXPLORATION: MAP RETURN TO DOOR:
    SUMMARY: In an exploration game there is typically one or more world-map and rooms or buildings you can enter. When you exit those rooms or buildings you want to return to the same place on the map where you entered, to maintain a sense of continuity of space.
    Just like in a checkpoint system, we need to capture a location value to use when the player needs to return to that location, BUT in an exploration game we need to preserve that information between Scenes, and so the values need to be static.

    a. Create a new C# script called GameHandler_PlayerReturn (see below).
    Add this script to the GameHandler prefab in the Prefabs folder.

    This script includes two static variables:
           static string lastMap (if there are multiple maps)
           static Vector2 lastDoorPosition.

    In the Start() function, check the current scene (requires the SceneManagement namespace) and compare it to the static string lastMap. If they match, set the character position to the static vector2.

    When we enter a door/building from a map, we just need a command that updates both static values.

    NOTE: if you need to preserve multiple locations (for example, if you are making a game in a building with hallways, and each hallway has rooms to enter off of it, and you can go back to previous hallways), then you will need a map string for each hallway, to rememebr where we last exited for that space.


    GameHandler_PlayerReturn.cs
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    using UnityEngine.SceneManagement;

    public class GameHandler_PlayerReturn : MonoBehaviour {

           public static string lastMap = "";
           public static Vector2 lastDoorPosition;        // where the Player entered a door
           private string thisLevel;
           private Transform player;

           void Start() {
                  thisLevel = SceneManager.GetActiveScene().name;
                  player = GameObject.FindWithTag("Player").GetComponent<Transform>();
                  if (thisLevel == lastMap){
                         player.position = lastDoorPosition;
                  }
           }
    }


    f. Here is a door script to remember where on the map it was located.
    IMPORTANT: This script should ONLY be active on doors in the map level (or the hallways levels, in a game that takes place on a multi-floor building).

    Note the offsetY value is intended to put the player far enough from the door that it does not re-collide with it and re-enter it:


    Door_MapReturn.cs
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.SceneManagement;

    public class Door_MapReturn : MonoBehaviour {

           public string NextLevel = "MainMenu";
           private string thisLevel;
           private Vector2 doorReturnPos;       // load with location for player when they return
           public float offsetX = 0f;        // distance to left or right of the door for player to spawn
           public float offsetY = -2f;        // distance above or below the door for player to spawn

          
           void Start() {
                  thisLevel = SceneManager.GetActiveScene().name;
                 
    doorReturnPos
    = new Vector2((transform.position.x + offsetX), (transform.position.y + offsetY));
           }

          
    public void OnTriggerEnter2D(Collider2D other) {
                  if (other.gameObject.tag == "Player"){
                                GameHandler_PlayerReturn.lastDoorPosition = doorReturnPos;
                                GameHandler_PlayerReturn.lastMap = thisLevel;
                                SceneManager.LoadScene (NextLevel);
                  }
           }
    }




  •  
    [2C]: ENVIRONMENT HAZARDS AND INTERACTIVE SPACES
    HAZARDS:
    1. Death Spikes
    2. Lava
    3. Moving Doom Wall


    STEALTH GAME TOOLS:
    1. Hide in a bush (WIP)


    PUZZLES:
    1. Moveable Block
    2. Block-able Laser

    3. Sliding Walls
    4. Switch-able Walls
    5. Breakable Walls (webs)

    6. Indicate Hidden Spaces
    7. SpriteMasks (light in dark)
    8. Enemy Zapper
    PLATFORMS:
    1. Moving Platforms
    2. Slippery/Sticky Platforms
    3. Bouncy Platforms
    4. Ladders
    INTERACTIVE TILEMAPS:
    1. Destructible Tilemaps
    2. Paintable Tilemaps
    3. Change Color (reveal, WIP)
    4. Dynamic World Generation

       
    HAZARDS: Spikes, Lava, Doom Wall
       

      HAZARD 1. DEATH SPIKES (Instant Damage):
    A classic of Platfomer design element is spikey death!

    This can be done two ways:

    OPTION 1. A duplicate-able Prefab of a spike (EmptyGame Object named "Spike" with box collider and sprite-art as child)

    OPTION 2. A Tilemap layer called "Tilemap_spikes" that contains nothing but spikes, and has the Tilemap Collider 2D Component applied.

    SCRIPT: In either option, drag the following script DamageInstant.cs onto the Spike object or Tilemap layer, to make it cause damage to the Player:
    DamageInstant.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class DamageInstant : MonoBehaviour {

           public GameHandler gameHandlerObj;
           public int damage = 1;
           //public Transform backToStart; //uncomment this line for "auto-death," to zap the Player back to start

           void Start(){
                if (GameObject.FindWithTag("GameHandler") != null){
                   gameHandlerObj = GameObject.FindWithTag("GameHandler").GetComponent<GameHandler>();
                }
           }

           public void OnCollisionEnter2D(Collision2D other) {
                  if (other.gameObject.tag == "Player") {
                         gameHandlerObj.playerGetHit(damage);
                         //other.transform.position = new Vector3(backToStart.position.x, backToStart.position.y, backToStart.position.z);
                  }
           }
    }



      HAZARD 2. DON'T TOUCH THE LAVA (Slow Damage):

    Lava produces increased damage the longer a player stands in it. Like the Spike, it can be created as a Tilemap layer called "Tilemap_lava" (with the Tilemap Collider 2D Component applied) or on a Prefab Sprite (with BoxCollider2D Component).

    SCRIPT: In either option, drag the following script DamageSlow.cs onto the Spike object or Tilemap layer, to make it cause damage to the Player:
    DamageSlow.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class DamageSlow : MonoBehaviour {

           public int damage = 1;
           public float damageTime = 0.5f;
           private bool isDamaging = false;
           private float damageTimer = 0f;
           private GameHandler gameHandlerObj;

           void Start () {
             if (GameObject.FindWithTag ("GameHandler") != null) {
                gameHandlerObj = GameObject.FindWithTag ("GameHandler").GetComponent<GameHandler>();
             }
           }

           void FixedUpdate(){
                  if (isDamaging == true){
                         damageTimer += 0.1f;
                         if (damageTimer >= damageTime){
                                gameHandlerObj.playerGetHit (damage);
                                damageTimer = 0f;
                         }
                  }
           }

           void OnTriggerStay2D(Collider2D other){
                  if (other.gameObject.tag == "Player") {
                         isDamaging = true;
                  }
           }

           void OnTriggerExit2D(Collider2D other){
                  if (other.gameObject.tag == "Player") {
                         isDamaging = false;
                  }
           }
    }



      HAZARD 3. MOVING WALL OF DOOM (Traveling Threat):
    To motivate a player to move forward or up, put a big wall of fiery / stabby death behind them, and move it towards the player!

    a. Create a new Empty GameObject. In the Inspector name it "WallOfDoom" and reset Transforms.

    b. [Add Components] to WallOfDoom: Physics2D > BoxCollider2D set to isTrigger, and Rigidbody2D, set BodyType = Kinematic.

    c. Create and import art for your wall of lava, fire, spikes, etc. An animated spritesheet will feel more dangerous. Drag into the Hierarchy and make the art a child of WallOfDoom.

    d. Create a new C# script called "WallOfDoom.cs" with the content below. Drag this script onto WallOfDoom:
    WallOfDoom.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class WallOfDoom : MonoBehaviour {
           //NOTE: this script moves right-ward by default, but turn on isVertical to move upward;
           public float moveDelay = 1f;
           public float moveRate = 100f;
           public bool isVertical = false;
           public bool startMove = false;
           public float moveTimer = 0;
           public Rigidbody2D rb2D;
           public Vector2 forceVector;
           //public GameObject startDoomEffect; //uncomment to spawn a spritesheet or particles on move start
           //private Animator anim; //uncomment for animated wall (rotating spike wheels, roiling fire or lava, etc)
           //public AudioSource startSFX;

           void Start(){
                  rb2D = gameObject.GetComponent<Rigidbody2D>();
                  //anim = gameObject.GetComponentInChildren<Animator>();
           }

           void FixedUpdate(){
                  moveTimer += 0.01f;
                  if (moveTimer >= moveDelay){
                         startMove = true;
                         //GameObject startMove = Instantiate (startDoomEffect, transform.position, transform.rotation);
                         //anim.SetBool("deathmotion", true);
                         //startSFX.Play();
                  }

                  if (startMove == true){
                         float moveForce = moveRate * Time.deltaTime;
                         if (isVertical == false){
                                forceVector = new Vector2(moveForce, 0);
                         } else if (isVertical == true) {
                                forceVector = new Vector2(0, moveForce);
                         }
                         rb2D.velocity = forceVector;
                  }
           }
    }
    SCRIPTING NOTE: There are multiple ways to move an object in Unity (see this video for motion types).
    The Rigidbody2D is here to allow colliders to work, and Rigidbody2D.velocity replaces all physics so motion is purely controlled by the script.

    e. ADD DAMAGE SCRIPT: This script is just to move / animate the wall. To make it cause damage, add the DamageInstant.cs script to the WallOfDoom GameObject (see above).



       
    PLATFORMS: Moving, Slippery / Sticky, Bouncy
       
      PLATFORM 1. MOVING PLATFORMS:

    OPTION 1: Back and Forth
    Summary: Add a timing test to your level: make some moving platforms!

    a. Create a new Empty GameObject. In the Inspector name it "PlatformMoving" and reset Transforms.

    b. [Add Components] to PlatformMoving: Physics2D > BoxCollider2D and Rigidbody2D, set BodyType = Kinematic.

    c. Create and import art for your Platform. Drag into the Hierarchy and make the art a child of PlatformMoving.

    d. Create a new C# script called "PlatformMove.cs" with the following, and add to PlatformMoving.

    PlatformMove.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PlatformMove : MonoBehaviour {

           private float speed = 2f;
           private bool moveToA = true;
           public Transform moveTargetA;
           public Transform moveTargetB;
           private Vector3 mTA;
           private Vector3 mTB;

           // If the platform is a creature that should turn around as it moves back and forth, set true:
           private bool isHorizontal = false;

           void FixedUpdate(){
                  mTA = new Vector3(moveTargetA.position.x, moveTargetA.position.y, 0);
                  mTB = new Vector3(moveTargetB.position.x, moveTargetB.position.y, 0);
                  float step = speed * Time.deltaTime;        // calculate distance to move

                  if (moveToA){
                         transform.position = Vector3.MoveTowards(transform.position, mTA, step);
                  } else {
                         transform.position = Vector3.MoveTowards(transform.position, mTB, step);
                  }

                  if (Vector3.Distance(moveTargetA.position, transform.position) < 1){ moveToA = false; turn();}
                  else if (Vector3.Distance(moveTargetB.position, transform.position) < 1){ moveToA = true; turn();}
           }

           void OnCollisionEnter2D(Collision2D other){
                  if (other.gameObject.tag == "Player"){
                         other.collider.transform.SetParent(transform);        // so Player moves with platform
                  }
           }

           void OnCollisionExit2D(Collision2D other){
                  if (other.gameObject.tag == "Player"){
                         other.collider.transform.SetParent(null);        // Player not parented when off platform
                  }
           }

           void turn(){
                  if (isHorizontal){
                         Vector3 theScale = transform.localScale;
                         theScale.x *= -1;
                         transform.localScale = theScale;
                  }
           }
    }

    e. Create move targets: Create a new Empty GameObject.
    In the Inspector name it "moveTargetA", reset Transforms, and click the upper-left icon to be visible in Scene.
    Duplicate moveTargetA [Ctrl/Cmd]+[d] and in the Inspector rename "moveTargetB".
    Drag these into the TargetA and TargetB script slots.

    f. Make a Prefab: Create a new Empty GameObject.
    In the Inspector name it "PlatformMoveSYSTEM" and reset Transforms.
    Drag PlatformMoving, PlatPosA, and PlatPosB onto PlatformMovingSYSTEM to make them children.
    Drag PlatformMovingSYSTEM into the Project panel to make it a Prefab, so copies can be made.
    NOTE: Only move the elements of this Prefab in Scenes, not in the Prefab, or all Scene copies may change.

     
    OPTION 2: Move With Player
    Summary: Make a platform that ONLY moves when the player stands on it (source):

    a.Create a new Empty GameObject. In the Inspector name it "PlatformMoving", reset Transforms.

    b. [Add Components] to PlatformMoveWithPlayer: Physics2D > BoxCollider2D and Rigidbody2D, set BodyType = Kinematic.

    c. Create and import art for your Platform.
    Drag into the Hierarchy and make the art a child of PlatformMoving.
    Name it "Platform_art". Adjust the BoxCollider2D size to match the width of the art.

    d. Create a new C# script named "PlatformMoveWithPlayer.cs" with the following content. Add to platform.
    In the Inspector, choose the Velocity values for the desired direction and speed. X= side-to-side, Y=Up and Down. Try X=1.

    PlatformMoveWithPlayer.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PlatformMoveWithPlayer : MonoBehaviour {

           [SerializeField]
           private Vector3 velocity;

           private bool moving = false;

           private void FixedUpdate(){
                  if (moving){
                         transform.position += (velocity * Time.deltaTime);
                  }
           }

           private void OnCollisionEnter2D(Collision2D other){
                  if (other.gameObject.tag == "Player"){
                         moving = true;
                         other.collider.transform.SetParent(transform); // so Player moves with platform
                  }
           }

           private void OnCollisionExit2D(Collision2D other){
                  if (other.gameObject.tag == "Player"){
                         moving = false;
                         other.collider.transform.SetParent(null); // Player not parented when off platform
                  }
          }
    }

    Hit [Play] and test by jumping on and off of this platform.
    Turn off [Play] to continue editing.

    e. Drag the PlatformMoving object into yourProject panel > Assets > Prefabs folder to make it a Prefab, so duplicates can be added to this and other scenes.

    MOVING PLATFORM SIZING NOTE:
    Because this script makes the Player temporarily a child of the PlatformMoveing object, the Platform object cannot be scaled bigger or smaller, or the Player will suddenly change size when touching the Platform!

    If you want a copy of the Platform Prefab in your scene to be a different size than in the Prefab:
    1. Scale the child Platform art
    instead, and then
    2. Change the size of the platform collider to match the new art size
    This is one of the benefits of making functionality and art as separate objects!



      PLATFORM 2. STICKY / SLIPPERY PLATFORMS:

    Summary: Add a script to certain platfoms to change how the player moves on those platforms.
    This can be done most simply by adding collision tests to the player movement script that look for platforms with a certain tag and then modifies the player movement speed as a result (and possibly player animation to show the challenges of moving on slippery or sticky ground). All the platform needs in that case is a special tag.

    A more robust version is accomplished if we add a script to the platforms that allows them to have customized slippery or sticky movement values. if the player base speed = 10f, then a decent slippery multiplier could be 3f, and a reasonably sticky multiplier = 0.2f.

    a. Given a Tilemap layer or (individual platform sprites) with collision already applied, and given a PlayerMove.cs script applied to the player to control movemrnt:
    Create a new C# script called "Platform_SlippyStick.cs" with the following, and add to the intended platform. The public bool "isSlippery" lets the level designer use thsi same script for both slippery and sticky platforms: turn it on for slippery surfaces, turn it off for sticky surfaces.

    Platform_SlippyStick.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PlatformMove : MonoBehaviour {

          private PlayerMove pMove;
          public bool isSlippery = false;
          public float slipperyMultiplier = 3f;
          public float stickyMultiplier = 0.2f;

          void Start(){
                pMove = GameObject.FindWithTag("Player").GetComponent<PlayerMove>();
          }

          void OnCollisionEnter2D(Collision2D other){
                if (other.gameObject.tag == "Player"){
                      if (isSlippery == true){
                             //Debug.Log("I am a slippery platform");
                            pMove.playerMoveModify(slipperyMultiplier, false);
                      } else {
                             //Debug.Log("I am a sticky platform");
                            pMove.playerMoveModify(stickyMultiplier, false);
                      }
                }
          }

          void OnCollisionExit2D(Collision2D other){
                if (other.gameObject.tag == "Player"){
                      pMove.playerMoveModify(0f, true);
                       //Debug.Log("I am moving normally!");
                }
          }
    }

    b. Add functions to the PlayerMove.cs script. The following code has the added lines in Bold.
    Note: only the first commands in the playerMoveModify() function are needed to change the speed variable.

    add move speed mofidcation to PlayerMove.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PlayerMove : MonoBehaviour{

          public Animator animator;
          public Rigidbody2D rb2D;
          private bool FaceRight = true; // determine which way player is facing.
          public static float runSpeed;
          public float startSpeed = 10f;
          public bool isAlive = true;
          public AudioSource walkSFX;

          private Renderer myRend;
          private Color defaultColor;
          private bool isSpeedChange = false;

          void Start(){
                animator = gameObject.GetComponentInChildren<Animator>();
                rb2D = transform.GetComponent<Rigidbody2D>();
                myRend = gameObject.GetComponentInChildren<Renderer>();
                defaultColor = myRend.material.color;
                runSpeed = startSpeed;
          }

          void Update(){
                //NOTE: Horizontal axis: [a] / left arrow is -1, [d] / right arrow is 1
                Vector3 hMove = new Vector3(Input.GetAxis("Horizontal"), 0.0f, 0.0f);
                if (isAlive == true){
                      transform.position = transform.position + hMove * runSpeed * Time.deltaTime;
                }

                if (Input.GetAxis("Horizontal") != 0){
                      animator.SetBool ("walk", true);
                      if (!walkSFX.isPlaying){
                            walkSFX.Play();
                      }
                } else {
                      animator.SetBool ("walk", false);
                      walkSFX.Stop();
                }

                //NOTE: if input is moving the Player right and Player faces left, turn, and vice-versa
                if ((hMove.x >0 && !FaceRight) || (hMove.x < 0 && FaceRight)){
                      playerTurn();
                }
          }

          private void playerTurn(){
                // NOTE: Switch player facing label
                FaceRight = !FaceRight;

                //NOTE: Multiply player's x local scale by -1.
                Vector3 theScale = transform.localScale;
                theScale.x *= -1;
                transform.localScale = theScale;
                //turnSFX.Play();
          }

          public void playerMoveModify(float multiplier, bool isNormal) {
                if (isNormal == true){
                      runSpeed = startSpeed;
                      isSpeedChange = false;
                      myRend.material.color = defaultColor;
                } else {
                      runSpeed = (startSpeed * multiplier);
                      isSpeedChange = true;
                      Debug.Log("Speed is now: " + runSpeed);
                      myRend.material.color = new Color(1.0f, 1.0f, 2.5f);
                }
          }
    }



      PLATFORM 3. BOUNCY PLATFORMS:

    Summary: Add bouncy surfaces to help propel your player over obstacles! The higher the player starts their leap, the higher the bounce. Consider using a "signifier" to indicate a bouncy surface, like an animated bouncy mushroom (with thanks to Fiona Campbell)

    a. Make an empty GameObject. In the Inspector name it "BouncyPlatform".

    b. [Add Component] Physics2D > Box Collider 2D and Rigidbody2D.
    Set Rigidbody2D from Dynamic to Static.

    c. In Assets, RightClick to create a 2D > PhysicMaterial2D.
    In the Inspector set Friction = 5 and bounciness = 1.05.
    Drag the PhysicMaterial2D onto BouncyPlatform, and it populates in the material slot of the BoxCollider2D.




      PLATFORM 4. Ladders:
    Create an Empty Game Object. In the Inspector name it "Ladder" and reset Transforms.

    a). Create a PNG for your lader: about the width of the Player character.
    Import into the Project, drag into the Hierarchy, rename it ladder_art and reset Transforms.


    b) Create an Empty Game Object. In the Inspector name it "Ladder" and reset Transforms.
    Drag ladder_art onto the Ladder object to make it a child.

    Select Ladder and [Add Component] Physics2D > BoxCollider2D.
    Turn on isTrigger.
    Hit the Edit Collider button and set collider width and height to fit ladder_art.

    c). Create a new C# script "LadderMove.cs" with the content below. Add this script to the Sign object.
    Make sure the player character has the tag "Player", and that the Vertical axis is not yet used for anything else (like jumping):

    LadderMove.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class LadderMove : MonoBehaviour {

            private Transform playerTrans;
            private Rigidbody2D playerRB;
            public float upSpeed = 10f;
            private Vector3 vMove;

           
    public bool
    canLadder = false;

           void Start(){
                  if (GameObject.FindWithTag("Player") != null){
                         playerTrans = GameObject.FindWithTag("Player").GetComponent<Transform>();
                         playerRB = GameObject.FindWithTag("Player").GetComponent<Rigidbody2D>();
                  }
           }

           void Update(){
                  vMove = new Vector3(0.0f, Input.GetAxis("Vertical"), 0.0f);
                  if (canLadder == true){
                         playerTrans.position = playerTrans.position + vMove * upSpeed * Time.deltaTime;
                  }
           }

            void OnTriggerStay2D(Collider2D other){
                  if (other.gameObject.tag == "Player"){
                         canLadder = true;
                         playerRB.gravityScale = 0;
                         // if game has jumping, add bool to disable it, and set true here
                  }
            }

            void OnTriggerExit2D(Collider2D other){
                  if (other.gameObject.tag == "Player"){
                         canLadder = false;
                         playerRB.gravityScale = 1;
                         // if game has jumping, add bool to disable it, and set false here
                  }
            }

    }



     
       
    STEALTH TOOLS
       

    STEALTH 1: Hide Inside a Bush
    Change the player appearance when inside a bush, and make chasing enemies walk away.

    1. MAKE THE BUSH:
    Create an Empty Game Object. Add bush art as a child.
    Add Component to Empty: BoxCollider2D, set to isTrigger.
    Create and add a tag "Bush"

    2. ADD TO GAMEHANDLER:
    Add to the GameHandler.cs script a class variable:
           public static bool isPlayerHidden = false;

    3. ADD TO ENEMY MOVETOWARDS FUNCTIONS:
    Look to this static variable to decide if the player should be chased or ignored.
    Add to the Update() function: check GameHandler.isPlayerHidden == false to chase.
    Add, if-condition: if GameHandler.isPlayerHidden == true and enemy is within distance threshold, move away:
           void Update(){
                  float DistToPlayer = Vector3.Distance(transform.position, target.position);

                  if ((target != null) && (DistToPlayer <= attackRange)){
                         //move towards unhidden player, if within distance threshold:
                        if (GameHandler.isPlayerHidden == false){
                                transform.position = Vector2.MoveTowards (transform.position, target.position, speed * Time.deltaTime);
                         }
                         //move away from hidden player, if within distance threshold:
                        else {
                                transform.position = Vector2.MoveTowards (transform.position, player.position, -speed * Time.deltaTime);
                         }
                  }

    // ... [enemy flip code here. See enemy scripts in section [2D]]

           }

    4. ADD TO PLAYER:
    Duplicate the player_art and set the duplicate to color=black.
    Create a new C# script and add to the player: PlayerHide.cs
    Add normal and black art to playerNormal and playerHidden slots in Inspector

    PlayerHide.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PlayerHide : MonoBehaviour{

           public GameObject playerHidden;
           public GameObject playerNormal;
           //public Transform lastBush;              //can destroy used bushes, make them single-use

           void Start(){
                  playerHidden.SetActive(false);
           }

           void Update(){
                  if ((Input.GetKeyDown("space")) && (canExplode == true)){
                         destroyTileArea();
                  }
           }

           void OnTriggerEnter2D(Collider2D other){
                  if (other.gameObject.tag == "Bush"){
                        //lastBush = other.gameObject.transform;
                         playerHidden.SetActive(true);
                         playerNormal.SetActive(false);
                         GameHandler.isPlayerHidden = true;
                  }
           }

           void OnTriggerExit2D(Collider2D other){
                  if (other.gameObject.tag == "Bush"){
                        //Destroy(lastBush);
                         playerHidden.SetActive(false);
                         playerNormal.SetActive(true);
                         GameHandler.isPlayerHidden = false;
                  }
           }

    }

       
    INTERACTIVE TILEMAPS
       

      TILEMAPS 1. Destructible Tilemaps
    Destroy tiles in a tilemap dynamically during play using a script.
    The following destroys tiles based on the player position as it walks around.

    Tilemap.WorldToCell can convert the player's position to the current cell they occupy.
    Then, depending on player direction, + 1 to that Cell position, and then use that position to SetTile.

    To change a tile: TilemapReference.SetTile(Vector3Int position, Tilemap.TileBase tile);

    To remove a tile: TilemapReference.SetTile(null);
           tilemap.SetTile(new Vector3Int(0,0,0), null);

    If the script is located on the Tilemap layer: TileMap tilemap = GetComponent<TileMap>();
           (can also be found with a Tag)

    Applied the script below to the Player. Fill the script slot with the desired Tilemap (Colliders, Lava, etc).
    Hit [Play], near part of the applied Tilemap, hit [spacebar] to remove all tiles within the destroyRange.


    DestroyTiles.cs script: (with thanks to Amy Stoltz)
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.Tilemaps;

    public class DestroyTiles : MonoBehaviour{

           public Tilemap destructableTilemap;
           private List<Vector3> tileWorldLocations;
           public float rangeDestroy = 2f;
           public bool canExplode = true;
           //public GameObject boomFX;

           void Start(){
                  TileMapInit();
           }

           void Update(){
                  if ((Input.GetKeyDown("space")) && (canExplode == true)){
                         destroyTileArea();
                  }
           }

           void TileMapInit(){
                  tileWorldLocations = new List<Vector3>();

                  foreach (var pos in destructableTilemap.cellBounds.allPositionsWithin){
                         Vector3Int localPlace = new Vector3Int(pos.x, pos.y, pos.z);
                         Vector3 place = destructableTilemap.CellToWorld(localPlace) + new Vector3(0.5f, 0.5f, 0f);

                         if (destructableTilemap.HasTile(localPlace)){
                                tileWorldLocations.Add(place);
                         }
                  }
           }

           void destroyTileArea(){
                 foreach (Vector3 tile in tileWorldLocations){
                         if (Vector2.Distance(tile, transform.position) <= rangeDestroy){
                                //Debug.Log("in range");
                                Vector3Int localPlace = destructableTilemap.WorldToCell(tile);
                                if (destructableTilemap.HasTile(localPlace)){
                                       //StartCoroutine(BoomVFX(tile));
                                       destructableTilemap.SetTile(destructableTilemap.WorldToCell(tile), null);
                                }
                         //tileWorldLocations.Remove(tile);
                         }
                  }
           }

           //IEnumerator BoomVFX(Vector3 tilePos){
                  //GameObject tempVFX = Instantiate(boomFX, tilePos, Quaternion.identity);
                  //yield return new WaitForSeconds(.5f);
                  //Destroy(tempVFX);
           //}

           //NOTE: To help see the attack sphere in editor:
           void OnDrawGizmosSelected(){
                  Gizmos.DrawWireSphere(transform.position, rangeDestroy);
           }
    }

    This script destroys just one tile at a time (needs to be adjusted to match player direction):

    DestroyTile.cs script: (code source)
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.Tilemaps;

    public class DestroyTile : MonoBehaviour{
           public Tile newTile;
           public Tilemap targetTilemap;

           private Vector3Int previous;

           // Make these changes in LateUpdate() so player has option to move in Update()
           private void LateUpdate(){
                  // Get the current grid location
                  Vector3Int currentCell = targetTilemap.WorldToCell(transform.position);
                  // Add one in a direction (change this as needed to match your directional control)
                  currentCell.x += 1;

                  // If the position has changed
                  if (currentCell != previous){
                         // erase previous
                         targetTilemap.SetTile(previous, null);

                         // save the new position for next frame
                         previous = currentCell;
                  }
           }
    }

    Here is an exciting script adjustment by Alex Koppel for a side-scroller game, where the destructible tiles represent buildings, and when the player destroys the supports the top gets destroyed, as well.





      TILEMAPS 2. Paintable Tilemaps:
    Want to paint new tiles into a Tilemap during runtime? For example, want to draw lava, or ice?

    The following C# Script draws new tiles onto a Tilemap based on the position of the object holding the script (like the Player) and specified range.
    Load two Tilemaps into the script slots: one for the empty layer to be painted (be sure it has a higher Order In Layer), and another for the layer that has the content to be covered (like a background layer).
    Then add the Tile sprite into the Tile slot.

    CreateTiles.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.Tilemaps;

    public class CreateTiles : MonoBehaviour{
           public Tilemap paintTilemap; //a (potentially) empty map to be painted
           public Tilemap rangeTilemap; // the entire space that can be painted
           public Tile newTile;
           private List<Vector3> tileWorldLocations;
           public float rangePaint = 1.5f;
           public bool canPaint = true;
           //public GameObject paintFX;

           void Start(){
                  TileMapInit();
           }

           void Update(){
                  if ((Input.GetKeyDown("space")) && (canPaint == true)){
                         CreateTileArea();
                  }
           }

           void TileMapInit(){
                  tileWorldLocations = new List<Vector3> ();

                  foreach (var pos in rangeTilemap.cellBounds.allPositionsWithin){
                         Vector3Int localPlace = new Vector3Int(pos.x, pos.y, pos.z);
                         Vector3 place = rangeTilemap.CellToWorld(localPlace) + new Vector3(0.5f, 0.5f, 0f);

                         if (rangeTilemap.HasTile(localPlace)){
                                tileWorldLocations.Add(place);
                         }
                  }
           }

           void CreateTileArea(){
                 foreach (Vector3 tile in tileWorldLocations){
                         if (Vector2.Distance(tile, transform.position) <= rangePaint){
                                //Debug.Log("in range");
                                //StartCoroutine(PaintVFX(tile));
                                paintTilemap.SetTile(paintTilemap.WorldToCell(tile), newTile);
                         }
                  }
           }

           //IEnumerator PaintVFX(Vector3 tilePos){
                  //GameObject tempVFX = Instantiate(paintFX, tilePos, Quaternion.identity);
                  //yield return new WaitForSeconds(.5f);
                  //Destroy(tempVFX);
           //}

           //NOTE: To help see the attack sphere in editor:
           void OnDrawGizmosSelected(){
                  Gizmos.DrawWireSphere(transform.position, rangePaint);
           }
    }
     

    The following C# Script paint one new tile at a time onto a Tilemap based on the position of the object holding the script (needs to be adjusted to match player direction).

    CreateTile.cs script: (code source)
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.Tilemaps;

    public class CreateTile : MonoBehaviour{
           public Tile newTile;
           public Tilemap targetTilemap;

           private Vector3Int previous;

           // Make these changes in LateUpdate() so player has option to move in Update()
           private void LateUpdate(){
                  // Get the current grid location
                  Vector3Int currentCell = targetTilemap.WorldToCell(transform.position);
                  // Add one in a direction (change this as needed to match your directional control)
                  currentCell.x += 1;

                  // If the position has changed
                  if (currentCell != previous){
                         // set the new tile
                         targetTilemap.SetTile(currentCell, newTile);

                         // erase previous
                         targetTilemap.SetTile(previous, null);

                         // save the new position for next frame
                         previous = currentCell;
                  }
           }
    }

     


      TILEMAPS 3. Change Tilemap Color: (Notes to come)
    Change the color of existing Tilemaps upon interaction
    Change the opacity of a "top" Tilemap to reveal the tilemap "below"









      TILEMAPS 4. Dynamic Terrain Generation: (Notes to come)
    Generate a Terrain of grid Tiles (does not use Tilemaps): Intro to Tile Generation
    Painting in Prefabs like trees or pickups as easily as painting a tilemap: Prefab Painting






       
    PUZZLE MECHANICS:
       

      PUZZLE 1. MOVEABLE BOX:
    Who doesn't love to move a crate? With thanks to Will Mielke.

    a. Create an Empty Game Object. Name it "Crate_Moveable". Reset Transforms.

    b. Create a Game Object > 2D Object > Sprite > Rectangle. Reset Transforms.
    In the Inspector name it "crate_art".
    Add art to the sprite (at least the default background sprite).
    Set OrderInLayer=95. Color and size as desired.
    Drag the crate_art onto Crate_Moveable to make it a child.

    c. With Crate_Moveable selected, [Add Components]: Physics2D > BoxCollider2D.
    Hit the edit collider button and adjust size to match the art child.
    [Add Components]: Physics2D > RigidBody2D. Set Gravity Scale = 0, turn on Constraints > Rotation Z.

    d. Create a new C# script, name it "CrateMove.cs".
    Add the content below and Save. In Unity apply the script to the Crate_Moveable object.

    CrateMove.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class CrateMove : MonoBehaviour {

           void OnCollisionExit2D(Collision2D colExt){
                  gameObject.GetComponent<Rigidbody2D>().velocity = Vector3.zero;
                  if ((colExt.gameObject.GetComponent<Rigidbody2D>() != null) && (colExt.gameObject.GetComponent<Rigidbody2D>().bodyType ==RigidbodyType2D.Dynamic)){
                            colExt.gameObject.GetComponent<Rigidbody2D>().velocity = Vector3.zero;
                    }
            }

    }
    Note this script does not need to enable object to be pushed; that is automatic between objects with Rigidbody2D = Dynamic. The OnCollisionEnter2D function makes sure when an object is relseased it stays where it is put.



      PUZZLE 2. BLOCK-ABLE MULTI-DIRECTIONAL LASER:
    Here is a laser to hurt the player, but it can be blocked with a moveable box. With thanks to Will Mielke.

    a). Create an Empty Game Object. Name it "Box_Laser". Reset Transforms.

    b). Create a Game Object > 2D Object > Sprite > Rectangle. Reset Transforms.
    In the Inspector name it "boxLaser_art".
    Add art to the sprite (at least the default background sprite).
    Set Order In Layer = 95. Color and size as desired.
    Drag the boxLaser_art onto Box_Laser to make it a child.

    c). With Box_Laser selected, [Add Components]: Physics2D > BoxCollider2D.
    Hit the Edit Collider button and adjust size to match the art child.

    d). Create an Empty Game Object: GameObject > Empty Game Object.
    Reset Transforms, name it "hitPoint".
    Drag it onto the BoxLaser object to make it a child (should be at parent center).

    e). Create 4 Line Renderers: GameObject > Effects > Line.
    Reset Transforms, name them "LaserLeft", "LaserRight", "LaserTop", "LaserBottom".
    Set Order In Layer = 94. Turn on "Use World Space". Color and size as desired.
    Select just left and right lasers, set Position > index 1 > Z from 1 to 0.
    For each laser, drag the word "width" to slide the value from 0.1 to 0.2.
    Drag them onto the BoxLaser object to make them children.
    Move them to the four sides of the box art, per their names (so LaserEmitLeft touches the art left side). IMPORTANT: Be sure all Line Renderer objects are positioned OUTSIDE of the object's collider, or they will not appear (they will stop at the collider edge).

    f). Create a new C# script, name it "Laser Controller.cs".
    Add the content below and Save. In Unity apply the script to the Box_Laser object.
    Populate the script slots with the hitPoint object and the 4 laser emitters.


    LaserController.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class LaserController : MonoBehaviour {
          // NOTE:: if laser does not hit something it throws null exception. Make sure laser is always enclosed
          public bool activated; // determines if laser is on or off
          public bool left, right, up, down; // determines which sides of box are on when activated
          public int laserDamage = 1;
          public bool killEnemy = true;

           private GameHandler gameHandlerObj;

           // ignore these variables public so that it is easier to access them as they are on children
           public LineRenderer leftLaser, rightLaser, topLaser, botLaser;
           public Transform hitPoint;
           private Transform leftTrans, rightTrans, topTrans, botTrans;

           void Start(){
                  leftLaser.enabled = rightLaser.enabled = topLaser.enabled = botLaser.enabled = true;
                  leftLaser.useWorldSpace = rightLaser.useWorldSpace = true;
                  topLaser.useWorldSpace = botLaser.useWorldSpace = true;

                  leftTrans = leftLaser.GetComponent<Transform>();
                  rightTrans = rightLaser.GetComponent<Transform>();
                  topTrans = topLaser.GetComponent<Transform>();
                  botTrans = botLaser.GetComponent<Transform>();

                  GameObject gameHandlerLocation = GameObject.FindWithTag("GameHandler");
                  if (gameHandlerLocation != null){
                          gameHandlerObj = gameHandlerLocation.GetComponent<GameHandler>();
                  }
            }

           void Update(){
                  if (!activated){ // if not activated do nothing
                            leftLaser.enabled = rightLaser.enabled = topLaser.enabled = botLaser.enabled = false;
                            return;
                  }

                  RaycastHit2D hit; // point where laser hits

                  if (left){
                         // find what the laser hits and draw it
                         leftLaser.enabled = true;
                         hit = Physics2D.Raycast(leftTrans.position, new Vector2(-1, 0));
                         hitPoint.position = hit.point;
                         leftLaser.SetPosition(0, leftTrans.position);
                         leftLaser.SetPosition(1, hitPoint.position);
                         // laser kills enemies and does constant damage to player, by a lot
                         if (hit.transform.CompareTag("Player") && laserDamage > 0){
                                 gameHandlerObj.playerGetHit(laserDamage);
                         }
                         else if ((hit.transform.CompareTag("Enemy")) && (killEnemy==true)){
                                 Destroy(hit.transform.gameObject);
                         }
                  }
                  else {
                          leftLaser.enabled = false;
                  }

                  if (right){
                          // find what the laser hits and draw it
                          rightLaser.enabled = true;
                          hit = Physics2D.Raycast(rightTrans.position, new Vector2(1, 0));
                          Debug.DrawLine(rightTrans.position, hit.point);
                          hitPoint.position = hit.point;
                          rightLaser.SetPosition(0, rightTrans.position);
                          rightLaser.SetPosition(1, hitPoint.position);
                          // laser kills enemies and does constant damage to player, by a lot
                          if (hit.transform.CompareTag("Player") && laserDamage > 0){
                                    gameHandlerObj.playerGetHit(laserDamage);
                          }
                          else if ((hit.transform.CompareTag("Enemy")) && (killEnemy==true)){
                                    Destroy(hit.transform.gameObject);
                          }
                  }
                  else {
                          rightLaser.enabled = false;
                  }

                  if (up){
                          // find what the laser hits and draw it
                          topLaser.enabled = true;
                          hit = Physics2D.Raycast(topTrans.position, new Vector2(0,1));
                          Debug.DrawLine(topTrans.position, hit.point);
                          hitPoint.position = hit.point;
                          topLaser.SetPosition(0, topTrans.position);
                          topLaser.SetPosition(1, hitPoint.position);
                         // laser kills enemies and does constant damage to player, by a lot
                         if (hit.transform.CompareTag("Player") && laserDamage > 0) {
                                    gameHandlerObj.playerGetHit(laserDamage);
                          }
                          else if ((hit.transform.CompareTag("Enemy")) && (killEnemy==true)){
                                    Destroy(hit.transform.gameObject);
                          }
                  }
                  else {
                          topLaser.enabled = false;
                  }

                  if (down){
                          // find what the laser hits and draw it
                          botLaser.enabled = true;
                          hit = Physics2D.Raycast(botTrans.position, new Vector2(0, -1));
                          Debug.DrawLine(botTrans.position, hit.point);
                          hitPoint.position = hit.point;
                          botLaser.SetPosition(0, botTrans.position);
                          botLaser.SetPosition(1, hitPoint.position);
                          // laser kills enemies and does constant damage to player, by a lot
                          if (hit.transform.CompareTag("Player") && laserDamage > 0){
                                    gameHandlerObj.playerGetHit(laserDamage);
                          }
                          else if ((hit.transform.CompareTag("Enemy")) && (killEnemy==true)){
                                    Destroy(hit.transform.gameObject);
                          }
                  }
                  else {
                          botLaser.enabled = false;
                  }
            }
    }




      PUZZLE 3. SLIDING WALLS:
    For a top-down game, slide vertical and horizontal walls along their axis to navigate a maze. This tutorial modifies a drag-drop script, which is not direcly compatible with Unity Physics, and the collider walls are separate objects that follows the dragged objects.

    a). Make the Parent Object for the Vertical sliding wall:
    Create an Empty Game Object.
    In the Inspector, name it "Wall_Vertical" and Reset Tranforms.

    b). Make the child Wall Object:
    RightClick Wall_Vertical to create an Empty Game Object.
    In the Inspector: name it "Wall".
    [Add Component] > BoxCollider2D. Set Size X = 1, Y = 2.
    [Add Component] > Rigidbody2D. Set Gravity=0. Constraints > Freeze Rotation.
    RightClick Wall to create 2D Object > Sprites > Square.
    In the Inspector: name it "wall_art".
    Set Scale X = 1, Y = 2.
    Set wall_art Color = blue and Order in Layer = 50.

    c). Make the child Dragger Object (NOTE: isTrigger, no Rigidbody2D, and alpha):
    RightClick
    Wall_Vertical to create another Empty Game Object.
    In the Inspector: name it "Dragger".
    [Add Component] > BoxCollider2D. Set Size X = 1, Y = 2.
    Set BoxCollider2D to isTrigger.
    RightClick Dragger to create 2D Object > Sprites > Square.
    In the Inspector: name it "dragger_art".
    Set Scale X = 1, Y = 2.
    Set Color = yellow, alpha = 20/255, and Order in Layer = 55.

    d). Create a new C# script called "WallSlider.cs". Add the following content and Save.
    In Unity apply this script to the Dragger object (NOT the parent or the Wall).
    Drag the Wall object into the "theWall" script slot.
    Hit [Play] and try dragging the wall on its axis! Collisions should work on the wall.

    WallToggle.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class WallToggle : MonoBehaviour{
           public int channel = 0;
           public bool startOn = true;
           private bool toggledOn = true;
           public GameObject wallUp;
           public GameObject wallDown;
           public bool snapToNearestGridSpace = true;

             void Start(){
                  wallUp.SetActive(true);
                  wallDown.SetActive(false);

                  GameObject[] buttonArr = GameObject.FindGameObjectsWithTag("WallToggleButton");
                  foreach (GameObject button in buttonArr){
                         if (button.GetComponent<WallToggleButton>().channel == channel){
                                button.GetComponent<WallToggleButton>().wallList.Add(this);
                         }
                  }

                  if (!startOn){ ToggleState(); }

                  if (snapToNearestGridSpace){
                         float newX = Mathf.Round(transform.position.x - 0.5f) + 0.5f;
                         float newY = Mathf.Round(transform.position.y - 0.5f) + 0.5f;

                         transform.position = new Vector3(newX, newY, transform.position.z);
                  }
           }

           public void ToggleState(){
                  toggledOn = !toggledOn;
                  if (toggledOn){
                         wallUp.SetActive(true);
                         wallDown.SetActive(false);
                         GetComponent<BoxCollider2D>().enabled =true;
                  }
                  else {
                         wallUp.SetActive(false);
                         wallDown.SetActive(true);
                         GetComponent<BoxCollider2D>().enabled = false;
                  }
           }

           public void SetColor(Color col){
                  GetComponentInChildren<SpriteRenderer>().color = col;
           }
    }


    Hit [Play] and try dragging the wall on its axis!
    Collisions should work on the wall.
    Turn off [Play]

    e). Make Wall_Horizontal by duplicating Wall_Vertical and making these 4 changes:
    Rename the duplicate parent "Wall_Horizontal".
    Set both Wall and Dagger BoxCollider2D to Size X = 2, Y = 1.
    Set both wall_art and dragger_art Scale X = 2, Y = 1.
    Select Dragger object. In the WallSlider.cs script component, set isVertical = off.

    f). Drag both Wall_Vertical and then Wall_Horizontal into the Project panel Assets folder to make them Prefabs.



      PUZZLE 4. SWITCH-ABLE WALLS:
    Turn a wall on and off by running over a button.

    a). Create a spritesheet with four pieces of art: two for the button (up and down) and two for the wall
    (present and absent, like a dotted outline). This example is 32x32, with each piece of art just 16x16:

    b). Import the spritesheet into Unity and set Inspector parameters:
            Sprite Mode = Multiple
            Pixels per unit = 16
            Filter Mode = Point (no filter)
            Compression = none. Hit [Apply].
            Open [Sprite Editor], open Slice menu, type="Grid by Cell Count", 2x2, hit [Slice] and [Apply].
    In the Assets folder open the sprite twirly to view the four individual sprites.

    c). Create an Empty Game Object, name it "WallToggle" and Reset Transforms.
    Drag into the Hierarchy both wall sprites. Name them "wall_art" and "wall_absent_art".
    Reset Transforms. Drag both sprites onto WallToggle to make them children.

    d). With WallToggle selected, [Add Components]: Physics2D > BoxCollider2D.
    Hit the Edit Collider button and adjust size to match the art child.

    e). Create another Empty Game Object, name it "WallToggleButton" and Reset Transforms.
    Drag into the Hierarchy both button sprites. Name them "wallButtonUp_art" and "wallButtonDown_art".
    Reset Transforms. Drag both sprites onto WallToggleButton to make them children.

    f). With WallToggleButton selected, [Add Components]: Physics2D > BoxCollider2D.
    Hit the Edit Collider button and adjust size to match the art child.
    Turn on isTrigger.

    g). Create a new tag named "WallToggleButton". Apply the tag to the WallToggleButton object.

    h). Create a new C# script called "WallToggle.cs". Add the following content and Save.
    In Unity apply this script to the WallToggle object and drag the child sprites into the script slots.

    WallToggle.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class WallToggle : MonoBehaviour{
           public int channel = 0;
           public bool startOn = true;
           private bool toggledOn = true;
           public GameObject wallUp;
           public GameObject wallDown;
           public bool snapToNearestGridSpace = true;

             void Start(){
                  wallUp.SetActive(true);
                  wallDown.SetActive(false);

                  GameObject[] buttonArr = GameObject.FindGameObjectsWithTag("WallToggleButton");
                  foreach (GameObject button in buttonArr){
                         if (button.GetComponent<WallToggleButton>().channel == channel){
                                button.GetComponent<WallToggleButton>().wallList.Add(this);
                         }
                  }

                  if (!startOn){ ToggleState(); }

                  if (snapToNearestGridSpace){
                         float newX = Mathf.Round(transform.position.x - 0.5f) + 0.5f;
                         float newY = Mathf.Round(transform.position.y - 0.5f) + 0.5f;

                         transform.position = new Vector3(newX, newY, transform.position.z);
                  }
           }

           public void ToggleState(){
                  toggledOn = !toggledOn;
                  if (toggledOn){
                         wallUp.SetActive(true);
                         wallDown.SetActive(false);
                         GetComponent<BoxCollider2D>().enabled =true;
                  }
                  else {
                         wallUp.SetActive(false);
                         wallDown.SetActive(true);
                         GetComponent<BoxCollider2D>().enabled = false;
                  }
           }

           public void SetColor(Color col){
                  GetComponentInChildren<SpriteRenderer>().color = col;
           }
    }


    i). Create a new C# script called "WallToggleButton.cs". Add the following content and Save.
    In Unity apply this script to the WallToggleButton object and drag the child sprites into the script slots.


    WallToggleButton.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class WallToggleButton : MonoBehaviour{
           public int channel = 0;
           public List<WallToggle> wallList = new List<WallToggle>();
           public GameObject buttonUp;
           public GameObject buttonDown;
           public bool snapToNearestGridSpace = true;

           private bool pressed = false;
           private bool setup = true;

           void Start(){
                  buttonUp.SetActive(true);
                  buttonDown.SetActive(false);
                  if (snapToNearestGridSpace){
                         float newX = Mathf.Round(transform.position.x - 0.5f) + 0.5f;
                         float newY = Mathf.Round(transform.position.y - 0.5f) + 0.5f;
                         transform.position = new Vector3(newX, newY, transform.position.z);
                  }
           }

           void Update(){
                  if (setup){
                         foreach(WallToggle wall in wallList){
                                wall.SetColor(GetComponentInChildren<SpriteRenderer>().color);
                         }
                         setup = false;
                  }
           }

           private void OnTriggerEnter2D(Collider2D collision){
                  if (collision.gameObject.name == "Player"){
                         pressed = true;
                         buttonUp.SetActive(false);
                         buttonDown.SetActive(true);
                         foreach(WallToggle wall in wallList){
                                wall.ToggleState();
                         }
                  }
           }

           private void OnTriggerExit2D(Collider2D collision){
                  if (collision.gameObject.name == "Player"){
                         pressed = false;
                         buttonUp.SetActive(true);
                         buttonDown.SetActive(false);
                  }
           }
    }



    j). Drag WallToggle and WallToggleButton into the Project panel Assets folder to make them Prefabs.

    k). To use this toggle-able wall feature in game:
           (a) Drag both prefabs into a Scene.

          
    (b) Select all 4 sprites and add a desired color (every wall that a given button opens should have the same color, and every other button and its walls in a scene should have a very distinct color). The WallToggleButton.cs script auto-sets the color of any same-channel walls to the same color, in case of any mistakes.

          
    (c) Set the "channel" property in the scripts on both objects to the same value.

          
    (d) Duplicate (Cmd/Ctrl) the wall to fill a gap and block player/enemy passage until the button is pressed.



      PUZZLE 5. WRECKABLE WALL (wip):

    Summary: Add a wall that can be destroyed by a player attack, like a massive cobweb that can be slashed by the player's sword. This can be a useful "affordance" -- give the player no where else to go, then teach the player how to attack something that does not fight back in order to pass through.

    a. Make an Empty GameObject: GameObject > Create Empty.
    In the Inspector name it "BreakableWall" and Reset Transforms.

    b. Create a wall PNGor download and import this web PNG.
    In the Inspector set Mode = Multiple, Pixels = 16, Point (no filter), compression = none. Hit [Apply].
    Open [Sprite Editor], choose "Slice by Cell Count", set r and C = 4x4, [slice] and [Apply].
    Drag the first frame of the spritesheet into the Hierarchy.
    In the Inspector rename "web_art", Reset Tranforms, and set the Order in Layer = 90.
    Drag this image onto the BreakableWall game object to make it a child.

    c. Set up animation: Open Window > Animation > Animation.
    With web_art seclected hit [CreateClip], navigate to your animation folder (or create it), name the new clip "web_full_idle" and [Save]. Drag in the first 4 frames, then drag the first one again to make a 5th, then space out to cover 30 frames (1/2 second). Hit Play in the Animation panel, adjust timing as desired.


    Create four more clips:
          web_full _cut (drag in sprites 4-8, time to 30 frames)
          web_half_idle (drag in sprites 8-12, time to 60 frames / 1 second)
          web_half_cut (drag in sprites 13-16, time to 30 frames)
          web_none_ide (drag in sprite 16 twice, time to 60 frames / 1 second)

    d. With web_art still selected, open the Animator panel:
    Create 4 parameters:
          Two bools named "wallHalf" and "wallGone".
          Two triggers named "cutFull" and "cutHalf".

    Set up the clips:
          RightClick [web_full_idle] to set it as the default.
          Draw transitions from [web_full_idle] to [web_half_idle] and vice-versa. Add the wallHalf bool to each direction (set return to false).
          Draw transitions from [web_full_idle] to [web_none_idle] and vice-versa. Add the wallGone bool to each direction (set return to false).
          Draw a transition from [any] to [web_full_cut], add the cutFull trigger to it. Draw a transition from [web_full_cut] to [web_half_idle] (with no parameter).
          Draw a transition from [any] to [web_half_cut], add the cutFull trigger to it. Draw a transition from [web_half_cut] to [web_none_idle] (with no parameter).


    e. Select web_breakable and [Add Component] Physics2D > Box Collider 2D.
    Turn on isTrigger, set width and height a bit bigger that the size of the art.

    f. Create a new Empty GameObject. In the Inspector rename it "web_collider", Reset Tranforms.
    [Add Component] Physics2D > Box Collider 2D. Set width and height a bit smaller than the art size.
    Drag onto the BreakableWall object to make it another child.

    g. Select any object and in the Inspector create a new Layer: BreakableWall.
    Select the BreakableWall object and assign the layer BreakableWall.

    h. Create a new C# script called "BreakableWall.cs", and add the following content.
    In Unity apply this script to the Web_breakable object.
    Then drag the web_collider object into the boxColliderObj script slot.

    NOTE1:
    Impacts can be enhanced with a relevant Sound Effect (applied to the BreakableWall object as an AudioSource, turn off Play On Awake) and / or a particle effect (consider a basic square texture).
    To use either of these, uncomment the code in this script and add the effect to the relevant script slot.


    BreakableWall.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class BreakableWall : MonoBehaviour {

          public Animator anim;
          //public GameObject ParticleVFX;
          //public AudioSource breakSFX;
          public int hitNum = 2; // how many times the object can be hit before it disappears.
          public GameObject boxColliderObj; // a child collider that can be turned off
          private Renderer myRend;
          private Color defaultColor;

          void Start(){
                anim = gameObject.GetComponentInChildren<Animator>();
                boxColliderObj.SetActive(true);
                myRend = gameObject.GetComponentInChildren<Renderer>();
                defaultColor = myRend.material.color;
          }

          void Update(){
                if (hitNum == 2){
                      anim.SetBool ("wallHalf", false);
                      anim.SetBool ("wallGone", false);
                }
                else if (hitNum == 1){
                      anim.SetBool ("wallHalf", true);
                      anim.SetBool ("wallGone", false);
                }
                else if (hitNum == 0){
                      anim.SetBool ("wallHalf", false);
                      anim.SetBool ("wallGone", true);
                      boxColliderObj.SetActive (false);
                }
          }

          public void wallDamage(){
                // this is the function that the player attack script would access
                if (hitNum > 0)
                      if (!breakSFX.isPlaying){ breakSFX.Play(); }
                      if (hitNum == 2){ anim.SetTrigger ("cutFull"); }
                      else if (hitNum == 1){ anim.SetTrigger ("cutHalf"); }              
                      StartCoroutine(wallHitReturn());
                }
          }

          IEnumerator wallHitReturn(){
                myRend.material.color = new Color(1.0f, 1.0f, 2.5f);
                yield return new WaitForSeconds(0.5f);
                hitNum -= 1;
                myRend.material.color = defaultColor;
                breakSFX.Stop();
          }
    }

    i. Finally, add the following bold-faced content to the PlayerAttackMelee.cs script, so the player can interact with the BreakableWall.cs script. This new content includes one new class variable for the wallLayer LayerMask and a new collider check for the contents of that Layer.

    Once this has been revised, open the Player prefab and in the Inspector set the wallLayer LayerMask rolldown to the layer "BreakableWall".


    Add bold-faced content to PlayerAttackMelee.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PlayerAttackMelee : MonoBehaviour{

          //public Animator animator;
          public Transform attackPt;
          public float attackRange = 0.5f;
          public float attackRate = 2f;
          private float nextAttackTime = 0f;
          public int attackDamage = 40;
          public LayerMask enemyLayers;

         
    public LayerMask wallLayer;

          void Start(){
               //animator = gameObject.GetComponentInChildren<Animator>();
          }

          void Update(){
               if (Time.time >= nextAttackTime){
                      //if (Input.GetKeyDown(KeyCode.Space))
                     if (Input.GetAxis("Attack") > 0){
                            Attack();
                            nextAttackTime = Time.time + 1f / attackRate;
                      }
                }
          }

          void Attack(){
                //animator.SetTrigger ("Melee");
                Collider2D[] hitEnemies = Physics2D.OverlapCircleAll(attackPt.position, attackRange, enemyLayers);
               
                foreach(Collider2D enemy in hitEnemies){
                      Debug.Log("We hit " + enemy.name);
                      enemy.GetComponent<EnemyMeleeDamage>().TakeDamage(attackDamage);
                }

              Collider2D[] hitWalls = Physics2D.OverlapCircleAll(attackPt.position, attackRange, wallLayer);
               
              foreach(Collider2D wall in hitWalls){
                      Debug.Log("We hit " + wall.name);
                      wall.GetComponent<BreakableWall>().wallDamage();
              }

          }

          //NOTE: to help see the attack sphere in editor:
          void OnDrawGizmosSelected(){
               if (attackPt == null) {return;}
                Gizmos.DrawWireSphere(attackPt.position, attackRange);
          }
    }





      PUZZLE 6. Announce an Interactive Space:
    Most platformers are about fast movement through the scenery, but what if you want the character to explore and find hidden messages or items?

    a. Download this "pulse" spritesheet and sound effect, and this press-e message, import each into the Project.

    b. Make the Pulse VFX Prefab:
            1). Slice the pulse spritesheet: Select it in the Project panel, in the Inspector set Sprite Mode = Multiple, hit [Apply], and open the [Sprite Editor] to Slice by Cell Count = 2x2 and [Apply].
            2). Animate the pulse spritesheet: Drag the first sprite into the Hierarchy. In the Inspector name it "pulse_art" and reset Transforms. With pulse_art selected open Window > Animation > Animation, hit [Create], name the clip "pulse_anim", locate in the Media > Animation folder, and add the four frames to the timeline in order: 0, 6, 12, 20 and add the last one again for 30. Close the Animation panel.
            3). Choose a pulse color: in the Inspector set the Sprite Renderer > Color as desired.
            4). Make a parent GO: Create an Empty GO. In the Inpector name it "PulseVFX" and reset Transforms.
    Drag pulse_art onto it to make it a child of PulseVFX.
            5). Make a Prefab: Drag PulseVFX to the Project panel, then delete the original in the Hierarchy.

    c. Make the SecretFinder Object and Script:
            1). Create an Empty Game Object. In the Inpector name it "SecretFinder", reset Transforms.
            2). [Add Component] Physics > BoxCollider2D. Set to isTrigger and Size X and Y = 2.
            3). Create a new C# script, name it "SecretFinder.cs". Add the content below, and drag the script onto the SecretFinder object.

    d. Populate the script slots:
            1). Drag the PulseVFX Prefab from the Project panel into the pulseVFX script slot.
            2). Drag pulse_SFX.wav from the Project panel onto SecretFinder to add an AudioSource Component. Turn off Play On Awake. Drag this AudioSource Component into the pulseSFX script slot.
            3). Drag the message_pressE.png into the Hierarchy onto SecretFinder, to make it a child. Select SecretFinder and drag message_pressE into the msgPressE script slot.

    SecretFinder.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class SecretFinder : MonoBehaviour {

            public GameObject pulseVFX;
            public AudioSource pulseSFX;
            public GameObject msgPressE;
            public bool canPressE =false;

           void Start(){
                  msgPressE.SetActive(false);
            }

           void Update(){
                  if ((canPressE == true) && (Input.GetKeyDown(KeyCode.E))){
                         //Put code here for getting a result, like revealing a message or secret door
                         Debug.Log("You pressed E and get a thing!");
                  }
            }

            void OnTriggerEnter2D(Collider2D other){
                  if (other.gameObject.tag == "Player"){
                         GameObject vfx = Instantiate(pulseVFX, transform.position, Quaternion.identity);
                         StartCoroutine(DestroyVFX(vfx));
                         pulseSFX.Play();
                         msgPressE.SetActive(true);
                         canPressE =true;
                  }
            }

            void OnTriggerExit2D(Collider2D other){
                  if (other.gameObject.tag == "Player"){
                         msgPressE.SetActive(false);
                         canPressE = false;
                  }
            }

           IEnumerator DestroyVFX(GameObject vfx){
                  yield return new WaitForSeconds(0.5f);
                  Destroy(vfx);
           }

          //NOTE: Draw a sphere at transform's position:
           void OnDrawGizmos(){
                  Gizmos.color = Color.yellow;
                  Gizmos.DrawWireSphere(transform.position, 1);
           }
    }

    e. Test the Mechanic: Drag a copy of the SecretFinder PrefabPlace the where the player can interact with the obj () with the

    f. Create a Prefab of your SecretFinder:
    Drag the SecretFinder Object into the Project panel to make it a Prefab. You can now put copies of this Prefab over art in the scene, to make that imag appear to be the source of the effect. Asdjust the collider on these copies to surround the art, to discover a hidden message, item, entrance, etc.

    g. Add Multi-Functionality: Want this script to be used in multiple scenarios? Add bools to the top variables for each use-case, like public bool revealMessage, public bool openSecretDoor, etc. These can be turned on or off in the Inspector when setting up the level. Then, in the Update function under the Key-press condition, add further if-conditions with these bools to route the action as desired.


    h. Set Input Manager for X-Box Controller:
    To use this mechanic with a controller, open the Edit > Project Settings > Input Manager.
    Open Fire4 and change the name to "Secret".
    Set "Positive Button" = "e" and "Alt Positive Button" = "joystick button 2" (the (x) buton on an xbox controller). Finally, adjust the Update() function to this code:
           void Update(){
                  if ((canPressE == true) && (Input.GetButtonDown("Secret"))){
                         //Put code here for getting a result, like revealing a message or secret door
                         Debug.Log("You pressed E and get a thing!");
                  }
            }



      PUZZLE 7. SPRITE MASKS (source):
    Use a Sprite Mask object to obscure or reveal a sprite! No script needed!
    Can be used for a lantern in the darkness, or creatively for x-ray vision,
    to show a player silhouette behind a wall, etc.

    SPRITEMASK EXAMPLE 1: A Light in the Dark:
    This tutorial creates a circle of light around the player in a world of darkness:

    a. Create the Darkness Sprite:
    In your level Hierarchy, create a 2D Object > Sprite > Rectangle.
    In the Inspector, name it "DarknessSprite" and change these settings:
           Transform > Size: X = 6000, Y = 3000.
           SpriteRenderer > Order in layer = 500, so it is WAY in front of everything else.
           Set color to a dark blue.
           Set color alpha to around 200, if you want content to be a little visible in the dark.

    b. Create the Sprite Mask:
    Create a GameObject > 2D Object > SpriteMask.
    Inspector: Find SpriteMask > Sprite and drag in a sprite for the shape you want to be visible in the darkness (a custom PNG or the default knob).

    c. Set the Darkness to interact with the mask:
    Re-select the DarknessSprite.
    In the Inspector under Sprite Renderer find Mask Interaction.
    Choose "Visible Outside Mask" so outside the mask will show dark!

    Hit [Play] and move the sprite mask to obscure or reveal content!

    d. Add to a Player:
    To add the SpriteMask to the Player, make it a child of the Player prefab (centered on a lantern, for example).

    To turn a SpriteMask on / off (if power runs out, for example):
            1. make a public variable for the SpriteMask object and use [SpriteMaskObjectName].SetActive(true);
            2. or add this line to the script that is meant to disable it, like the GameHandler
                    player.GetComponentInChildren<SpriteMask>().enabled = false;



    SPRITEMASK EXAMPLE 2: X-Ray Vision:
    This tutorial allows the player to "see inside" objects. This can also be used for revealing part or all of the player as an outline when their character is partially or fully behind something too big / obscuring.

    a. Create PNGs for two elements:
            an object to be obscured (like a box)
            and the object to be revealed (like a skull hidden behind or inside the box).
    Import each into the Project > Assets folder, and drag both into the Hierarchy.

    b. Create a GameObject > 2D Object > Sprite Mask.
    In the Inspector Choose a sprite image (a custom PNG you make in Photoshop, or the default Unity knob circle).

    c. Select one of the art PNGs.
    In the Inspector under Sprite Renderer find Mask Interaction
    Choose either "Visible Inside Mask" or "Visible Outside Mask" (a box should be Visible Outside, and the contents Visible Inside, in order for the Mask to be used as X-Ray vision to look inside the box)

    That is it. Hit [Play] and move the sprite mask to obscure or reveal content!



      PUZZLE 8. Enemy Zapper:
    Want to create an electrified fence to kill enemies?

    The following C# Script can be applied to a GameObject or a Tilemap layer with a collider set to isTrigger.
    Drag into the boomVFX Script slot: a Prefab with an animated spritesheet VFX.
    Make sure all enemies have their layer set to Enemy.
    In the script slot set LayerMark "Enemy Layer" to "Enemy".

    EnemyZapper.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class EnemyZapper : MonoBehaviour{
           public LayerMask enemyLayer; // this script only takes one Layer
           public GameObject boomVFX;

           private Vector3Int previous;

           private void OnTriggerEnter2D(Collider2D col){
                  float layerNumber = Mathf.Log(enemyLayer.value, 2);
                  if (col.gameObject.layer == layerNumber){
                         Vector2 SpawnBoomPos = col.gameObject.transform.position;
                         GameObject boom = Instantiate (boomVFX, SpawnBoomPos, Quaternion.identity);
                         StartCoroutine(DestroyVFX(boom));
                         Destroy(col.gameObject);
                  }
           }

           IEnumerator DestroyVFX(GameObject thisBoom){
                  yield return new WaitForSeconds(0.5f);
                  Destroy(thisBoom);
           }
    }

     




     
    [2D]: NPC ENEMIES
    NPC #1: Follow Behavior, Knockback, and Enemy Health  
    NPC #2: Follow / Shoot and Projectile
    NPC #3: Patrol a. Back-&-Forth   |  b. Points Sequence & Attack
                                      c. Random Points   |  d. Random Space
    NPC #4: Top-Down Death Gaze (for stealth games) or Spot Player

    NPC #5: Manage NPC behavior through a Mecanim State Machine
    (with thanks to Jo Chung. Click to download PowerPoint pptx)

      OPTION 1. Enemy that follows the player and damages the Player on contact:
    This enemy is ideally displayed using a spritesheet that includes the following animations (1-4 sprites each):
    idle, chasing the player, attack, getHurt, and a death.

    a. Create an EmptyGO named "Enemy_MoveHit". Reset Transforms.

    b. [Add Component] Physics2D > Rigidbody2D (turn on Constraint > Freeze Rotation Z) and BoxCollider2D.

    c. Add a .PNG character sprite, make it a child of Enemy_MoveHit. The scripts below can access animations for enemy_idle, enemy_walk, enemy_attack, enemy_getHit, enemy_KO.

    d. Create a new C# script, name it "EnemyMoveHit.cs", apply to Enemy_MoveHit.
    This script allows the enemy to "see" when the Player is close enough (within AttackRange) and then attack.


    EnemyMoveHit.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class EnemyMoveHit : MonoBehaviour {

           public Animator anim;
           public Rigidbody2D rb2D;
           public float speed = 4f;
           private Transform target;
           public int damage = 10;

           public int EnemyLives = 3;
           private GameHandler gameHandler;

           public float attackRange = 10;
           public bool isAttacking = false;
           private float scaleX;

           void Start () {
                  anim = GetComponentInChildren<Animator> ();
                  rb2D = GetComponent<Rigidbody2D> ();
                  scaleX = gameObject.transform.localScale.x;

                  if (GameObject.FindGameObjectWithTag ("Player") != null) {
                         target = GameObject.FindGameObjectWithTag ("Player").GetComponent<Transform> ();
                  }

                  if (GameObject.FindWithTag ("GameHandler") != null) {
                      gameHandler = GameObject.FindWithTag ("GameHandler").GetComponent<GameHandler> ();
                  }
           }

           void Update () {
                  float DistToPlayer = Vector3.Distance(transform.position, target.position);

                  if ((target != null) && (DistToPlayer <= attackRange)){
                         transform.position = Vector2.MoveTowards (transform.position, target.position, speed * Time.deltaTime);
                        //anim.SetBool("Walk", true);
                        //flip enemy to face player direction. Wrong direction? Swap the * -1.
                        if (target.position.x > gameObject.transform.position.x){
                                       gameObject.transform.localScale = new Vector2(scaleX, gameObject.transform.localScale.y);
                        } else {
                                        gameObject.transform.localScale = new Vector2(scaleX * -1, gameObject.transform.localScale.y);
                        }
                  }
                   //else { anim.SetBool("Walk", false);}
           }

           public void OnCollisionEnter2D(Collision2D other){
                  if (other.gameObject.tag == "Player") {
                         isAttacking = true;
                         //anim.SetBool("Attack", true);
                         gameHandler.playerGetHit(damage);
                  }
           }

           public void OnCollisionExit2D(Collision2D other){
                  if (other.gameObject.tag == "Player") {
                         isAttacking = false;
                         //anim.SetBool("Attack", false);
                  }
           }

           //DISPLAY the range of enemy's attack when selected in the Editor
           void OnDrawGizmosSelected(){
                  Gizmos.DrawWireSphere(transform.position, attackRange);
           }
    }


     Knock Back:
    e. Want the player to get knocked back when getting hit by the Enemy?
    Add the two parts below to the EnemyMoveHit script (This method adds force to the player, pushing them back without teleporting) AND the new script below to the Player:


    Add to EnemyMoveHit.cs script (in a top-down game):
    //1. Near top, add this line to the Class variables:
           public float knockBackForce = 20f;


    //2. Replace OnCollisionEnter2D() with this version, to include knockback:
           public void OnCollisionEnter2D(Collision2D other){
                  if (other.gameObject.tag == "Player") {
                         isAttacking = true;
                         //anim.SetBool("Attack", true);
                         gameHandler.playerGetHit(damage);
                         //rend.material.color = new Color(2.4f, 0.9f, 0.9f, 0.5f);
                         //StartCoroutine(HitEnemy());

                         //Tell the player to STOP getting knocked back before getting knocked back:
                         other.gameObject.GetComponent<Player_EndKnockBack>().EndKnockBack();
                         //Add force to the player, pushing them back without teleporting:
                        Rigidbody2D pushRB = other.gameObject.GetComponent<Rigidbody2D>();
                        
    Vector2 moveDirectionPush = rb2D.transform.position - other.transform.position;
                        pushRB.AddForce(moveDirectionPush.normalized * knockBackForce * - 1f, ForceMode2D.Impulse);

                  }
           }


    The knockback is started by the enemy, but must be ended by the Player.
    This is in case the enemy is destroyed in the short time between the start and end of the knockback (or the player will keep moving backwards!).

    Create a new C# script called "Player_EndKnockBack.cs" and add the content below.
    Add this new script to the player, and make sure the top level of the Player has the tag "Player":


    Player_EndKnockBack.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class Player_EndKnockBack : MonoBehaviour {

           private Rigidbody2D playerRB;

           public void EndKnockBack(){
                  playerRB = gameObject.GetComponent<Rigidbody2D>();
                  StartCoroutine(EndKnockBack(playerRB));
           }

           IEnumerator EndKnockBack(Rigidbody2D myRB){
                  yield return new WaitForSeconds(0.2f);
                  myRB.velocity= new Vector3(0,0,0);
           }
    }


    NOTE: The knockback above assumes a top-down game.
    In a platfomer game, we want to knock the player back just on the x-axis.
    Try this method which teleports the player away from the enemy (NOTE: can accidentally teleport through a wall):

    Add to EnemyMoveHit.cs script (in a Platformer game):
    //1. Near top, add this line to the Class variables:
           public float attackPush = 3.5f;


    //2. Replace OnCollisionEnter2D() with this version, to include knockback:
           public void OnCollisionEnter2D(Collision2D other){
                  if (other.gameObject.tag == "Player") {
                         isAttacking = true;
                         //anim.SetBool("Attack", true);
                         gameHandler.playerGetHit(damage);
                         //rend.material.color = new Color(2.4f, 0.9f, 0.9f, 0.5f);
                         //StartCoroutine(HitEnemy());

                         //This method teleports the player away from the enemy; can teleport through a wall
                         float pushBack = 0f;
                         if (other.gameObject.transform.position.x > gameObject.transform.position.x){
                                pushBack = 3f;
                         }
                         else {
                                pushBack = -3f;
                         }
                         other.gameObject.transform.position = new Vector3(transform.position.x + pushBack, transform.position.y + 1, 0);
                  }
           }



     Enemy Health:
    f. Create a new C# script, name it "EnemyMeleeDamage.cs", apply to Enemy_MoveHit.
    This script allows the enemy to be destroyed when its health is depleted. It also drops loot (instantiates a prefab). Once aplied, drag a Pick-Up object into the healthLoot slot.


    EnemyMeleeDamage.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class EnemyMeleeDamage : MonoBehaviour {
           private Renderer rend;
           public Animator anim;
           public GameObject healthLoot;
           public int maxHealth = 100;
           public int currentHealth;

           void Start(){
                  rend = GetComponentInChildren<Renderer> ();
                  anim = GetComponentInChildren<Animator> ();
                  currentHealth = maxHealth;
           }

           public void TakeDamage(int damage){
                  currentHealth -= damage;
                  //rend.material.color = new Color(2.4f, 0.9f, 0.9f, 1f);
                  //StartCoroutine(ResetColor());
                  //anim.SetTrigger ("Hurt");
                  if (currentHealth <= 0){
                         Die();
                  }
           }

           void Die(){
                  Instantiate (healthLoot, transform.position, Quaternion.identity);
                  //anim.SetBool ("isDead", true);
                  GetComponent<Collider2D>().enabled = false;
                  StartCoroutine(Death());
           }

           IEnumerator Death(){
                  yield return new WaitForSeconds(0.5f);
                  Debug.Log("You Killed a baddie. You deserve loot!");
                  Destroy(gameObject);
           }

           IEnumerator ResetColor(){
                  yield return new WaitForSeconds(0.5f);
                  rend.material.color = Color.white;
           }
    }




    Do you have a ghost character that should follow the player but also pass through the walls/collider?
    Add this script in addition to EnemyMoveHit.cs.

    IgnoreCollider.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class IgnoreCollider : MonoBehaviour {

           public GameObject enemy;
           public string TagToIgnore = "Ignored";

           void OnCollisionEnter2D (Collision2D collision){
                  if (collision.gameObject.tag == TagToIgnore){
                         Physics2D.IgnoreCollision(collision.gameObject.GetComponent<Collider2D>(), GetComponent<Collider2D>());
                  }
           }
    }



    ENEMY SHOOTER

     OPTION 2. ENEMY SHOOTER:
    An NPC that follows the player at a distance and shoots at the Player:

    a. Create an Empty Game Object. In the Inspector name it "Enemy_FollowShoot", reset Transforms, add tag = "enemyShooter" (so its own bullets won't effect it) and add layer = "Enemies" (so the Player can jump on it, if desired).

    b. [Add Components] Physics > BoxCollider2D and Rigidbody2D (set Constraints to Freeze Z rotation).

    c. Drag the enemy character art from the project panel into the Hierarchy, and drag the sprite onto Enemy_FollowShoot to make it a child. In the Inspector name it "enemyShoot_art".

    d. On Enemy_FollowShoot adjust the BoxCollider2D to surround the enemyShoot_art sprite.

    e. Create new C# script, name it "EnemyMoveShoot.cs". Add the content below, Save, and apply to Enemy_FollowShoot. This script allows the enemy to approach the player but stay at a distance form which to shoot at the Player.

    EnemyMoveShoot.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class EnemyMoveShoot : MonoBehaviour {

          //public Animator anim;
           public float speed = 2f;
           public float stoppingDistance = 4f; // when enemy stops moving towards player
           public float retreatDistance = 3f; // when enemy moves away from approaching player
           private float timeBtwShots;
           public float startTimeBtwShots = 2;
           public GameObject projectile;

           private Rigidbody2D rb;
           private Transform player;
           private Vector2 PlayerVect;

           public int EnemyLives = 30;
           private Renderer rend;
           //private GameHandler gameHandler;

           public float attackRange = 10;
           public bool isAttacking = false;
           private float scaleX;

           void Start () {
                  Physics2D.queriesStartInColliders = false;
                  scaleX = gameObject.transform.localScale.x;

                  rb = GetComponent<Rigidbody2D> ();
                  player = GameObject.FindGameObjectWithTag("Player").transform;
                  PlayerVect = player.transform.position;

                  timeBtwShots = startTimeBtwShots;

                  rend = GetComponentInChildren<Renderer> ();
                  //anim = GetComponentInChildren<Animator> ();

                  //if (GameObject.FindWithTag ("GameHandler") != null) {
                  // gameHander = GameObject.FindWithTag ("GameHandler").GetComponent<GameHandler> ();
                  //}
           }

           void Update () {
                  float DistToPlayer = Vector3.Distance(transform.position, player.position);
                  if ((player != null) && (DistToPlayer <= attackRange)) {
                         // approach player
                         if (Vector2.Distance (transform.position, player.position) > stoppingDistance) {
                                transform.position = Vector2.MoveTowards (transform.position, player.position, speed * Time.deltaTime);
                                if (isAttacking == false) {
                                       //anim.SetBool("Walk", true);
                                }
                                //Vector2 lookDir = PlayerVect - rb.position;
                                //float angle = Mathf.Atan2(lookDir.y, lookDir.x) * Mathf.Rad2Deg -90f;
                                //rb.rotation = angle;
                         }
                         // stop moving
                         else if (Vector2.Distance (transform.position, player.position) < stoppingDistance && Vector2.Distance (transform.position, player.position) > retreatDistance) {
                                transform.position = this.transform.position;
                                //anim.SetBool("Walk", false);
                         }

                         // retreat from player
                         else if (Vector2.Distance (transform.position, player.position) < retreatDistance) {
                                transform.position = Vector2.MoveTowards (transform.position, player.position, -speed * Time.deltaTime);
                                if (isAttacking == false) {
                                       //anim.SetBool("Walk", true);
                                }

                         }

                         //Flip enemy to face player direction. Wrong direction? Swap the * -1.
                         if (player.position.x > gameObject.transform.position.x){
                                gameObject.transform.localScale = new Vector2(scaleX, gameObject.transform.localScale.y);
                        } else {
                                 gameObject.transform.localScale = new Vector2(scaleX * -1, gameObject.transform.localScale.y);
                         }

                         //Timer for shooting projectiles
                         if (timeBtwShots <= 0) {
                                isAttacking = true;
                                //anim.SetTrigger("Attack");
                                Instantiate (projectile, transform.position, Quaternion.identity);
                                timeBtwShots = startTimeBtwShots;
                         } else {
                                timeBtwShots -= Time.deltaTime;
                                isAttacking = false;
                         }
                  }
           }

           void OnCollisionEnter2D(Collision2D collision){
                  //if (collision.gameObject.tag == "bullet") {
                  // EnemyLives -= 1;
                  // StopCoroutine("HitEnemy");
                  // StartCoroutine("HitEnemy");
                  //}
                  if (collision.gameObject.tag == "Player") {
                         EnemyLives -= 2;
                         StopCoroutine("HitEnemy");
                         StartCoroutine("HitEnemy");
                  }
           }

           IEnumerator HitEnemy(){
                  // color values are R, G, B, and alpha, each divided by 100
                  rend.material.color = new Color(2.4f, 0.9f, 0.9f, 0.5f);
                  if (EnemyLives < 1){
                         //gameControllerObj.AddScore (5);
                         Destroy(gameObject);
                  }
                  else yield return new WaitForSeconds(0.5f);
                  rend.material.color = Color.white;
           }

          //DISPLAY the range of enemy's attack when selected in the Editor
           void OnDrawGizmosSelected(){
                  Gizmos.DrawWireSphere(transform.position, attackRange);
           }
    }

    Now we need the projectile that the shooting enemy will fire!


     Enemy Projectile:
    a. Create an Empty Game Object. In the Inspector name it "EnemyProjectile", reset Transforms.

    b. [Add Components] Physics > BoxCollider2D (turn on isTrigger)
    and Physics > Rigidbody2D (set Gravity Scale = 0, set Constraints to Freeze Z rotation).

    c. Drag the enemy projectile art from the project panel into the Hierarchy, and drag the sprite onto EnemyProjectile to make it a child. In the Inspector name it "enemyProjectile_art".

    d. On EnemyProjectile adjust the BoxCollider2D to surround the enemyprojectile_art sprite.

    e. Create new C# script, name it "EnemyProjectile.cs". Add the content below, Save, and apply to EnemyProjectile.

    f. Drag EnemyProjectile into the Project panel to make it a Prefab. Delete the original in the Hierarchy.
    Select the Enemy_FollowShoot object and drag the EnemyProjectile Prefab into the projectile script slot.

    EnemyProjectile.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class EnemyProjectile : MonoBehaviour {

           public GameHandler gameHandlerObj;
           public int damage =1;
           public float speed = 10f;
           private Transform playerTrans;
           private Vector2 target;
           public GameObject hitEffectAnim;
           public float SelfDestructTime = 2.0f;

           void Start() {
                 //NOTE: transform gets location, but we need Vector2 for direction, so we can use MoveTowards.
                 playerTrans = GameObject.FindGameObjectWithTag("Player").transform;
                 target = new Vector2(playerTrans.position.x, playerTrans.position.y);

                 if (gameHandlerObj == null){
                   gameHandlerObj = GameObject.FindWithTag("GameHandler").GetComponent<GameHandler>();
                 }
                 StartCoroutine(selfDestruct());

                 //code for trajectory (moves target way beyond player position):
                 Vector2 startPos = transform.position;
                 float distance = Vector2.Distance(startPos, target);
                 distance = distance * (10f);
                 Vector2 difference = target - startPos;
                 difference = difference.normalized * distance;
                 target = (startPos + difference);
           }

           void Update () {
                  transform.position = Vector2.MoveTowards (transform.position, target, speed * Time.deltaTime);
           }

           //if the bullet hits a collider, play the explosion animation, then destroy the effect and the bullet
           void OnTriggerEnter2D(Collider2D collision){
                  if (collision.gameObject.tag == "Player") {
                         gameHandlerObj.playerGetHit(damage);
                  }
                  if (collision.gameObject.tag != "enemyShooter") {
                         GameObject animEffect = Instantiate (hitEffectAnim, transform.position, Quaternion.identity);
                         Destroy (animEffect, 0.5f);
                         Destroy (gameObject);
                  }
           }

           IEnumerator selfDestruct(){
                  yield return new WaitForSeconds(SelfDestructTime);
                  Destroy (gameObject);
           }
    }


    CREATE THE EXPLOSION EFFECT:
    g. Create a spritesheet for the explosion / impact effect, like this one:
    In Unity import the asset. In the Inspector set the following:
               Sprite Mode = Multiple
               Pixels Per Unit = 16
               Filter Mode = Point (no filter)
               Compression = None
    Hit [Apply]. Open the [Sprite Editor], choose slice by cell count, set rows/columns, hit [Slice] & [Apply].

    h. Drag the first frame into Hierarchy.
         In the Inspector name it "boom".
         Reset Transforms.
         Drag it in the Project > Assets folder to make it a Prefab.
         Delete it for the scene.
         Drag the Prefab from the Assest folder onto the enemyProjectile object into the hitEffectAnim script slot.



    PATROLLING ENEMIES


      OPTION 3a. Back-and-Forth Patrolling Enemy (video sources: 01 | 02):
    SUMMARY: Here is a simple side-to-side patrolling enemy, that stops when it hits the end of a platform.

    a. Create a Platform with a Collider and a Layer named Ground:
    Build the visuals and collider/s:
         OPTION A: RightClick the Hierarchy to create a 2D Object > Sprite > Rectangle.
    [Add Component] Physics2D > BoxCollider2D. Name it "Platform"
         OPTION B: Create a Tilemap with TilemapCollider2D / CompositeCollider2D.
    Create and apply a new "Ground" Layer:
         Inspector upper-right: hit Layer > Add Layer. Choose a slot, name it "Ground".
         In the Hierarchy re-select the platform object and set the Layer = Ground.
    NOTE: If when you hit play your enemy is just flipping back and forth wthout patrolling, you likely forgot to add the Ground layer to the ground object!

    b. Create the Enemy Parent object:
    Create an Empty Game Object.
    In the Inspector name it "EnemyPatrol" and reset Transforms.
    [Add Component] Physics2D > BoxColider2D and Rigidbody2D (set Constraint > Freeze Z Rotation).

    c. Add the Enemy Art:
    Create and import an enemy art sprite into the Assets folder.
    Set Inspector properties and drag into Hierarchy, onto EnemyPatrol to make it a child.
    In the Inspector name it "enemyPatrol_art" and set Order In Layer = 90.
    Select EnemyPatrol and adjust the BoxColider2D to surround art, starting at ankle.

    d. Add the GroundCheck object to act as the raytrace origin point (will look straight down for the platform edge):
    RightClick EnemyPatrol to create another Empty Game Objectas a child.
    In the Inspector name it "GroundCheck" and reset Transforms.
    Click the Inspector upper-left icon to choose a diamond shape for Scene view visibility.
    Move it slightly "in front" of the character art (on the side the art faces).

    e. Create a new C# script with the content below, "EnemyPatrolHit.cs". Save.
    In Unity add to EnemyPatrol. This script makes the enemy move back and forth on a platform, using raytracing to "see" platform ends in order to turn around.
    In Inspector: Drag the GroundCheck Game Object into the groundCheck script slot.
    Set the GroundLayers rolldown to Ground.

    EnemyPatrolHit.cs script (on floating platforms):
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class EnemyPatrolHit : MonoBehaviour {

           public float speed = 2f;
           private Rigidbody2D rb;
           //private Animator anim;
           public LayerMask groundLayers;
           public Transform groundCheck;
           bool faceRight = true;
           RaycastHit2D hit;

           public int damage = 10;
           private GameHandler gameHandler;

           void Start(){
                  rb = GetComponent<Rigidbody2D>();
                  //anim.SetBool("Walk", true);
                  if (GameObject.FindWithTag ("GameHandler") != null) {
                      gameHandler = GameObject.FindWithTag ("GameHandler").GetComponent<GameHandler> ();
                  }
           }

           void Update(){
                  hit = Physics2D.Raycast(groundCheck.position, -transform.up, 1f, groundLayers);
                  Debug.DrawLine(groundCheck.position, hit.point, Color.green);
           }

           void FixedUpdate(){
                  if (hit.collider != false){
                         if (faceRight){
                                rb.velocity = new Vector2(speed, rb.velocity.y);
                         } else {
                                rb.velocity = new Vector2(-speed, rb.velocity.y);
                         }
                  } else {
                         faceRight = !faceRight;
                         transform.localScale = new Vector3(-transform.localScale.x, 1f, 1f);
                  }
           }

           public void OnCollisionEnter2D(Collision2D other){
                  if (other.gameObject.tag == "Player") {
                         //anim.SetBool("Attack", true);
                         gameHandler.playerGetHit(damage);
                         //rend.material.color = new Color(2.4f, 0.9f, 0.9f, 0.5f);
                         //StartCoroutine(HitEnemy());
                  }
           }

           public void OnCollisionExit2D(Collision2D other){
                  if (other.gameObject.tag == "Player") {
                         //anim.SetBool("Attack", false);
                  }
           }
    }

    //Vector2 groundCheck2 = new Vector2(groundCheck.position.x, groundCheck.position.y);
    //hit = Physics2D.Raycast(groundCheck2, new Vector2(0, -1), rayLength, groundLayers);
    //Debug.DrawRay(groundCheck.position, new Vector2(0, -1), Color.green);


    ENEMY CAN ALSO TURN AFTER HITTING A WALL:
    To adapt this Enemy for use on floating platforms OR on platorfms bounded by walls (so it switched irections when hitting an empty space OR wall):
    1. Make sure that the GroundCheck Position Y =0, so it is straight out in front of the player. to be used for both ground and wall checks.
    2. Add a new Tilemap layer TM_walls for all walls, ad Layer ID "walls" and apply to this Tilemap.
    3. Use this adjusted script. In Inspector, set wallCheck layer slot to the new Layer ID "walls":


    EnemyPatrolHit.cs script (on floating platforms AND spaces bounded by walls):
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class EnemyPatrolHit : MonoBehaviour {

           public float speed = 2f;
           private Rigidbody2D rb;
           //private Animator anim;
           public LayerMask groundLayer;
           public LayerMask wallLayer;
           public Transform groundCheck;
           bool faceRight = true;
           RaycastHit2D hitDwn;
           RaycastHit2D hitFwd;

           public float raylength = 2f;

           public int damage = 10;
           private GameHandler gameHandler;

           void Start(){
                  rb = GetComponent<Rigidbody2D>();
                  //anim.SetBool("Walk", true);
                  if (GameObject.FindWithTag ("GameHandler") != null) {
                      gameHandler = GameObject.FindWithTag ("GameHandler").GetComponent<GameHandler> ();
                  }
           }

        void Update(){
          hitDwn = Physics2D.Raycast(groundCheck.position, -transform.up, raylength, groundLayer);
          hitFwd = Physics2D.Raycast(groundCheck.position, -transform.up, raylength/2, wallLayer);

        }

           void FixedUpdate(){
                  if (hitDwn.collider != false){
                         if (faceRight){
                                rb.velocity = new Vector2(speed, rb.velocity.y);
                         } else {
                                rb.velocity = new Vector2(-speed, rb.velocity.y);
                         }
                  } else {
                         faceRight = !faceRight;
                         transform.localScale = new Vector3(-transform.localScale.x, 1f, 1f);
                  }
     
                  // wall turning:
                  if (hitFwd.collider != false){
                         Debug.Log("I hit a wall");
                         faceRight = !faceRight;
                         transform.localScale = new Vector3(-transform.localScale.x, 1f, 1f);
                  }

           }

           public void OnCollisionEnter2D(Collision2D other){
                  if (other.gameObject.tag == "Player") {
                         //anim.SetBool("Attack", true);
                         gameHandler.playerGetHit(damage);
                         //rend.material.color = new Color(2.4f, 0.9f, 0.9f, 0.5f);
                         //StartCoroutine(HitEnemy());
                  }
           }

           public void OnCollisionExit2D(Collision2D other){
                  if (other.gameObject.tag == "Player") {
                         //anim.SetBool("Attack", false);
                  }
           }
    }



     OPTION 3b. Enemy Patrol Sequence: Multiple Points
    SUMMARY: An enemy that follows a set path between waypoints until it sees the player, then attacks.
    This is typically done with systems like A-star (A*) or Breadth First Search (BFS), but the enemy can also simply move between a sequence of patrol targets.


    a. Create an Empty GameObject. In the Inspector name it "Enemy_PatrolSequence". Reset Transforms.
    [Add Component] Physics > BoxCollider2D and RigidBody2D (Gravity Scale = 0). Add art Sprite as a child.

    b. Create a new C# script called "NPC_PatrolSequencePoints.cs" and apply to Enemy_PatrolSequence. Add the content below to the script and save.

    c. In the Inpector Choose the moveSpots[] array number to add script slots.

    d. In the Hierarchy create another Empty GameObject, name it "patrolPoint" and in the Inspector upper-left icon set the Scene-view visual to a diamond.

    e. Move and duplicate the patrolPoints to represent all locations you want the NPC to patrol.
    Drag the right-most patrolPoint into the first moveSpot script slot, then the rest in order (left-most is last).

    Hit [Play]: the NPC should move forward from point to point, and then back. Turn off Play to resume editing.

    f. To complete the enemy, make a Prefab: new Empty GameObject to act as the ultimate parent for this enemy system. In the Inspector name it "Enemy_PATROLSYSTEM" and reset Transforms.
    Drag EnemyPatrolSequence and all patrolPoints onto Enemy_PATROLSYSTEM to make them children, then drag Enemy_PATROLSYSTEM into the Project panel to make it a Prefab, so it can be duplicated and used in other scenes.


    NPC_PatrolSequencePoints.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class NPC_PatrolSequencePoints : MonoBehaviour {
           // private Animator anim;
           public float speed = 10f;
           private float waitTime;
           public float startWaitTime = 2f;

           public Transform[] moveSpots;
           public int startSpot = 0;
           public bool moveForward = true;

           // Turning
           private int nextSpot;
           private int previousSpot;
           public bool faceRight = false;

           void Start(){
                  waitTime = startWaitTime;
                  nextSpot = startSpot;
                  //anim = gameObject.GetComponentInChildren<Animator>();
           }

           void Update(){
                  transform.position = Vector2.MoveTowards(transform.position, moveSpots[nextSpot].position, speed * Time.deltaTime);

                  if (Vector2.Distance(transform.position, moveSpots[nextSpot].position) < 0.2f){
                         if (waitTime <= 0){
                                if (moveForward == true){ previousSpot = nextSpot; nextSpot += 1; }
                                else if (moveForward == false){ previousSpot = nextSpot; nextSpot -= 1; }

                                waitTime = startWaitTime;
                         } else {
                                waitTime -= Time.deltaTime;
                         }
                  }

                  //switch movement direction
                  if (nextSpot == 0) {moveForward = true; }
                  else if (nextSpot == (moveSpots.Length -1)) { moveForward = false; }

                  //turning the enemy
                  if (previousSpot < 0){ previousSpot = moveSpots.Length -1; }
                  else if (previousSpot > moveSpots.Length -1){ previousSpot = 0; }

                  if ((previousSpot == 0) && (faceRight)){ NPCTurn(); }
                  else if ((previousSpot == (moveSpots.Length -1)) && (!faceRight)) { NPCTurn(); }
                  // NOTE1: If faceRight does not change, try reversing !faceRight, above
                  // NOTE2: If NPC faces the wrong direction as it moves, set the sprite Scale X = -1.
           }

           private void NPCTurn(){
                  // NOTE: Switch player facing label (avoids constant turning)
                  faceRight = !faceRight;

                  // NOTE: Multiply player's x local scale by -1.
                  Vector3 theScale = transform.localScale;
                  theScale.x *= -1;
                  transform.localScale = theScale;
           }

    }

    NOTE: The facing direction in this script assumes the location nodes are arranged from Right to Left, ideal for a platformer game. For a more robust script that allows a zigzag or circular pattern (more useful in a Top-Down game), try turning the player when:
           ((nextSpot.position.x < previousSpot) && (previousSpot.positon.x > [the spot before previousSpot]))
           ((nextSpot.position.x > previousSpot) && (previousSpot.positon.x < [the spot before previousSpot]))



     Want your patrolling enemy to hurt the player?
    A moveHit script can be modified to disable the patrol script and chase the player if the player gets close enough. Alternatively, this simple script will cause the patrolling enemy to hurt the player if the player gets near their front.

    1. Create an Empty Game Object as a child of the enemy, name it "AttackPoint".Position it in front of the player.

    2. Add the C# script below to the player and drag Attack Point into the script slot.

    3. Adjust Attack Range, Damage Range, and Damage as desired, but keep Damage Range > Attack Range.

    EnemyBite.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class EnemyBite : MonoBehaviour {
           // private Animator anim;
           private GameHandler gameHandler;

           private Transform player;
           public Transform AttackPoint;
           public float attackRange = 5f;
           public float damageRange = 7f;
           //public LayerMask playerLayer;

           public int damage = 10;
           private float distanceToMouth;
           public float timeToNextAttack = 2f;
           public bool canAttack = true;


           void Start(){
                  gameHandler = GameObject.FindWithTag ("GameHandler").GetComponent<GameHandler>();
                  //anim = GetComponentInChildren<Animator>();
                  player = GameObject.FindWithTag("Player").transform;
           }

           void Update(){
                  distanceToMouth = Vector3.Distance(player.position, AttackPoint.position);
                  if ((distanceToMouth < attackRange) && (canAttack)){
                         Attack();
                         StartCoroutine(AttackDelay());
                  }
           }

           void Attack(){
                  anim.SetTrigger ("Attack");
                  Debug.Log("I am biting the player!");
                  if (distanceToMouth < damageRange){
                         gameHandler.playerGetHit(damage);
                  }

                  // Collider2D[] hitPlayer = Physics2D.OverlapCircleAll(AttackPoint.position, damageRange, playerLayer);
                  // foreach(Collider2D player in hitPlayer){
                         // Debug.Log("We hit " + player.name);
                         // gameHandler.playerGetHit(damage);
                  // }
           }

           IEnumerator AttackDelay(){
                  canAttack = false;
                  yield return new WaitForSeconds(timeToNextAttack);
                  canAttack = true;
           }

           //NOTE: to help see the attack sphere in editor:
           void OnDrawGizmos(){
                  Gizmos.DrawWireSphere(AttackPoint.position, attackRange);
                  Gizmos.DrawWireSphere(AttackPoint.position, damageRange);
           }
    }






    OPTION 3c. Enemy Patrol Randomly Move Between Points (source, part 1)
    a. Create an Empty Game Object called "NPC". In the Inspector reset Transforms.
    b. Add a BoxCollider2D and a RigidBody2D (Constraint > Freeze Z rotation, change Dynamic to Kinematic).
    c. Drag your Sprite art into the Hierarchy. Name it "NPC_art" and drag it onto NPC to make it a child.
    d. Create a new C# script called "NPC_PatrolRandomPoints.cs".
    e. In your script editor add the content below, Save, and in Unity apply to NPC.

    For the location points:
    f. Create two new Empty GOs to act as location points. Name them "movSpot1A" and "moveSpot1B".
    g. Open the Inspector and select NPC.
    h. Under moveSpots hit [+] twice to add two Transforms variable slots.
    i. Fast-drag the two location point GOs into these new Transform variables slots.
    Hit [Play]: the NPC will move randomly between the points! Turn off Play to continue editing.


    ANIMATION NOTE: The script below is desigend to include animation on the enemy: a default idle, a walk animation, and an attacking animation. Un-comment the anim lines to access them.

    NPC_PatrolRandomPoints.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class NPC_PatrolRandomPoints : MonoBehaviour {

           public float speed = 10f;
           private float waitTime;
           public float startWaitTime = 2f;

           public Transform[] moveSpots;
           private int randomSpot;

           //public Animator anim;
           private Vector3 theScale;
           private Renderer rend;
           private GameHandler gameHandler;
           public bool LeftRightCreature = true;
           public bool isAttacking = false;
           public int damage = 10;

           void Start(){
                  waitTime = startWaitTime;
                  randomSpot = Random.Range(0, moveSpots.Length);
                  //anim = GetComponentInChildren<Animator>();
                  theScale = transform.localScale;
                  rend = GetComponentInChildren<Renderer>();
                  if (GameObject.FindWithTag ("GameHandler") != null) {
                      gameHandler = GameObject.FindWithTag ("GameHandler").GetComponent<GameHandler> ();
                  }
           }

           void Update(){
                  //move enemy to the destination (appears to stay put if there is no destination)
                  transform.position = Vector2.MoveTowards(transform.position, moveSpots[randomSpot].position, speed * Time.deltaTime);

                  if (Vector2.Distance(transform.position, moveSpots[randomSpot].position) < 0.2f){
                         if (waitTime <= 0){
                                //assign new destination
                                randomSpot = Random.Range(0, moveSpots.Length);
                                waitTime = startWaitTime;
                                //anim.SetBool("Walk", true);
                         } else {
                                //no new destination (stop moving) until time runs out
                                waitTime -= Time.deltaTime;
                                //anim.SetBool("Walk", false);
                         }
                  }

                  //flip display of enemies that move left-to-right, based on movement direction vector
                  if (LeftRightCreature== true){
                         //capture direction
                         Vector3 posA = transform.position;
                         Vector3 posB = moveSpots[randomSpot].position;
                        //Destination - Origin
                         Vector3 dir = (posB - posA).normalized;
                         Debug.Log("Enemy direction: " + dir);

                         float moveDirection = dir.x;
                         if (moveDirection < 0){
                                transform.localScale = theScale * -1;
                         }
                         else if (moveDirection > 0){
                                transform.localScale = theScale;
                         }
                         else { }
                  }
           }

           //Injure the Player on contact:
           public void OnCollisionEnter2D(Collision2D collision){
                  if (collision.gameObject.tag == "Player") {
                         isAttacking = true;
                         //anim.SetBool("Attack", true);
                         gameHandler.playerGetHit(damage);
                         //rend.material.color = new Color(2.4f, 0.9f, 0.9f, 0.5f);
                         //StartCoroutine(HitEnemy());
                  }
           }

           public void OnCollisionExit2D(Collision2D collision){
                  if (collision.gameObject.tag == "Player") {
                         isAttacking = false;
                         //anim.SetBool("Attack", false);
                  }
           }

           IEnumerator HitEnemy(){
                  yield return new WaitForSeconds(0.5f);
                  rend.material.color = Color.white;
           }

    }



    OPTION 3d. Enemy Patrol Randomly Moving Around A Space (source, part 2)

    a. Create an Empty Game Object called "NPC". In the Inspector reset Transforms.
    b. Add a BoxCollider2D and a RigidBody2D (Gravity Scale = 0).
    c. Drag your Sprite art into the Hierarchy. Name it "NPC_art" and drag it onto NPC to make it a child.
    d. Create a new C# script called "NPC_PatrolRandomSpace.cs".
    e. In your script editor add the content below, Save, and in Unity apply to NPC.

    For the location points:
    f. Create a new Empty GOs to act as location points. Name it "MoveSpot".
    g. Open the Inspector and select NPC.
    h. Fast-drag MoveSpot into the moveSpot script slot.
    i. Set the min and max values to define the movement rectangle (you can create a temporary Empty GO to get position values).
    Hit [Play]: the NPC will move randomly between the points! Turn off Play to continue editing.

    NPC_PatrolRandomSpace.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class NPC_PatrolRandomSpace : MonoBehaviour {

           public float speed = 10f;
           private float waitTime;
           public float startWaitTime = 2f;

           public Transform moveSpot;
           public float minX;
           public float maxX;
           public float minY;
           public float maxY;

           void Start(){
                  waitTime = startWaitTime;
                  float randomX = Random.Range(minX, maxX);
                  float randomY = Random.Range(minY, maxY);
                  moveSpot.position = new Vector2(randomX, randomY);
           }

           void Update(){
                  transform.position = Vector2.MoveTowards(transform.position, moveSpot.position, speed * Time.deltaTime);

                  if (Vector2.Distance(transform.position, moveSpot.position) < 0.2f){
                         if (waitTime <= 0){
                                float randomX = Random.Range(minX, maxX);
                                float randomY = Random.Range(minY, maxY);
                                moveSpot.position = new Vector2(randomX, randomY);
                                waitTime = startWaitTime;
                         } else {
                                waitTime -= Time.deltaTime;
                         }
                  }
           }
    }



    GAZE OF DEATH
    4a. NPC Rotates in Place and Destroys Player   |   4b. Spot Player to Raise NPC Alertness

      OPTION 4a. Rotating Gaze of Death (great for stealth gameplay. See below for moving gaze.)
    This enemy is good for stealth mechanics: it looks around with a laser, and destroys what it sees!
    Alternatively, gaze-contact could initiate Enemy following and/or attacking.

    The player can avoid the gaze by sneaking behind them or obstacles, and could attack from behind.
    For a 2D game rotating "forward" means to rotate on the Z-axis, the axis perpendicular to the screen plane (the only axis of rotation for a 2D game).

    This enemy uses the Unity Line Renderer effect to show the gaze-line in the Game view.
    This could be replaced with a .PNG spritesheet for animation.
    Debug.drawline shows the full line in the Scene view.

    1. Build the Enemy:
    a. Create an Empty Game Object. Name it NPC_DeathGaze. Reset Transforms (0,0,0).
    b. Drag your Sprite art into the Hierarchy. Name it "NPC_DeathGaze_art". Reset Transforms (0,0,0).
    c. Set "Order in Layer" = 90. Apply / Add tag "enemy". Drag it onto NPC_DeathGaze to make it a child.
    d. Add to NPC_DeathGaze:
           [Add Component] Physics2D > Rigidbody2D, set Mass = 10 and Gravity Scale = 0.
           [Add Component] Physics2D > Collider2D (box or circle, set size to surround child art).

    2. Add the behavior script:
    a.
    Create a new C# script called "EnemyGazeMove.cs". In your script editor add content below. Save.
    b. In Unity apply the script to NPC_DeathGaze. Set the 2 colors in script slots (redColor and greenColor).

    EnemyGazeMove.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class EnemyGazeMove : MonoBehaviour {

           public float rotationSpeed = 30;
           public float distance = 50;
           public LineRenderer lineOfSight;
           public Gradient redColor;
           public Gradient greenColor;
           public GameObject hitEffectAnim;

           public int EnemyLives = 30;
           private Renderer rend;
           private GameController gameControllerObj;

           void Start() {
                  Physics2D.queriesStartInColliders = false;

                  rend = GetComponent<Renderer>();
                  GameObject gameControllerLocation = GameObject.FindWithTag ("GameController");
                  if (gameControllerLocation != null) {
                         gameControllerObj = gameControllerLocation.GetComponent<GameController>();
                  }
           }

           void FixedUpdate () {
                  transform.Rotate (Vector3.forward * rotationSpeed * Time.deltaTime);

                  // Raycast needs location, Direction, and length
                  RaycastHit2D hitInfo = Physics2D.Raycast (transform.position, transform.right, distance);
                  if (hitInfo.collider != null) {
                         Debug.DrawLine(transform.position, hitInfo.point, Color.red);
                         lineOfSight.SetPosition(1, hitInfo.point); // index 1 is the end-point of the line
                         lineOfSight.colorGradient = redColor;

                         if ((hitInfo.collider.CompareTag ("Player")) || (hitInfo.collider.CompareTag ("enemy"))) {
                                GameObject animEffect = Instantiate (hitEffectAnim, hitInfo.point, Quaternion.identity);
                                Destroy(animEffect, 0.5f);
                                Destroy (hitInfo.collider.gameObject);
                         }
                  } else {
                         Debug.DrawLine(transform.position, transform.position + transform.right * distance, Color.green);
                         lineOfSight.SetPosition(1, transform.position + transform.right * distance);
                         lineOfSight.colorGradient = greenColor;
                  }
                  lineOfSight.SetPosition (0, transform.position); // index 0 is the start-point of the line
           }


           void OnCollisionEnter2D(Collision2D collision){
                  if (collision.gameObject.tag == "bullet") { // destroy player / enemy projectiles
                         EnemyLives -= 1;
                         StopCoroutine("HitEnemy");
                         StartCoroutine("HitEnemy");
                  }
                  else if (collision.gameObject.tag == "Player") {
                         EnemyLives -= 1;
                         StopCoroutine("HitEnemy");
                         StartCoroutine("HitEnemy");
                  }
           }

           IEnumerator HitEnemy(){
                  // color values are R, G, B, and alpha, each divided by 100
                  rend.material.color = new Color(2.4f, 0.9f, 0.9f, 0.5f);
                  if (EnemyLives < 1){
                         gameControllerObj.AddScore (10);
                         Destroy(gameObject);
                  }
                  else {
                         yield return new WaitForSeconds(0.5f);
                         rend.material.color = Color.white;
                  }
           }
    }



    3. Add the Laser / Line Renderer:
           a. Create a new Empty Game Object. In Inspector name it "LineOfSight". Reset Transforms.
           b. Drag it onto NPC_DeathGaze to parent it beneath.
           c. [Add Component] Effect > Line Renderer.
           (Turn OFF Use World Space to see it follow the parent in-Scene, but turn it ON to follow in-Game).
           d. Under Positions set Index 0 to Z= 0, and set Index 1 to X=1 and Z=0.
           e. Change color from the default purple with a Material:
                  Click the hotspot to right of Element0, choose white "Sprites_Default" material.
                  Toward bottom, set "Order In Layer" = 95.
           f. Reduce the Line Renderer Width (try 0.25).
           g. Change Material placeholder color by clicking the rectangle.
           h. Select the EnemyGaze and drag the LineOfSight into the script slot.




     OPTION 4b. Moving Gaze of "Hey, stop right there!" (for moving guards in stealth gameplay)
    To create moving guards that can react if they see you:

    Start with the simplest NPC_PatrolSequencePoints system (Option 3b, above) to manage movement.
       Set Collider on guard = isTrigger.

    Add the C# script below.
       In the Inspector set the gradient colors for green (all-clear) and red (I-see-you).
       Note the option for isVertical. If you arrange a vertical patrol for your guard, click this option in the gaze.

    Follow Option4a step 3, directly above, for the Laser / Line Renderer.

    NPC_Gaze_ForPatrol.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class NPC_Gaze_ForPatrol : MonoBehaviour {

           public bool isVertical = false;

           public float distance = 50;
           public LineRenderer lineOfSight;
           public Gradient redColor;
           public Gradient greenColor;
           public Vector3 rayDirection;

           private Renderer rend;
           private GameHandler gameHandler;

           private bool canHit = true;
           private float coolDown = 0.5f;

           //public Transform tempCircle; //test where ray hits, 1/2

           void Start() {
                  Physics2D.queriesStartInColliders = false;

                  rend = GetComponentInChildren<Renderer>();
                 
                  if (GameObject.FindWithTag ("GameHandler") != null) {
                     gameHandler = GameObject.FindWithTag ("GameHandler").GetComponent<GameHandler>();
                  }
           }

           void FixedUpdate () {
                  // allow designers to choose vertical or horizontal patrol paths:
                  if (isVertical == false){
                         bool isRight = GetComponent<NPC_PatrolSequencePoints>().faceRight;
                         if (isRight){rayDirection = transform.right;}
                         else {rayDirection = -transform.right;}
                  } else {
                         bool isRight = GetComponent<NPC_PatrolSequencePoints>().faceRight;
                         if (isRight){rayDirection = transform.up;}
                         else {rayDirection = -transform.up;}
                  }

                  // Raycast needs location, Direction, and length
                 
    RaycastHit2D hitInfo = Physics2D.Raycast (transform.position, transform.right, distance);

                  // tempCircle.position = hitInfo.point; //test where ray hits 2/2

                  if (hitInfo.collider != null) {
                         // if ray hits player, set it to red and add commands for alerted guard
                         if (hitInfo.collider.CompareTag ("Player")) {
                                Debug.DrawLine(transform.position, hitInfo.point, Color.red);
                                lineOfSight.SetPosition(1, hitInfo.point); // index 1 is line end-point
                                lineOfSight.colorGradient = redColor;
                                if (canHit){
                                       StartCoroutine(Enemy_Alert(1));
                                       StartCoroutine(EnemyCoolDown());
                                       Debug.Log("Oops -- Guard saw me.");
                               }
                        
    }

                         // if ray hits not player, set it to green
                         else if (!hitInfo.collider.CompareTag ("Player")) {
                                Debug.DrawLine(transform.position, hitInfo.point, Color.green);
                                lineOfSight.SetPosition(1, hitInfo.point); // index 1 is line end-point
                                lineOfSight.colorGradient = greenColor;
                        
    }

                  // if ray hits nothing, set it to green
                  } else {
                         Debug.DrawLine(transform.position, transform.position + rayDirection * distance, Color.green);
                         lineOfSight.SetPosition(1, transform.position + rayDirection * distance); // index 1 is line end
                         lineOfSight.colorGradient = greenColor;
                  }

                  lineOfSight.SetPosition (0, transform.position); // index 0 is the line start-point
           }

           // if player touches the guard:
           void OnTriggerEnter2D(Collider2D other){
                  if ((other.gameObject.tag == "Player") && (canHit)) {
                         StartCoroutine(Enemy_Alert(2));
                         StartCoroutine(EnemyCoolDown());
                         Debug.Log("Ack -- Hit a Guard.");

                  }
           }

           // raise enemy alertness, flash enemy color:
           IEnumerator Enemy_Alert(int amt){
                  float pauseTime = 1f * amt;
                  // color values are R, G, B, and alpha, each divided by 100
                  rend.material.color = new Color(2.4f, 0.9f, 0.9f, 0.5f);

                  // alert GameHandler to increase guard alertness: a function that
                  // increases a static int. If it gets too high, guards chase, go faster, etc.

                  // gameHandler.EnemyAlertness(amt)
                  Debug.Log("Enemy alertness increased by "+ amt);

                  yield return new WaitForSeconds(pauseTime);
                  rend.material.color = Color.white;
           }

           // cooldown prevents enemy from destroying player with one gaze:
           IEnumerator EnemyCoolDown(){
                  lineOfSight.colorGradient = redColor;
                  canHit = false;
                  Debug.Log("canHit "+ canHit);
                  yield return new WaitForSeconds(coolDown);
                  canHit = true;
                  Debug.Log("canHit " + canHit);
                  lineOfSight.colorGradient = greenColor;
           }
    }



     
    [2E]: NPC FRIENDLIES
    Monologue listen to any number of Friendly NPCs (also Road Signs, Treasure Boxes, etc)
    Dialogue chat with any number of Friendly NPCs, back and forth scripted responses.
    NPC Follow / Battle Buddies (to fight with the player against enemies)



    NPC COMMUNICATION BASIC: CHARACTER MONOLOGUE:
    SUMMARY: Create any number of NPC characters to go about their daily business until approached by the Player, and then give a sequence of (informative? snarky?) sentences in a dialogue block. Also works with signage, treasure boxes, etc.


    Want an animated NPC?
    1). Create a spritesheet PNG with enough frames for these two animation clips.
    Work (what they do when the player is not interacting with them: rake the lawn, etc)
    Chat (how they animate when speaking)

    2). Set properties in Inspector:
           Sprite Mode = Multiple, Pixels Size = 16, Filter Mode = Point (no filter), Compression = None.
           Hit [Apply]. Open the [Sprite Editor], choose slice by cell count, set r/c, hit [Slice] and [Apply].

    3). Drag the first frame into Hierarchy.
           In the Inspector name it "NPC_[name]_art"
           Reset Transforms.
           Drag it onto NPC_chat to make it a child.

    4). Set up animation and State Machine for the NPC:
    a. In your Project panel Assets folder create new folders for Media > Animation (if needed).

    b. Open Window > Animation > Animation to create 2 clips and save them to Animation folder:
           NPC_name_work_anim
           NPC_name_chat_anim
    (replace "name" with the character name)

    c. DoubleClick the Animator created with those clips to open the Animator panel.

    d. RightClick NPC_name_work_anim to set it to the default (will become orange).

    e. RightClick NPC_name_work_anim to create transition to NPC_name_chat_anim, and vice-versa.

    f. In the Parameters tab on the left of the Animator window, create a bool named "Chat".

    g. Select the transition arrow from NPC_name_work_anim to NPC_name_chat_anim, and in the Inspector hit [+] to add a new Condition (will auto-set to Chat and true).

    h. Select the reverse transition arrow (from NPC_name_chat_anim to NPC_name_work_anim), and in the Inspector hit [+] to add a new Condition. The will auto-set to Chat and true -- set it to false.

    5). In the NPCDialogues.cs cripts below, uncomment all commands with Animator or anim!
    [1] CREATE THE NPC:
    a. Create and import NPC character art
    (64x64 to match Player character, or as desired).
    If it is just a single sprite pixel art image (animation is not required to test this script):
    Set properties in Inspector:
           Pixels Size = 16
           Filter Mode = Point (no filter)
           Compression = None.
           Hit [Apply].
    Drag the sprite into Hierarchy.
    In the Inspector name it "NPC_[name]_art"
    Reset Transforms.

    b. Create a GameObject > Empty GameObject.
    In the Inspector name it "NPC_chat"
    Reset Transforms.
    Drag NPC art onto NPC_chat to make it a child.
    In Inspector set "Order in layer" = 90.

    c. Add two colliders to NPC_chat:
    Select the NPC_chat object.
    [Add Component] Physics 2D > BoxCollider2D.
    Change the size of the first one to closely surround the NPC form (to block player movement).
    Change the second one to isTrigger, and extend its Collider outward to the sides and below, for the space the Player can stand to chat with this NPC.


    [2] CREATE THE UI ELEMENTS:
    a.
    Select your Canvas, if it exists,
    (If not, GameObject > UI > Canvas. Set Canvas Scaler > UI Scale Mode from "Constant Pixel Size" to "Scale With Screen Size". Set size to 1280 x 720.).
    RightClick Canvas, choose UI > Image.
    Name it "MONOLOGUE_BOX".
    Zoom out in the Scene view to see the entire Canvas (can select the Canvas and in the Scene hit [f]).
    In the Inspector set the image size for the full Canvas (1280x720) and set color alpha=0 (fully transparent).
    Also, to allow click-through, uncheck Raycast Target.

    b. RightClick MONOLOGUE_BOX to create a second UI > Image (centered as a child under it).
    Name it "MonologueBG".
    Size 600 x 150 and move to fit in the bottom third of the Canvas. Set color alpha=0.
    Optionally add framing art to this ImageSprite.

    c. RightClick DialogueBG to create a UI > Legacy > Text (centered as a child under MonologueBG).
    Name it "TextMonologue".
    Size 590 x 140 and move to fit MonologueBG.
    Set Font Size = 30 and color = black.
    Set text field to an ellipsis: "...".

    d. RightClick DialogueBG to create a UI > Legacy > Button.
    Name it "ButtonNext".
    Scale and move ButtonNext to fit in the lower-right corner of MonologueBG: 100 x 60.
    Set the button Normal and Highlight colors.
    In the child Text object, set text field to "NEXT".
    Set Font Size = 35, Style = Bold, and color = black.
    Set Navigation from "Automatic" to "None".


    [3] CREATE THE DIALOGUE MANAGER:
    a. Create an empty game object: GameObject > Empty.
    Name it "MonologueManager".
    Reset the Transforms.

    b. In the Inspector add a new TAG: "MonologueManager":
    Find "tag" near the top of the Inpector. Hit Untagged, choose AddTag. Hit [+] and type.
    Re-select the MonologueManager Object and add this tag.


    [4] CREATE THE TWO SCRIPTS:
    a. Create a new C# script "NPCMonologueManager.cs" with the content below.

    b. Select the MonologueManager object and add this script.
    In the Inspector, fast-drag MONOLOGUE_BOX and TextMonologue from the Canvas into the script slots.
    Open the Array and hit [+] for as many lines of monologue you desire and type them in (for testing).

    c. Set ButtonNext:
    Select ButtonNext, and in the Inspector, near bottom, add an OnClick function [+].
    Drag the MonologueManager into the "None" slot.
    Set the function to NPCMonologueManager > MonologueNext().

    NPCMonologueManager.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.UI;

    public class NPCMonologueManager : MonoBehaviour {

           public GameObject monologueBox;
           public Text monologueText;
           public string[] monologue;
           public int counter = 0;
           public int monologueLength;

           void Start(){
                  monologueBox.SetActive(false);
                  monologueLength = monologue.Length; //allows us test dialogue without an NPC
           }

           void Update(){
                  //temporary testing before NPC is created
                  if (Input.GetKeyDown("o")){
                         monologueBox.SetActive(true);
                  }
                  if (Input.GetKeyDown("p")){
                         monologueBox.SetActive(false);
                         monologueText.text = "..."; //reset text
                         counter = 0; //reset counter
                  }
           }

           public void OpenMonologue(){
                  monologueBox.SetActive(true);
     
                  //auto-loads the first line of monologue
                  monologueText.text = monologue[0];
                  counter = 1;
           }

           public void CloseMonologue(){
                  monologueBox.SetActive(false);
                  monologueText.text = "..."; //reset text
                  counter = 0; //reset counter
           }

           public void LoadMonologueArray(string[] NPCscript, int scriptLength){
                  monologue = NPCscript;
                  monologueLength = scriptLength;
           }

            //function for the button to display next line of dialogue
           public void MonologueNext(){
                  if (counter < monologueLength){
                         monologueText.text = monologue[counter];
                         counter +=1;
                  }
                  else { //when lines are complete:
                         monologueBox.SetActive(false); //turn off the dialogue display
                         monologueText.text = "..."; //reset text
                         counter = 0; //reset counter
                  }
           }

    }


    d. Create a second script called "NPCMonologue.cs" and include the content below.
    Apply this second script to your NPC_chat object in the Scene.
    In the Inspector, open the Array and hit [+] for as many lines of monologue you desire and type them in.
    The contents of this array will replace the MonologueManger array for each interaction!
    This script works as long as (1) the MonologueManager object with the MonologueManager tag and script exists in the scene, and (2) The Canvas with MONOLOGUE_BOX exists:


    NPCMonologue.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.UI;

    public class NPCDialogue : MonoBehaviour {
           //private Animator anim;
           private NPCMonologueManager monologueMNGR;
           public string[] monologue; //enter monologue lines into the inspector for each NPC
           public bool playerInRange = false; //could be used to display an image: hit [e] to talk
           public int monologueLength;

           void Start(){
                  //anim = gameObject.GetComponentInChildren<Animator>();
                  monologueLength = monologue.Length;
                  if (GameObject.FindWithTag("MonologueManager")!= null){
                         monologueMNGR = GameObject.FindWithTag("MonologueManager").GetComponent<NPCMonologueManager>();
                  }
           }

           private void OnTriggerEnter2D(Collider2D other){
                  if (other.gameObject.tag == "Player") {
                         playerInRange = true;
                         monologueMNGR.LoadMonologueArray(monologue, monologueLength);
                         monologueMNGR.OpenMonologue();
                         //anim.SetBool("Chat", true);
                         //Debug.Log("Player in range");
                  }
           }

           private void OnTriggerExit2D(Collider2D other){
                  if (other.gameObject.tag =="Player") {
                         playerInRange = false;
                         monologueMNGR.CloseMonologue();
                         //anim.SetBool("Chat", false);
                         //Debug.Log("Player left range");
                  }
           }
    }


    [5]. MAKE PREFABS:
    a. NPC Prefab:
    Drag the NPC_chat into the Project > Assets folder to make it a Prefab.
    Drag copies into the Scene as needed.
    These copies can include new character art (just disable the old art) and new monologue lines, but will use the same Canvas MonologueBox.
    The name of these copies in the Hierarchy can also be changed for each NPC non-combat character name.

    b. MonologueManager Prefab and MONOLOGUE_BOX Prefab:
    Drag the MonologueManager into the Project > Assets folder to make it a Prefab.
    Drag the MONOLOGUE_BOX parent object from the Canvas into Project > Assets folder to make it a Prefab.
    Both of these can now be added to any desired Scene, to add NPC monologue functionality.
    Just be sure to add the Scene copes of MONOLOGUE_BOX and MonologueText objects to the MonologueManager script slots in each scene.



    NOTE: To use a Canvas set to World Space for local text display, like a word bubble, the text needs to be handled carefully: the standard Unity UI Text Object will present a sharp, crisp result only if the Rect is set large (in this case, 400 x 100), the Font is large (in this case, 30) and the Scale is then set very small (in this case, X, Y, Z all = 0.01).





         

    NPC COMMUNICATION ADVANCED: CHARACTER DIALOGUE:
    SUMMARY: Create any number of NPC characters to go about their daily business until approached by the Player, and then hold a conversation in a dialogue block: back and forth pre-scripted text with the player character.

    [1] CREATE THE NPC:
    a. Create and import art for at least two NPC characters
    (64x64 to match Player character, or as desired).
    If it is just a single sprite pixel art image (animation is not required to test this script):
    Set properties in Inspector:
           Pixels Per Unity = 16
           Filter Mode = Point (no filter)
           Compression = None.
           Hit [Apply].
    Drag the sprite into Hierarchy.
    In the Inspector name it "NPC_[name]_art"
    Reset Transforms.

    ART SIZE NOTE:
    Character art should be sized using Pixels Per Unit in the project panel Inspector, NOT by changing size in the Scene Inspector.

    Once the sprite is in the Scene, adjust its size by selecting the source image in the Project panel and changing Pixels Per Unit and hitting [Apply].

    Choose large numbers to shrink characters, smaller numbers make larger.

    If it is a Spritesheet for animation:
    In the Inspector, set Sprite Mode = Multiple.
    Hit [Apply] (near bottom).

    Open the [Sprite Editor] (near middle).
    Under Slice set Type = Grid by Cell Count.
       Choose columns and rows.
       Hit [Slice] and [Apply].

    The first frame of the idle is typically deagged into the Hierarchy as the initial character sprite.
    Animation is added later to this sprite

    NOTE: Animation is not required to test this script.


    b. Create and import face bubble art for the Player character and the NPC characters (128x128).
    Do not drag into the Hierarchy (will be added to the UI).

    c. Create a GameObject > Empty GameObject.
    In the Inspector name it "NPC_chat"
    Reset Transforms.
    Drag both NPC art images onto NPC_chat to make them each a child
    (all NPC art gets added to the same NPC_chat object, and only one is enabled for each NPC in game).
    In Inspector set "Order in layer" on these sprites = 90.
    Disable all but one of the character images.

    d. Add a Collider to NPC_chat:
    Select the NPC_chat object.
    [Add Component] Physics 2D > BoxCollider2D.
    Enable isTrigger, and extend its Collider outward to the sides and below, for the space the Player can stand to chat with this NPC.


    [2] CREATE THE UI ELEMENTS:
    a.
    Select your Canvas, if it exists,
    (If not, GameObject > UI > Canvas. Set Canvas Scaler > UI Scale Mode from "Constant Pixel Size" to "Scale With Screen Size". Set size to 1280 x 720.).
    RightClick Canvas, choose UI > Image.
    Name it "DIALOGUE_BOX".
    Zoom out in the Scene view to see the entire Canvas (can select the Canvas and in the Scene hit [f]).
    In the Inspector set the image size for the full Canvas (1280x720) and set color alpha=0 (fully transparent).
    Also, to allow click-through, uncheck Raycast Target.

    b. RightClick DIALOGUE_BOX to create a second UI > Image (centered as a child under it).
    Name it "DialogueBG".
    Size 600 x 150 and move to fit in the bottom third of the Canvas. Set color alpha=0.
    Optionally add framing art to this ImageSprite.

    c. RightClick DialogueBG to create a UI > Legacy > Text (centered as a child under DialogueBG).
    Name it "TextDialogue".
    Size 590 x 140 and move to fit DialogueBG.
    Set Font Size = 30 and color = black.
    Set text field to an ellipsis: "...".

    d. RightClick DialogueBG to create a UI > Legacy > Button.
    Name it "ButtonNext".
    Scale and move ButtonNext to fit in the lower-right corner of DialogueBG: 100 x 60.
    Set the button Normal and Highlight colors.
    In the child Text object, set text field to "NEXT".
    Set Font Size = 35, Style = Bold, and color = black.
    Set Navigation from "Automatic" to "None".


    [3] CREATE THE DIALOGUE MANAGER:
    a. Create an empty game object: GameObject > Empty.
    Name it "DialogueManager".
    Reset the Transforms.

    b. In the Inspector add a new TAG: "DialogueManager":
    Find "tag" near the top of the Inpector. Hit Untagged, choose AddTag. Hit [+] and type.
    Re-select the DialogueManager Object and add this tag.


    [4] CREATE THE TWO SCRIPTS:
    a. Create a new C# script "NPCDialogueManager.cs" with the content below.

    b. Select the DialogueManager object and add this script.
    In the Inspector, fast-drag DIALOGUE_BOX and TextDialogue from the Canvas into the script slots.
    Open the NPC_Choices Array. Hit [+] for as many character bubble images you desire.
       Drag the sprites in from your Project panel art folder.
       Pay attention to how they are numbered: those are the values for each NPC.

    c. Set ButtonNext:
    Select ButtonNext, and in the Inspector, near bottom, add an OnClick function [+].
    Drag the DialogueManager into the "None" slot.
    Set the function to NPCDialogueManager > DialogueNext().

    NPCDialogueManager.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.UI;

    public class NPCDialogueManager : MonoBehaviour {

            public GameObject dialogueBox;
            public Text dialogueTextChar1; // Player text
            public Text dialogueTextChar2; // NPC text

            public GameObject displayChar1; // Parent object of Player text and bubble image
            public GameObject displayChar2; // Parent object of NPC text and bubble image
            public Image NPCBubbleArt; // Dynamically replaced by code
            public Sprite[] NPC_Choices; // Array of bubble art: drag in from Project panel art folder
            bool isOtherChar = true;
            bool playerFirst = false;
            bool singleSpeaker = false;

            public string[] dialogue;
            public int counter = 0;
            public int dialogueLength;

            void Start(){
                    dialogueBox.SetActive(false);
                    dialogueLength = dialogue.Length; //allows us to test dialogue without an NPC
            }

            void Update(){
                    //temporary testing before NPC is created
                    if (Input.GetKeyDown("o")){
                            dialogueBox.SetActive(true);
                    }
                    if (Input.GetKeyDown("p")){
                            dialogueBox.SetActive(false);
                            dialogueTextChar1.text = "..."; //reset text
                            dialogueTextChar2.text = "..."; //reset text
                            counter = 0; //reset counter
                    }

                    //control the display of who is speaking
                    if (isOtherChar == true){
                            displayChar1.SetActive(false);
                            displayChar2.SetActive(true);
                    } else {
                            displayChar1.SetActive(true);
                            displayChar2.SetActive(false);
                    }
            }

            public void ChooseNPC(int thisNPC, bool thisPlayerFirst, bool thisSingleSpeaker){
                  NPCBubbleArt.sprite = NPC_Choices[thisNPC];
                  playerFirst = thisPlayerFirst;
                  singleSpeaker = thisSingleSpeaker;
            }

            public void OpenDialogue(){
                    //Choose first speaker: player or NPC
                    if (playerFirst){isOtherChar=false;}
                    else {isOtherChar=true;}

                    dialogueBox.SetActive(true);
     
                    //auto-load the first line of dialogue
                    dialogueTextChar1.text = dialogue[0];
                    dialogueTextChar2.text = dialogue[0];
                    counter = 1;
           }

           public void CloseDialogue(){
                  dialogueBox.SetActive(false);
                  dialogueTextChar1.text = "..."; //reset text
                  dialogueTextChar2.text = "..."; //reset text
                  counter = 0; //reset counter
           }

           public void LoadDialogueArray(string[] NPCscript, int scriptLength){
                  dialogue = NPCscript;
                  dialogueLength = scriptLength;
           }

            //function for the button to display next line of dialogue
           public void DialogueNext(){
                  if (counter < dialogueLength){
                         if (!singleSpeaker){ isOtherChar = !isOtherChar; } //If two speakers, swap for each line
                         dialogueTextChar1.text = dialogue[counter];
                         dialogueTextChar2.text = dialogue[counter];
                         counter +=1;
                  }
                  else { //when lines are complete:
                         CloseDialogue();
                  }
           }

    }


    d. Create a second script called "NPCDialogue.cs" and include the content below.
    Apply this second script to your NPC_chat object in the Scene.
    In the Inspector, open the Array and hit [+] for as many lines of dialogue you desire and type them in.
    The contents of this array will replace the DialogueManger array for each interaction!
    This script works as long as (1) the DialogueManager object with the DialogueManager tag and script exists in the scene, and (2) The Canvas with DIALOGUE_BOX exists:


    NPCDialogue.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.UI;

    public class NPCDialogue : MonoBehaviour {
           //private Animator anim;

           public int whichNPC = 1; // set this number for the NPC, to show the correct bubble image
           public bool playerFirst = false; //player speaks first in back-and-forth
           public bool singleSpeaker = false; //only show speech from one character, player or NPC

           private NPCDialogueManager dialogueMNGR;
           public string[] dialogue; //enter dialogue lines into the inspector for each NPC
           public bool playerInRange = false; //could be used to display an image: hit [e] to talk
           public int dialogueLength;

           void Start(){
                  //anim = gameObject.GetComponentInChildren<Animator>();
                  dialogueLength = dialogue.Length;
                  if (GameObject.FindWithTag("DialogueManager")!= null){
                         dialogueMNGR = GameObject.FindWithTag("DialogueManager").GetComponent<NPCDialogueManager>();
                  }
           }

           private void OnTriggerEnter2D(Collider2D other){
                  if (other.gameObject.tag == "Player") {
                         playerInRange = true;
                         dialogueMNGR.LoadDialogueArray(dialogue, dialogueLength);
                         dialogueMNGR.LoadDialogueArray(whichNPC, playerFirst, singleSpeaker);
                         dialogueMNGR.OpenDialogue();
                         //anim.SetBool("chat", true);
                         //Debug.Log("Player in range");
                  }
           }

           private void OnTriggerExit2D(Collider2D other){
                  if (other.gameObject.tag =="Player") {
                         playerInRange = false;
                         dialogueMNGR.CloseDialogue();
                         //anim.SetBool("chat", false);
                         //Debug.Log("Player left range");
                  }
           }
    }


    [5]. MAKE PREFABS:
    a. NPC Prefab:
    Drag the NPC_chat into the Project > Assets folder to make it a Prefab.
    Drag copies into the Scene as needed.
    These copies can include new character art (just disable the old art) and new dialogue lines, but will use the same Canvas DialogueBox.
    The name of these copies in the Hierarchy can also be changed for each NPC non-combat character name.

    b. DialogueManager Prefab and DIALOGUE_BOX Prefab:
    Drag the DialogueManager into the Project > Assets folder to make it a Prefab.
    Drag the DIALOGUE_BOX parent object from the Canvas into Project > Assets folder to make it a Prefab..
    Both of these can now be added to any desired Scene, to add NPC dialogue functionality.
    Just be sure to add the Scene copies of DIALOGUE_BOX and DialogueText objects to the DialogueManager script slots in each scene.



         




      NPC MOVEMENT: Following / Flocking Behavior:
    SUMMARY: Have NPCs follow the Player.
    Can be used for ships / birds / fish that move together, friendly NPCs to guide to safety, or buddies that fight with the player (activated by Player attacks).

    With additional scripts, this follow behavior can be activated on an NPC that displays other behaviors first ("save" a character so that it follows the player). Another useful feature could be to add every new follower to a List, then alert the player if any followers get left behind (especially around a bend).

    CREATE THE NPC:
    a. Create a GameObject > Empty GameObject.
    In the Inspector name it "NPC_Follow"
    Reset Transforms.
    [Add Component] Physics 2D > BoxCollider2D.
    [Add Component] Physics 2D > Rigidbody2D (set Gravity Scale = 0).

    b. Drag NPC art into the Hierarchy (or make a temporary Unity sprite).
    Drag onto NPC_Follow to make it a child.
    Adjust the NPC_Follow BoxCollider2D to fit the art.

    c. Create a new C# script called "NPC_Follower".
    Add the content below and drag onto NPC_Follow.
    Make sure the Player has the tag "Player".
    Check Player's moveSpeed to set NPC_Follow's topSpeed the same.

    NPC_Follower.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class NPC_Follower : MonoBehaviour {
           //this script offers basic flocking behavior for friendly NPCs to follow the player
           //commented-out are functions for followers to help attack enemies if the player attacks

           //private Animator anim;

    //Follow Player
           private GameObject player;
           private Vector2 playerPos;
           private float distToPlayer;
           public float startFollowDistance; //Follow Player when further than this distance
           public float followDistance; //Stop moving towards player when at this distance
           public float moveSpeed;
           public float topSpeed = 10f;
           private float scaleX;
           //public Vector2 offsetFollow;

    //Follow Player vs Attack Enemies
           public bool followPlayer = true;
           /*
            public bool attackEnemy = false; // target enemy within range of player when player attacks
            public bool isAttacking = false; // attack a targeted enemy
            public float peacefullTime = 4f;
           */

    //Attack variables
           /*
            public LayerMask enemyLayers;
            public GameObject enemyTarget;
            private Vector2 enemyPos;
            private float distToEnemy;
            private float timeBtwShots;
            public float startTimeBtwShots = 2;
            public GameObject projectile;
            public float attackRange = 10f;
           */

           void Start(){
                  //anim = gameObject.GetComponentInChildren<Animator>();
                  player = GameObject.FindWithTag("Player");
                  followDistance = Random.Range(1f, 2f);
                  startFollowDistance = followDistance + 1f;
                  moveSpeed = Random.Range((topSpeed * 0.7f), topSpeed);
                  scaleX = gameObject.transform.localScale.x;
           }

           void Update(){
                  //listen for player attacking an enemy, enter combat until player stops attacking
                  /*
                  if ((Input.GetAxis("AttackFire") > 0) || (Input.GetAxis("AttackMelee") > 0)){
                         followPlayer = false;
                         attackEnemy = true;
                         StartCoroutine(StopAttackingEnemies());
                         FindTheEnemy();
                   }
                   */
            }

            void FixedUpdate(){
                    //FOLLOW PLAYER
                   if ((followPlayer) && (player != null)){
                          playerPos = player.transform.position;
                          distToPlayer = Vector2.Distance(transform.position, playerPos);

                          //Retreat from Player
                          if (distToPlayer <= followDistance){
                                    transform.position = Vector2.MoveTowards (transform.position, playerPos, -moveSpeed * Time.deltaTime);
                                    //anim.SetBool("Walk", true);
                          }

                          // Stop following Player
                          if ((distToPlayer > followDistance) && (distToPlayer < startFollowDistance)){
                                    transform.position = this.transform.position;
                                    //anim.SetBool("Walk", false);
                          }

                          // Follow Player
                          else if (distToPlayer >= startFollowDistance){
                                    transform.position = Vector2.MoveTowards (transform.position, playerPos, moveSpeed * Time.deltaTime);
                                    //anim.SetBool("Walk", true);
                          }

                          // Turn follower toward player (good for bipedal characters)
                          /*
                           if (player.transform.position.x > gameObject.transform.position.x){
                                    gameObject.transform.localScale = new Vector2(scaleX, gameObject.transform.localScale.y);
                            } else {
                                    gameObject.transform.localScale = new Vector2(scaleX * -1, gameObject.transform.localScale.y);
                            }
                            */


                            // Rotate to face player (good for swimming / flying followers)
                            Vector2 direction = (playerPos - (Vector2)transform.position).normalized;
                            float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
                            float offset = 90f;
                            transform.rotation = Quaternion.Euler(Vector3.forward * (angle + offset));
                  }


                    //FOLLOW ENEMY
                    /*
                    if ((attackEnemy) && (enemyTarget != null)){
                            enemyPos = enemyTarget.transform.position;
                   
            distToEnemy = Vector2.Distance(transform.position, enemyPos);

                            // Retreat from enemy
                            if (distToEnemy <= followDistance){
                           
            transform.position = Vector2.MoveTowards (transform.position, enemyPos, -moveSpeed * Time.deltaTime);
                   
                    anim.SetBool("Walk", true);
                            }

                            // Stop following enemy
                            if ((distToEnemy > followDistance) && (distToEnemy < startFollowDistance)){
                           
            transform.position = this.transform.position;
                   
                    anim.SetBool("Walk", false);
                            }

                   
            // Follow enemy
                   
            else if ((distToEnemy >= startFollowDistance)){
                           
            transform.position = Vector2.MoveTowards (transform.position, enemyPos, moveSpeed * Time.deltaTime);
                   
                    anim.SetBool("Walk", true);
                            }

                   
            // Turn buddy toward enemy
                   
            if (enemyTarget.transform.position.x > gameObject.transform.position.x){
                   
                    gameObject.transform.localScale = new Vector2(scaleX, gameObject.transform.localScale.y);
                            } else {
                           
            gameObject.transform.localScale = new Vector2(scaleX * -1, gameObject.transform.localScale.y);
                   
            }
                    }
                    */

                    //Timer for shooting projectiles
                    /*
                    if ((attackEnemy==true)&&(enemyTarget !=null) && (distToEnemy > followDistance) && (distToEnemy < startFollowDistance)){
                   
            if (timeBtwShots <= 0) {
                                    isAttacking = true;
                   
                    anim.SetBool("Attack", true);

                   
                    GameObject myProjectile = Instantiate (projectile, transform.position, Quaternion.identity);
                                    myProjectile.GetComponent<NPC_Projectile>().attackPlayer = false;
                   
                    myProjectile.GetComponent<NPC_Projectile>().enemyTrans = enemyTarget.transform;

                                    timeBtwShots = startTimeBtwShots;
                            } else {
                   
                    timeBtwShots -= Time.deltaTime;
                                    isAttacking = false;
                   
                    anim.SetBool("Attack", false);
                            }
                    } else {anim.SetBool("Attack", false); }

                    */

            //end bracket of FixedUpdate:

            }


            /*
            void FindTheEnemy(){
           
            //animator.SetTrigger ("Melee");
           
            Collider2D[] hitEnemies = Physics2D.OverlapCircleAll(playerPos, attackRange, enemyLayers);

           
            foreach(Collider2D enemy in hitEnemies){
           
                    Debug.Log("Buddy targeting " + enemy.name);
           
                    enemyTarget = enemy.gameObject;
                            //enemy.GetComponent ().TakeDamage(attackDamage);
           
            }
            }
            */

            /*
            IEnumerator StopAttackingEnemies(){
                    yield return new WaitForSeconds(peacefullTime);
           
            followPlayer = true;
           
            attackEnemy = false;
            }
            */


            // DISPLAY the range of enemy's attack when selected in the Editor
            void OnDrawGizmos(){
                    Gizmos.DrawWireSphere(transform.position, followDistance);
            }

    }




     
    [2F]: MORE PLAYER MOVEMENT
    1. Teleporters   |   2. Hookshot/ Grappling Hook   |   3. Flying   |   4. Dash

      1. TELEPORTERS:
    Let your player jump betwen point son the map to solve puzzles or manage enemies:

    a. Make the Teleporter Object:
            1). Create an Empty GO: RightClick in the Hierarchy to create an Empty GameObject.
                 In the Inspector reset Transforms. Name it "Teleporter".
            2). Add Collider: In the Inspector [Add Component] BoxCollider2D. Enable isTrigger.
                 The default size = 1x1 is fine-- you can go a bit bigge,r but no smaller.
            3). Add tag: In the Inspector click the tag rolldown, choose Add Tag, create a new tag named "teleport".
                 Select the Teleporter object and apply that new tag.
            4). Add Art: RightCklick Teleporter to create a 2D > Sprite > Rectangle.
                 In the Inspector name it "teleporter_art" and reset Transforms (if needed).


    SCRIPT OPTION 1. Basic Teleport Script to Jump to Nearest Teleporter Object

    b. Add the script:
            1). RightClick the Project panel: create > C# script. Name it "TeleportBasic", hit [Enter] to commit.
            2). When the script has compiled, apply it to the Teleporter object.
            3). Open the script in your editor, and copy and paste the content below.
                 Save and return to Unity to compile.

    TeleporterBasic.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    //This script goes onto the teleport object
    //The teleport object needs a BoxCollider2D set to isTrigger and a tag called "teleport"
    public class TeleporterBasic : MonoBehaviour {

           public Vector3 newPosition;
           public GameObject[] allTeleporters;

            public void OnTriggerEnter2D(Collider2D other){
                    if (other.gameObject.tag == "Player"){
                            Vector3 playerPos = other.gameObject.transform.position;
                            float DistToPlayer = Vector3.Distance(playerPos, transform.position);

                            //to prevent teleporters from looping: set trigger colliders no smaller than 1x1
                            if (DistToPlayer > 0.5f){
                                    GetNearest();
                                    other.gameObject.transform.position = newPosition;
                            }
                    }
            }

            public void GetNearest(){
                    //populate array with all teleporters except self
                    this.gameObject.tag = "Untagged"; //temporary tag
                    allTeleporters = GameObject.FindGameObjectsWithTag("teleport");
                    this.gameObject.tag = "teleport"; //change tag back

                    //test each array item for closest
                    Transform closestTarget = null;
                    float closestTargetSquare = Mathf.Infinity;
                    Vector3 thisPosition = transform.position;
                    for (int i = 0; i < allTeleporters.Length; i++){
                            Vector3 distanceToNewTarget = allTeleporters[i].transform.position - thisPosition;
                            float distanceSquareToNewTarget = distanceToNewTarget.sqrMagnitude;
                            if (distanceSquareToNewTarget < closestTargetSquare){
                                    closestTargetSquare = distanceSquareToNewTarget;
                                    closestTarget = allTeleporters[i].transform;
                            }
                    }
                    newPosition = closestTarget.position;
            }
    }

    // "find nearest" adapted from:
    // https://forum.unity.com/threads/clean-est-way-to-find-nearest-object-of-many-c.44315/


    c. Create a Prefab of your Teleporter:
            1). Make it a Prefab: Drag Teleporter to the Project panel to make it a Prefab.
            2). Make copies: In the Hierarchy, duplicate the Teleporter and move it away from the original.

    d. Test the Mechanic:
            Drag a copy of a working Player prefab into the Scene.
            Hit Play and try teleporting between Teleporters.



    SCRIPT OPTION 2. Color-Specific Script to Jump to Nearest Teleporter of the Same Color:
    (Continues from the process above)

    e. Replace the Script:
            1). RightClick the Project panel: create > C# script. Name it "TeleportByColor", hit [Enter] to commit.
            2). DoubleClick the Teleport Prefab to open it in Scene view.
                 RightClick the TeleportBasic script to choose "Remove Component".
                 Apply the new TeleportByColor script to the Teleporter object.
            3). Open the script in your IDE, and copy and paste the content below. Save and return to Unity to compile.

    TeleportByColor.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using System;

    //This script goes onto the teleport object
    //The teleport object needs a BoxCollider2D set to isTrigger and a tag called "teleport"
    //Set the color name for each object you want to teleport between
    public class TeleporterByColor : MonoBehaviour {

           public Vector3 newPosition;
           public GameObject[] allTeleporters;
           // since arrays are not meant to be dynamic, we use a list:
           public List<GameObject> myColorTeleporters = new List<GameObject>();
           public string myColor = "purple";

            public void OnTriggerEnter2D(Collider2D other){
                    if (other.gameObject.tag == "Player"){
                            Vector3 playerPos = other.gameObject.transform.position;
                            float DistToPlayer = Vector3.Distance(playerPos, transform.position);

                            //This prevents teleporters from looping. Set trigger colliders no smaller than 1x1
                            if (DistToPlayer > 0.5f){
                                    GetNearest();
                                    other.gameObject.transform.position = newPosition;
                            }
                    }
            }

            public void GetNearest(){
                    //1. Populate ARRAY with ALL teleporters except self:
                    this.gameObject.tag = "Untagged"; //set a temporary tag
                    allTeleporters = GameObject.FindGameObjectsWithTag("teleport");
                    this.gameObject.tag = "teleport"; //change the tag back

                    //2. Get the LIST of ONLY the teleporters that share the same color:
                    if (myColorTeleporters.Count==0){
                            string thisColor = myColor;
                            int addToIndex = 0;
                            for (int i = 0; i < allTeleporters.Length; i++){
                                    string thatColor = allTeleporters[i].GetComponent<teleportByColor>().myColor;
                                    if (thatColor == thisColor){
                                            myColorTeleporters.Add(allTeleporters[i]);
                                            addToIndex ++;
                                    }
                            }
                    }

                    //3. Test each List item for CLOSEST:
                    Transform closestTarget = null;
                    float closestTargetSquare = Mathf.Infinity;
                    Vector3 thisPosition = transform.position;
                    if (myColorTeleporters == null){ Debug.Log("no other teleporters of this color");}
                    else {
                            for (int i = 0; i < myColorTeleporters.Count; i++){
                                    Vector3 distanceToNewTarget = myColorTeleporters[i].transform.position - thisPosition;
                                    float distanceSquareToNewTarget = distanceToNewTarget.sqrMagnitude;
                                    if (distanceSquareToNewTarget < closestTargetSquare){
                                            closestTargetSquare = distanceSquareToNewTarget;
                                            closestTarget = myColorTeleporters[i].transform;
                                    }
                            }
                            newPosition = closestTarget.position;
                    }
            }
    }



    c. Create multiple Teleporter colors:
            1). Duplicate the Teleporters: In the Hierarchy, duplicate the Teleporter multiple times
                 and move them away from each other
    .
            2). Create Color Groups: Select two or more teleporters top mak them part fo the same color group:
                 (a) change the sprite color of the child art (Example: hit the color rectangle and choose a red)
                 (b) rename parent objects for the color (Example, call Red teleporters TeleporterR1, TeleporterR2, etc)
                 (c) in the TeleportByColor script slot set the name of the color (Example: "red")

    When you hit [Play], the Player will teleport to the nearest teleporter of the same color!





      2. Hookshot / Grappling Hook (source video tutorial, steps and scripts to come):
    A Grappling hook is most commonly used in a Platformer, but can be used in a top-down game as well.
    This video uses a Unity Line-Renderer to represent the rope.
    Encourage Player agency by allowing them to move and jump while hanging from the rope.






      3. Flying (Steps and scripts to come):
    There are many ways to handle flying: With wings, with a rocket pack, with a cape.
    Similarly, Swimming can be thought of as flying underwater
    Important things to consider for each type of flying motion:
                 (a) how can the motion and input make it feel like the intended type of flying and
                 (b) what constraints can we add to encourage interesting player choice?

    For example, consider this "flappy bird"-like system for flying with Wings:
    Adjust a jumping script so the player does not need to be grounded to jump.
    Limit how high the player can jump, so each "jump" goes only a bit higher.
    Add a Coroutine to run after each "jump" to allow the player to fall a bit, then briefly push the player up a bit, to imitate an extra wing flap
    Add a Time Constraint circle: when the player is flying, the circle depletes. When the player lets the wings rest, the player refills. The player cannot fly when the circle is fully depleted.





      4. Dash (Steps and script to come):
    Consider a Dash that moves the player at much greater speeds over short distances, and kills (or damages) enemies along the way. It may convey temporary inviulnerability.
    It likely should include a cool down, so it cannot just be used constantly.






     
    [2G]: GAME FEEL: VFX / SFX
    Power-Up VXF   |   Color Change   |   Audio Shepard Scale
    Particle Effects   |   Screen Shake   |   Audio   |   UI Animations   |   Screen Fade

     OPTION 1. Power Up VFX on the Player:

    a. Download and import these player VFX spritesheets (or make your own):

    b. Select both and in the Inspector [Apply] these settings:
            Sprite Mode = Multiple
            Pixels Per Unit = 16
            Filter Mode = Point (no filter)
            Compression = None

    c. Open the [Sprite Editor] for each and Slice each with Cell Count = 4x4, hit [Apply].

    d. Drag the first frame of each into the Hierarchy onto the Player object, to make them children of the Player. Name them and set Order In Layer to surround the Player:
            PowerUp_VFX_front (set Order in Layer = 105)
            PowerUp_VFX_back (set Order in Layer = 95)

    e. Open the Window > Animator > Animator. Select PowerUp_VFX_Front and hit [Create], name the clip powerUpFront_anim. Drag all 16 frames from that Spritesheet into the timeline, on 3s starting at frame 10:
    10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46, 49, 52, 55.
    Repeat for PowerUp_VFX_Back, frames 15-60 (so the back and front move at separate times)

    f. Create a new C# script named "PlayerVFX.cs". Add the following code, Save, and drag this script onto the Player. Drag each of the child VFX images into the powerUp script slots.

    PlayerVFX.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class PlayerVFX : MonoBehaviour{

            public GameObject powerUp1;
            public GameObject powerUp2;

            void Start(){
                  powerUp1.SetActive(false);
                  powerUp2.SetActive(false);
            }
            public void powerup(){
                  Debug.Log("PowerUp VFX!");
                  StartCoroutine(playVFX1());
            }

            IEnumerator playVFX1(){
                  powerUp1.SetActive(true);
                  powerUp2.SetActive(true);
                  yield return new WaitForSeconds(3.0f);
                  powerUp1.SetActive(false);
                  powerUp2.SetActive(false);
            }

    }



    COLLISION EFFECTS:
    There are many ways to emphasize an impact or action and let the player know something happened.
    Here are four ways to communicate information to the player in-game:
    change the object color, play a sound, create a particle effect, add camera-shake.
    These effects can also be changed to happen on an attack impact instead of just a jump or fall.

    Create a simple test scenario in a new Scene:
    a. Create a ground platform (GameObject > 2D > Sprite > Rectangle) or Tilemap with a collider.

    b. Create an object to fall: Create a GameObject > 2D > Sprite > Rectangle.
    In the Inspector name it "BounceBox" and set Order In Layer = 100.
    [AddComponent] Physics2D > Rigidbody2D and BoxCollider2D. Lift it significantly above the ground.

    c. Make it bouncy: RightClick the Project panel to create a 2D > Physic Material2D.
    In the Inspector: Set bounciness to 0.7 and friction to 0.1.
    Drag the PhysicMaterial2D onto the BounceBox so it is added to the Collider "Material" slot.


     CHANGE COLOR:
    a. Create a new C# script called "ImpactColorChange.cs" with the content below.

    b. Add the script to the Player.

    c. Hit [Play] to test, turn off Play to edit.
    ImpactColorChange.cs
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class ImpactColorChange : MonoBehaviour {

           private float hits = 0;     //the # of this object's collisions
           private float r ;               // red color, 0-1
           private float g ;              // green color, 0-1
           private float b ;              // blue color, 0-1

           void OnCollisionEnter2D(Collision2D other){
                  //if the impact has enough force
                  if (other.relativeVelocity.magnitude > 5) {
                        //increment hits #
                         hits += 1;
                        //randomize RGB color values, but go from warm to cool
                         if (hits <= 4) {
                               r = Random.Range (0.3f, 1f);
                               g = Random.Range (0.1f, 0.5f);
                               b = Random.Range (0f, 0.2f);
                         }
                         if (hits > 4) {
                               r = Random.Range (0f, 0.3f);
                               g = Random.Range (0f, 0.3f);
                               b = Random.Range (0.3f, 1f);
                        }

                        SpriteRenderer squareRenderer = GetComponent<SpriteRenderer> ();
                        squareRenderer.color = new Color(r,g,b);
                  }
           }
    }
    This is a random color generator. For a simple color change on an attack impact, set the color to a specific Vector3, and then ina CoRoutine add a delay and then set it back to white (2.5f, 2.5f, 2.5f).


     PLAY A SOUND (and raise the pitch with each impact: SHEPARD SCALE):
    a. Download this sound file (piano middle C), drag into Project and onto Player.

    b. Inspector: Turn off Play on Awake, set Volume = 0.5.

    c. Create a new C# script called "ImpactSounds.cs", add it to the Player.
    This script creates a "Shepard tone": an increasing pitch with each hit.
    Hit [Play] to test, turn off Play to edit.


    ImpactSounds.cs (sound file needs to be on cube)
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class
    ImpactSounds : MonoBehaviour {

           private float hits = 0;
           private AudioSource audioClip;    //the actual AudioSource
           private float clipStartPitch;               //the original note
           private float clipPitch;                       //a modified note
           private float clipVolume;                   //hold and modify the volume

           void Awake() {
                 //populate the variables
                 audioClip = GetComponent ();
                 clipStartPitch = audioClip.pitch;
                 clipPitch = audioClip.pitch;
                 clipVolume = audioClip.volume;
           }

           void OnCollisionEnter2D(Collision2D other){
                  //if the impact has enough force, play audio
                  if (other.relativeVelocity.magnitude > 5) {
                        audioClip.Play();
                        //increment hits #
                        hits += 1;
                        //if pitch is above 0, lower pitch per hits, lower volume
                        if (clipPitch > 0){
                               clipPitch = (clipStartPitch - (hits/10)); //lower pitch
                               audioClip.pitch = clipPitch;                 //assign to clip
                               audioClip.volume = clipVolume / 2;
                        }
                 }
           }
    }


     INSTANTIATE (SPAWN) A PARTICLE EFFECT:
    a. Download this smoke texture or this Empty PNG, drag into Project.
    RightClick Project panel to create Material, name it ParticleSmoke.
    Hit Albedo hotspot and choose this ParticleSmokeSmall.png.
    Set Shader to Particles > Alpha Blended.

    b. Create a new GameObject > Effect > ParticleSystem.
    EMISSION: Set Rate Over Time = 0, add a single Burst [+].
    SHAPE: Shape = Sphere.
    RENDERER (bottom): Set Material to ParticleSmoke Material.
    MAIN SETTINGS (click image to the right for image):
        Duration = 3, Turn off Looping, Start Lifetime = 2,
        Start Speed = 0, Start Size = 1.
    VELOCITY OVER LIFETIME: Turn on. Set Radial = 0.5.
    COLOR OVER LIFETIME: Turn on. Click white rectangle to access
       color and opacity bar. Set bottom tabs (color) to brown and black.
       Set top right tab to Alpha=0. Move the right-side tabs to Location=50%.

    c. Drag the Particle Effect to the Project panel to make a Prefab.
    Delete the Particle Effect from the Hierarchy.

    d. Create a new C# script called "ImpactParticles.cs" to the Player.
    Add the Particle Prefab to script slot.
    Hit [Play] to test, turn off Play to continue editing.

    ImpactParticles.cs (drag Particles Prefab into script slot)
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class ImpactParticles : MonoBehaviour{

           public GameObject hitParticles;
           public Vector3 spwnPoint;

           void OnCollisionEnter2D(Collision2D other){
               //if the impact has enough force
               if (other.relativeVelocity.magnitude > 5) {
                   //get impact location
                  spwnPoint = other.contacts[0].point;
                   //make particles
                  GameObject particleSys = Instantiate (hitParticles, spwnPoint, other.transform.rotation);
                  StartCoroutine(destroyParticles(particleSys));
               }
           }

           IEnumerator destroyParticles(GameObject pSys){
                  yield return new WaitForSeconds(4f);
                  Destroy(pSys);
           }
    }
    A particle system can be added to a jump lift off or landing to emphasize the motion, to any melee or projectile impact.

     ADD SCREEN SHAKE (CAMERA SHAKE, source):
    a. Create a new GameObject > Empty Game Object.
    In the Inspector, name it "CameraShaker" and reset Transforms.

    b. Select your MainCamera. Set Position Transforms to (0,0, -10).
    Drag MainCamera onto CameraShaker to make it a child.
    Remove any CameraFollow.cs script from MainCamera and add it to CameraShaker.

    c. Create a new C# script called "CameraShake.cs" with the following content. Drag this script onto your CameraShaker. Hit [Play] to test: hit [p] on your keyboard to see the shake.


    CameraShake.cs
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class CameraShake : MonoBehaviour{

           public float durationTime = 0.15f;
           public float magnitude = 0.3f;

           //The Update Function is just for testing (hit [p]), and can be commented out:
           void Update(){
                  if (Input.GetKeyDown(KeyCode.P)){
                         StartCoroutine(ShakeMe(durationTime,magnitude));
                 }
           }

           //use this to call from another script, like when the player gets hit
           public void ShakeCamera(float durationTime2, float magnitude2){
                  StartCoroutine(ShakeMe(durationTime2, magnitude2));
           }

           //the screenshake!
           public IEnumerator ShakeMe(float durationTime, float magnitude){
                  Vector3 origPos = transform.localPosition;
                  float elapsedTime = 0.0f;

                  while (elapsedTime < durationTime){
                         float sX = Random.Range(-1f, 1f) * magnitude;
                         float sY = Random.Range(-1f, 1f) * magnitude;

                         transform.localPosition = new Vector3((origPos.x+sX), (origPos.y+sY), origPos.z);
                         elapsedTime += Time.deltaTime;
                         yield return null;
                  }
                  transform.localPosition = origPos;
           }

    }



    d. To use this shake-effect as response to in-game interactions, like an impacting enemy attack, simply call the co-routine from the other script. The first float is the duration and the second is the maginitude (the distance the shake travels). For small getting-hit shake, try 0.1f and 0.2f. For a big impact, go up to 0.5f:

    add the bold-faced lines to the desired script:
           public CameraShake cameraShake;

           void Start(){
                  cameraShake = GameObject.FindWithTag("MainCamera").GetComponent<CameraShake>();
           }

           void [desired function](){
                  cameraShake.ShakeCamera(0.15f, 0.3f);
           }
    }



     ADDING AUDIO (SFX):
    Audio effects are one of the most powerful ways to make gameplay interaction meaningful and impactful!

    To add a sound effect to an object:
    a. Drag an audio clip onto the object in the Hierarchy (.WAV for short sounds, .MP3 for longer music).
    This adds an AudioSource Component to the object with the clip loaded.

    b. The AudioSource component has a bool "Play on Awake" turned on by default.
    This bool causes audio clips to play immediately when the Scene starts.
    Turn off Play on Awake for all AudioSources except ambient music that you DO want to play immediately.

    c. Next, add a script to play the audio when desired (or add to an existing script):
    Start with a public variable of type AudioSource at the top of the script:
           public AudioSource jumpSFX;

    Then put the Play() command where you want the sound to be activated, typically paired with a VFX, like inside a Input.GetButtonDown() condition:
                  
          if (Input.GetButtonDown("Jump"){
                  jumpSFX.Play();
           }

    To make a looping sound, like footsteps, and not get a clicking sound when you play:
    An audio called by an Update() function will happen every frame by default, getting 1 frame into the clip before it is re-started. To solve this we put the AudioSource.Play(); command inside of an if-condition that checks to see if the clip is not already playing (not = !).

    Here is code from the playerMove.cs script Update() function in this tutorial, with Audio parts set bold:
                 if (Input.GetAxis("Horizontal") != 0){
                         animator.SetBool ("Walk", true);
                         if (!WalkSFX.isPlaying){
                                WalkSFX.Play();
                         }
                  } else {
                         animator.SetBool ("Walk", false);
                         WalkSFX.Stop();
                  }

    In order to play a sound NOT intended to loop, like an impact or an explosion, put the AudioSource.Play(); command in a function that is called only once, like an OnTriggerEnter2D.
    Pair it with a visual effect: Instantiate a Particle System or sprite animation, change color, add screenshake, etc:

    If the triggering object is a pickup (meant to be destroyed after the Player touches it) the destroy method must be delayed so the audio has time to play.

    The below script uses a Coroutine to create a delay. The script is applied to an Empty Game Object, the art is a child of this GO, and on impact with the Player both the Collider and art Renderer are turned off before the Coroutine is called. This makes the pickup seem to disapear before it is actually destroyed, so the audio has time to play:
    myPickup.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class myPickup : MonoBehaviour{
           public AudioSource pickupSFX;
           public float destroyDelay = 1f;

           public void OnTriggerEnter2D(Collider2D other){
                  if (other.gameObject.tag == "Player"){
                         gameObject.GetComponent<Collider2D>().enabled = false;
                         gameObject.GetComponentInChildren<SpriteRenderer>().enabled = false;
                         pickupSFX.Play();
                         StartCoroutine(destroyPickup());
                  }
           }

           IEnumerator destroyPickup(){
                  yield return new WaitForSeconds(destroyDelay);
                  Destroy(gameObject);
           }
    }

    To Play Multiple sound clips from the same object (like the player):
    1. Select the object and [Add Component] Audio > AudioSource, 1 for each clip as you want added (only one clip can just be dragged onto the object to make an AudioSource; after that, instead of making a new Component, it just replaces the clip already there, so the Components need to be added manually).
    2. Drag the clips one by one into the AudioSource Components. Turn off "Play On Awake" for each.
    3. In your script/s on this object, make sure the class variables for AudioSource are each public, so they are exposed in the Inspector.
    4. Drag the AudioSource Components into each script slot in the Inspector (LeftClick-drag the Component rolldown bar), so you can choose exactly which clip is activated by which variable.


    d. Add Mixer: The volume control tutorial for making a pause menu includes creating an Audio Mixer for your game. Load this mixer into the mixer slot of the AudioSource, to put that sound clip under the Mixer's control.



    EXTRA: This cool video tutorial offers a system for keeping all game sounds on one "SoundManager" object, and calling those sounds as needed from other scripts. It also includes an example of using Switch Case.



     USER INTERFACE ANIMATION: TRANSITIONS WITH TWEEN:
    Your interface can be juicy, too! Open a menu by scaling horizontally, have buttons appear progressively, etc. Use code to "Tween" the motion using a library like LeanTween (free) or DoTween.

    See this powerpoint for steps to learn tweening:
    Short code to animate small, impactful transforms or color changes!

    Also, try this video and tutorials on LeanTween installation and easing (for more controlled motion).

    STEPS:
    1. In your MainMenu Scene, create a UI > Button named "Button_MENU"
    (can also use a UI > Legacy > Button, with small script adjustments)
    Size 300 x 80, and decide Normal / Highlight colors. Set child Text: size=45, bold.
    Make into a Prefab, then duplicate for 3 total buttons: "Button_Play", "Button_Credits", "Button_Quit".
    Arrange in the Canvas as desired. Set the text content for each, and add OnClick functionality.

    2. Create a new C# script using one of the tween scripts below.
    Add the script to all three buttons.
    For each button, click the choice of button (isButton1, isButton2, or isButton3)

    ButtonsTween_AlphaScale.cs: Use to make buttons appear one by one (fade and scale in with a bounce).

    ButtonsTween_AlphaScale.cs
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    using TMPro;

    public class ButtonsTween_AlphaScale : MonoBehaviour{
           public AnimationCurve curveScale = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
           public AnimationCurve curveAlpha = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
           float elapsed = 0f;
           Vector3 startScale;
           Image thisImage;
           private TextMeshProUGUI buttonText;
           //private Text buttonText // legacy version

           public bool isButton1 = false;
           bool doButton1 = false;
           public bool isButton2 = false;
           bool doButton2 = false;
           public bool isButton3 = false;
           bool doButton3 = false;

           float timer = 0;
           float button1Timer = 0.5f;
           float button2Timer = 0.75f;
           float button3Timer = 1f;

           void Start(){
                  startScale = transform.localScale;
                  thisImage = GetComponent<Image>();
                  thisImage.color = new Color(2.55f, 2.55f, 2.55f, 0f);
                  buttonText = GetComponentInChildren<TextMeshProUGUI>();
                  //buttonText = GetComponentInChildren<Text>(); // legacy version
                  buttonText.color = new Color(2.55f, 2.55f, 2.55f, 0f);
           }

           void FixedUpdate () {
                  timer += Time.deltaTime;
                  if (timer >= button1Timer){doButton1=true;}
                  if (timer >= button2Timer){doButton2=true;}
                  if (timer >= button3Timer){doButton3=true;}

                  if (
                         ((isButton1) && (doButton1))
                         || ((isButton2) && (doButton2))
                         || ((isButton3) && (doButton3))
                  ){
                         // Tween Move:
                         transform.localScale = startScale * curveScale.Evaluate(elapsed);

                         // Tween Alpha:
                         if (elapsed <= 1f){
                                float newAlpha = curveAlpha.Evaluate(elapsed);
                                thisImage.color = new Color(2.55f, 2.55f, 2.55f, newAlpha);
                                buttonText.color = new Color(2.55f, 2.55f, 2.55f, newAlpha);
                         }
                         elapsed += Time.deltaTime;
                  }
           }
    }

    ButtonsTween_AlphaMove.cs: Use this script to fade-in and move the buttons into position:

    ButtonsTween_AlphaMove.cs
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    using TMPro;

    public class ButtonsTween_AlphaMove : MonoBehaviour{
           public AnimationCurve curveMove = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
           public AnimationCurve curveAlpha = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
           float elapsed = 0f;
           float elapsedMove = 0f;
           Image thisImage;
           public TextMeshProUGUI buttonText;

           public bool isButton1 = false;
           bool doButton1 = false;
           public bool isButton2 = false;
           bool doButton2 = false;
           public bool isButton3 = false;
           bool doButton3 = false;

           float timer = 0;
           float button1Timer = 0.5f;
           float button2Timer = 1.5f;
           float button3Timer = 2f;

           float preOffsetPos;
           float startOffset = 100f;
           Vector3 startButtonPos;

           void Start(){
                  preOffsetPos = transform.position.y; //save the destination
                  startButtonPos = transform.position;
                  startButtonPos.y += startOffset;
                  transform.position = startButtonPos; //set the start position

                  thisImage = GetComponent<Image>();
                  thisImage.color = new Color(2.55f, 2.55f, 2.55f, 0f);
                  buttonText = GetComponentInChildren<TextMeshProUGUI>();
                  buttonText.color = new Color(2.55f, 2.55f, 2.55f, 0f);
           }

           void FixedUpdate () {
                  timer += Time.deltaTime;
                  if (timer >= button1Timer){doButton1=true;}
                  if (timer >= button2Timer){doButton2=true;}
                  if (timer >= button3Timer){doButton3=true;}

                  if (
                         ((isButton1) && (doButton1))
                         || ((isButton2) && (doButton2))
                         || ((isButton3) && (doButton3))
                  ){
                         // Tween Move:
                         if(startButtonPos.y >= preOffsetPos){
                                startButtonPos.y -= curveMove.Evaluate(elapsedMove) * startOffset;
                                transform.position = startButtonPos;
                         }
                        
                         // Tween Alpha:
                         if (elapsed <= 1f){
                                float newAlpha = curveAlpha.Evaluate(elapsed);
                                thisImage.color = new Color(2.55f, 2.55f, 2.55f, newAlpha);
                                buttonText.color = new Color(2.55f, 2.55f, 2.55f, newAlpha);
                         }
                         elapsed += Time.deltaTime;
                         elapsedMove += (Time.deltaTime / 10f);
                  }
           }
    }


    Animation Curves can be adjusted in Unity directly, by clicking on the curve in the Inspector. Right click to add a control point on the curve. Select a control point to adjust tangents.

    Or, edit the scale bounce script to include this specific scale curve (copy one of the curve scripts below, then in Unity right-click the curve and hit "paste"):
      UnityEditor.AnimationCurveWrapperJSON:{"curve":{"serializedVersion":"2", "m_Curve": [{"serializedVersion":"3", "time":0.0, "value":0.0, "inSlope":3.9950766563415529, "outSlope":3.9950766563415529, "tangentMode":0, "weightedMode":0, "inWeight":0.0, "outWeight":0.10047300904989243}, {"serializedVersion":"3", "time":0.7080852389335632, "value":1.2484225034713746, "inSlope":0.002636743476614356, "outSlope":0.002636743476614356, "tangentMode":0, "weightedMode":0, "inWeight":0.268322616815567, "outWeight":1.0}, {"serializedVersion":"3", "time":1.0, "value":1.0, "inSlope":-1.6685060262680054, "outSlope":-1.6685060262680054, "tangentMode":0, "weightedMode":0, "inWeight":0.5478678345680237, "outWeight":0.0}], "m_PreInfinity":2, "m_PostInfinity":2, "m_RotationOrder":4}}

    And this for the opacity curve:
      curve:UnityEditor.AnimationCurveWrapperJSON:{"curve":{"serializedVersion":"2", "m_Curve": [{"serializedVersion":"3", "time":0.0, "value":0.0, "inSlope":0.012769672088325024, "outSlope":0.012769672088325024, "tangentMode":0, "weightedMode":0, "inWeight":0.0, "outWeight":0.46172669529914858}, {"serializedVersion":"3" ,"time":0.5594940185546875, "value":0.9989122152328491, "inSlope":0.007558754645287991, "outSlope":0.007558754645287991, "tangentMode":0, "weightedMode":0, "inWeight":0.29996269941329958, "outWeight":0.18988880515098573}, {"serializedVersion":"3", "time":1.0, "value":1.0, "inSlope":0.01713709533214569, "outSlope":0.01713709533214569, "tangentMode":0, "weightedMode":0, "inWeight":0.23450583219528199, "outWeight":0.0}], "m_PreInfinity":2, "m_PostInfinity":2, "m_RotationOrder":4}}


     SCREEN FADE (FADE TO BLACK):
    Has your player died? Fade to black! Are they awakening from a dream? Fade to white!

    This script assumes end scenes "End_Win" and "End_Lose". Be sure the GameHandler is at 0,0,0.

    Create a new GameObject > 2D > Sprite > Rectangle. In the Inspector name it "fadeBlack". Set color = black, position = 0,0,0, and size = 1000 x 600. Drag onto GameHandler to make it a child.

    Create a new script "ScreenFadeOutIn.cs" and add to the GameHandler.
    Drag the FadeBlack object into the fadeBlack script slot.
    ScreenFadeOutIn.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class ScreenFadeOutIn : MonoBehaviour{
           public GameObject fadeBlack;
           public Renderer fadeBlackRend;
           private bool timeToFadeOut = false;
           private bool timeToFadeIn = false;
           private float fadeAlpha = 0f;
           private string sceneName;

           void Awake(){
                  sceneName = SceneManager.GetActiveScene().name;

                  fadeBlack.SetActive(false);
                  fadeBlackRend = fadeBlack.GetComponent<Renderer>();

                  if ((sceneName == "End_Lose") || (sceneName == "End_Win")){
                         //Start the object opacity at fully visible
                         fadeBlackRend.material.color = new Color(2.55f, 2.55f, 2.55f, 1f);
                         StartCoroutine(FadedIn());
                  } else {
                         //Start the object opacity at zero (invisible)
                        fadeBlackRend.material.color = new Color(2.55f, 2.55f, 2.55f, 0f);
                  }
           }

           void FixedUpdate(){
                  if (timeToFadeOut){
                         fadeAlpha += 0.005f;
                         fadeBlackRend.material.color = new Color(2.55f, 2.55f, 2.55f, fadeAlpha);
                         if (fadeAlpha >= 1f){fadeAlpha=1f;}
                  }
                  else if (timeToFadeIn){
                         fadeAlpha -= 0.005f;
                         fadeBlackRend.material.color = new Color(2.55f, 2.55f, 2.55f, fadeAlpha);
                         if (fadeAlpha <= 0f){fadeAlpha=0f;}
                  }
           }

           public void FadeOut(){
                  fadeBlack.SetActive(true);
                  timeToFadeOut = true;
           }

           IEnumerator FadedIn(){
                  yield return new WaitForSeconds(2f);
                  timeToFadeIn = true;
                  fadeBlack.SetActive(true);
                  //Debug.Log("End Scene Fading");
           }
    }

    add to GameHandler.cs script (on player death, for instance):
                  gameObject.GetComponent<ScreenFadeOutIn>().FadeOut();


     
    [2H]: GAME TIMER
    Display Timer   |   Triggered Timer   |   Simple Delay   |   Energy Recharge (dual timers)


      DISPLAY TIMER
    SUMMARY. A timer on a level can be explicit (displayed on a text objet in the HUD) or implied (drive the motion of walls closing in on the player). We run the timer in the FixedUpdate() function in order to get reliable results across machines.

    a. Create a UI > Legacy > Text object. In the Inspector name it "textTimer", set position and size of the text object at the top (try width=160 and height=80). Set font larger (try 40), bold-faced, and align=centered. Set text field = "??".


    b. Create a new C# script called "GameTimer.cs" with the following content.
    Drag this script onto the GameHandler.
    Drag the textTimer Text object into the timerText script slot.

    GameTimer.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class GameTimer : MonoBehaviour {
           public int timer = 0;
           private float theTimer = 0f;
           public GameObject timerText;

           void FixedUpdate(){
                  theTimer += 0.01f;
                  if (theTimer >= 1f){
                         timer +=1;
                         theTimer = 0;
                         UpdateTimer();
                  }
          }

          public void UpdateTimer(){
                Text timeTextTemp = timerText.GetComponent<Text>();
                timeTextTemp.text = "" + timer;
          }
    }



      TRIGGERED TIMER
    What if you want a timer for an action?

    Let's say you want a timer that only activates when a player does a thing (in this case, hit the number 8 on the keyboard, but it could be used for when hitting a collider, etc).

    DelayedAction1.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class DelayedAction1 : MonoBehaviour {
           public float timer =2f;       //set the number of seconds here
           private float theTimer = 0f;
           public bool doTheThing = false;

          void Start(){
               theTimer = timer;
          }

          void Update(){
                if (Input.GetKeyDown("8")){
                      doTheThing = true;
                }
          }

           void FixedUpdate(){
                if (doTheThing == true){
                      theTimer -= 0.01f;
                      Debug.Log("time: " + theTimer);
                      if (theTimer <= 0){
                            theTimer = timer;
                            Debug.Log("I do the thing!");       //can be replaced with the desired commands
                            doTheThing = false;
                        }
                  }
           }
    }


    PIE CHART TIMER: Want to display remaining time graphically as a depleting circle?
    1. In the Canvas create a UI > Image, set it to the knob sprite and a dark color. Name it "TimerBG".
    2. Duplicate the circle [Ctrl/Cmd]+[d], name it "TimerCharge".
           Set size = 90 x 90 and choose a light color.
           Drag onto TimerBG to make it a child.
           Change Image Type of TimerCharge from simple to "Filled".

    3. Add the script below to the GameHandler, and drag TimerCharge into the empty script slot.

    CircleTimer.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.UI;

    public class CircleTimer : MonoBehaviour {
           public float timerMax = 10f;       //set the number of seconds here
           private float theTimer = 0f;
           public bool doTheThing = false;

           public Image timerCircleDisplay;

          void Start(){
               timerCircleDisplay.gameObject.SetActive(false);
               theTimer = timerMax;
          }

          void Update(){
                //test functionality. Normally set=true by external script.
                if (Input.GetKeyDown("8")){
                      doTheThing = true;
                }
          }

           void FixedUpdate(){
                if (doTheThing == true){
                      theTimer -= 0.01f;
                      Debug.Log("time: " + theTimer);
                      timerCircleDisplay.gameObject.SetActive(true);
                      timerCircleDisplay.fillAmount = theTimer / timerMax;

                      if (theTimer <= 0){
                            theTimer = timerMax;
                            Debug.Log("I do the thing!");       //can be replaced with the desired commands
                            doTheThing = false;
                        }
                  }
           }

           //public function to be accessed by other scripts to activate the timer.
           public void TimeToDoTheThing(){
                  doTheThing = true;
                  //other commands when turnign on timer can go here.
           }
    }



      SIMPLE DELAY: COROUTINE
    What if you just want to delay an action?
    Of course, if you do not need to display the increments of a countdown, the easiest way to delay a command for a specfic time is to simply use a CoRoutine.

    DelayedAction2.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class DelayedAction2 : MonoBehaviour {
           public float timer =2f;       //set the number of seconds here

          void Update(){
                if (Input.GetKeyDown("8")){
                      StartCoroutine(myDelay());
                }
          }

           IEnumerator myDelay(){
                 yield return new WaitForSeconds(timer);
                 Debug.Log("I do the thing!");       //can be replaced with the desired commands
          }
    }



      RECHARGE SYSTEM:
    Does your player have a power that they can only use for limited amounts of time, and then must wait for it to recharge, like a shield, flashlight, etc? Use a double timer!

    RechargeTimer.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class RechargeTimer : MonoBehaviour {
           public float energyMax =3f;       //set the number of seconds here (both floats can be static)
           private float energyTimer = 0f;
           public bool thingOn = false;

          void Start(){
               energyTimer = energyMax;
          }

          void Update(){
                if (Input.GetKeyDown("8")){
                      thingOn = !thingOn;           //reverse the bool, so keypress is a toggle
                }
          }

           void FixedUpdate(){
                if (thingOn == true){
                      energyTimer -= 0.01f;
                      Debug.Log("energy down: " + energyTimer);  //replace with the desired ability display
                      if (energyTimer <= 0){
                            thingOn = false;                                   // time has run out
                      }
                 } else if ((thingOn == false) && (energyTimer < energyMax)) {
                      energyTimer += 0.01f;
                      Debug.Log("energy up: " + energyTimer);  //replace with the desired ability display
                 }
           }
    }


    OR: Want a display in the HUD to visualize the decrease and increase in the charge value?

    1. Create a GameObject > UI > Image.
    In the Inspector name it "ChargeCircleBG", set width and height = 120, set SourceImage to Knob, set color to a dark hue. Locate in Canvas as desired.

    2. Duplicate ChargeCircleBG. In Inspector name duplicate "ChargeCicle", set width and height = 90, set color = white. Drag ChargeCircle onto Charge CircleBG to make it a child.

    3. Create a new C# script "RechargeTimer2.cs". Add the following content, save, and in Unity drag onto the ChargeCircleBG object. Drag ChargeCicle into the Display script slot.

    RechargeTimer2.cs script:
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.UI;

    public class RechargeTimer2 : MonoBehaviour {
           public float energyMax =3f;       //set the number of seconds here (both floats can be static)
           private float energyTimer = 0f;
           public bool thingOn = false;

           public GameObject display;

           void Start(){
               energyTimer = energyMax;
            }

           void Update(){
                if (Input.GetKeyDown("8")){
                      thingOn = !thingOn;           //reverse the bool, so keypress is a toggle
                }
            }

           void FixedUpdate(){
                  if (thingOn == true){
                         energyTimer -= 0.01f;
                         Debug.Log("energy down: " + energyTimer);  //replace with the desired ability display

                         Color oldCol = display.GetComponent<Image>().color;
                         if (oldCol.a > 0.01f){
                                 float r = oldCol.r - 0.005f;
                                float g = oldCol.g - 0.005f;
                                 float b = oldCol.b - 0.005f;
                                float a = oldCol.a - 0.005f;
                                 display.GetComponent<Image>().color = new Color(r,g,b,a);
                                 //float x = display.localScale.x/1.005f;
                                 //float y = display.localScale.y/1.005f;
                                 //float z = display.localScale.z;
                                 //display.localScale = new Vector3(x, y, z);
                         }

                         if (energyTimer <= 0){
                                thingOn = false;                                   // time has run out
                         }
                  } else if (thingOn == false){
                         if (energyTimer < energyMax) {
                                energyTimer += 0.01f;
                                Debug.Log("energy up: " + energyTimer);  //replace with the desired ability display

                                Color oldCol = display.GetComponent<Image>().color;
                                if (oldCol.a < 1f) {
                                       float r = oldCol.r + 0.01f;
                                       float g = oldCol.g + 0.01f;
                                       float b = oldCol.b + 0.01f;
                                       float a = oldCol.a + 0.01f;
                                       display.GetComponent<Image>().color = new Color(r,g,b,a);
                                       //float x = display.localScale.x * 1.005f;
                                       //float y = display.localScale.y * 1.005f;
                                       //float z = display.localScale.z;
                                       //display.localScale = new Vector3(x, y, z);
                                }
                         }
                  }
            }
    }



     
    [2I]: DRAG AND DROP
    Drag and Drop  and  Puzzles   |   Player Notepad

     DRAG AND DROP
    SUMMARY: Are you making an exploration game, and want the player to grab elements for their inventory, or move them out of the way? or do you want players to use their mouse to build a home or character from multipel sprites? You need drag and drop!

    OPTION 1: 2D SPRITE MOUSE DRAG AND DROP (source video)
    Want players to customize their character in game?
    Or move scenery around? Or make a puzzle?
    Start with Drag and Drop.


    a. Add GameObject > 2D Objects > Sprite objects to the Hierarchy, set position Z = 0. Add art as desired.

    b. For each Sprite [Add Component] Physics2D > BoxCollider2D, set to isTrigger (to detect OnMouseOver actions).

    c. Add a new C# script named "DragAndDrop.cs", with the following content. Add to all of these objects.


    DragAndDrop.cs
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;

    public class DragAndDrop : MonoBehaviour {

           private bool selected;

           void Update () {
                  if (selected == true) {
                         Vector2 cursorPos = Camera.main.ScreenToWorldPoint (Input.mousePosition);
                         transform.position = new Vector2 (cursorPos.x, cursorPos.y);
                  }

                  if (Input.GetMouseButtonUp (0)) {
                         selected = false;
                  }
           }

           void OnMouseOver(){
                  if (Input.GetMouseButtonDown (0)) {
                         selected = true;
                  }
           }

    }
    To take this further, try adding tags and other scripts to individual objects that create specific interactions when they collide with specific others.


    OPTION 2: CANVAS IMAGE DRAG-DROP (source video)
    This system is for 2D images in the UI Canvas system (for enhancing inventory systems).

    a. Create two UI > Images.
        Name them "InventoryBox" and "PickUp".

    b. Select Pickup, [Add Component] Layout > Canvas Group (gives control of transparency and raycasting).

    c. Create a new C# script named "UI_DragDrop.cs", with the following content.
        Save and drag onto PickUp.
        This script creates events for the many stages of clicking on, dragging, and clicking off something, each of which can contain commands.
    UI_DragDrop.cs
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using
    UnityEngine.UI;
    using UnityEngine.EventSystems;

    public class UI_DragDrop : MonoBehaviour, IPointerDownHandler, IBeginDragHandler, IEndDragHandler, IDragHandler, IDropHandler {

           [SerializeField] private Canvas myCanvas;

           private RectTransform rectTransform;
           private CanvasGroup canvasGroup;

           private void Awake(){
                  rectTransform = GetComponent<RectTransform>();
                  canvasGroup = GetComponent<CanvasGroup>();
          
    }

           public void OnBeginDrag(PointerEventData eventData){
                  canvasGroup.alpha = 0.6f;
                  canvasGroup
    .blocksRaycasts = false;
           }

           public void OnDrag(PointerEventData eventData){
                  rectTransform.anchoredPosition += eventData.delta / myCanvas.scaleFactor;
           }

           public void OnEndDrag(PointerEventData eventData){
                  canvasGroup.alpha = 1f;
                  canvasGroup
    .blocksRaycasts = true;
           }

           public void OnPointerDown(PointerEventData eventData){

           }

           public void OnDrop(PointerEventData eventData){
                  throw new System.NotImplementedException();
           }

    }

    d. Create a new C# script named UI_ItemSlot, with the following content. Save and drag onto the InventoryBox. This script snaps the dropped item to the Box center. Addional commands can be added under the if-condition to send messages to the GameHandler about the object that has been slotted.

    UI_ItemSlot.cs
    using System.Collections.Generic;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.EventSystems;

    public class UI_ItemSlot : MonoBehaviour, IDropHandler {

            public void OnDrop(PointerEventData eventData){
                  if (eventData.pointerDrag != null){
                         Vector2 slotCenter = GetComponent<RectTransform>().anchoredPosition;
                         eventData.pointerDrag.GetComponent<RectTransform>().anchoredPosition = slotCenter;
                  }
           }
    }

    e. Hit [Play] to test: click on the PickUp to drag it around, notice it turns semi-transparent when dragged.
    Drop partially on the InventoryBox to snap to the IncentoryBox center.



     
    DRAG-AND-DROP PUZZLE EXAMPLES

    Full Sandwich-Making Puzzle using in-scene Drag-and-Drop (added Sp 2023).
    6 recipes chosen at random, color-change feedback. To open and view:
        Download the .UnityPackage.
        In Unity, RightClick the Project panel, choose Import Package.
        A window will open showing the package contents. Hit [OK] to import all.
        Open the SandwichWork folder. Find the SANDWHICH_SYSTEM Prefab.
        Drag this Prefab into a new Scene and hit [Play].



     SIMPLE CANVAS NOTEPAD
    SUMMARY: Are you making an exploration game, and want the player to take notes along the way?
    Here is a simple system that lets the the player continuously add to a static string.

    a. Select your Canvas and create a UI > Image.
    Name it Notepad and choose color and location.
    It needs to be big enough to hold a few elements: try 200x120.

    b. Create a UI > Input Field.
        Parent InputField to Notepad: in the Hierarchy, drag the InputField onto Notepad to make it a child.
        In the Scene, postion the Input Field near the top of the Notepad image, leaving ample space below.
        In the Hierarchy click the InputField triangle to view its children.
        Select Placeholder. In Inspector change default text to "Enter a note" and set Font Size = 30.
        Select Text and in Inspector set font = 30 also.
        Finally, in the Hierarchy move Text above Placeholder.

    c. Create a GameObject > UI > Legacy > Button.
        Move and scale to locate next to the Input Field.
        Select its Text and change font to 30 and text to "Enter" or just ">>".

    d. Create a GameObject > UI >Text .
        Set Rect to fill the space below the Input Field and Button.
        In the Inspector name it TextNotes. Set Font Size = 30.

    e. Add a new C# script named "Notepad.cs", with the following content.
        Add the scipt to the Notepad image object.
        Populate the inputField and textDisplay script slots with the objecst from the Hierarchy.
        Select the button, add an OnClick event [+].
        Drag the Notepad object into [None] and add this function: Notepad > UpdateNotes().


    f. Drag the Notepad object into the Project panel to make it a Prefab.
        Add this prefab to the Canvas in all scenes in your game.

    G. Optionally, add a function / button in your PauseMenu to make the notepad visible or not.

    Notepad.cs
    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine.UI;

    public class
    Notepad : MonoBehaviour {

            public static string myNotes;
            public GameObject inputField;
            public GameObject textDisplay;

            public void Start(){
                    UpdateNotes();
            }

            public void UpdateNotes(){
                    myNotes = myNotes + " " + inputField.GetComponentInChildren<
    Text>().text;
                    textDisplay.GetComponent<
    Text>().text = myNotes;
                    inputField.GetComponent<InputField>().text = "";
            }
    }



     
    [2J]: AUDIO
    Player: Multiple Audio Files   |   Footstep Variation
    Play music at Specific Timestamp   |   Music Manager: Continuous Play
      |  Spatial Audio 

    Sound Effects (SFX) are best imported into Unity as .WAV (music is best as .MP3).
    Sound effects are added in Unity to GameObjects just like any other Component.
    The Component for an audio clip is called "Audio Source", essentially the transmitter.
    The reciever is the "Audio Listener", typically on the Camera (one Listener, many Sources).

    Sounds can be easily triggered through player Input, OnCollision or OnTrigger interactions, a Button press, or a timer countdown.

    We will primarily use these functions:
    AudioSource.Play()     is used to play a clip. Given an AudioSource crySFX, then crySFX.Play().
    AudioSource.Stop()    used to stop a clip. Helpful to stop looping clips, like care sounds.
    AudioSource.isPlaying()     check to see if a clip is playing before playing it. Avoids the "click" of constantly restarting a sound of a highly repeated action, like footsteps.




     PLAYER: Multiple Audio Files for Distinct Actions
    SUMMARY: Player Actions are better with (subtle) sounds!
    Want separate audio to signify an attack, getting hurt, a jump, etc? Add all the sound clips to the Player as separate AudioSource Components and trigger them in the Player scripts.

    a. CREATE YOUR SOUNDS:
    Prepare your sounds as .WAV (if recording on an iphone, use an online converter).
    Edit to remove empty time before and after each sound. Audacity rcommended.
    Save each clip with short names: player_jump, player_attack, etc.
    Import the files into the Unity Project panel, into a fiolder called Audio.

    b. ADD AUDIOSOURCE TO PLAYER IN CHILD OBJECTS:
    Open the Player prefab.
    RightClick the parent object to create a new child Empty Game Object.
    Name it "AUDIO" (all caps, because it is meant to be a folder).
    RightClick AUDIO to create a new Empty Game Object named for the sound it will hold (example: "Punch_SFX").
    [Add Component] Audio Source.
    Drag a sound effect into the Audio Clip script slot.
    Turn off "Play on Awake", set Volume down to about 40%.
    If it exists, add your Music Mixer to the Output slot (see Pause Menu steps).

    c. DUPLICATE:
    Create a new AUDIO child object for each desired sound effect, changing the object name and Audio Clip fro each.


    d. ADD TO SCRIPTS:
    In the Player scripts, add a class variable for each relevant sound effect:

            public AudioSource jumpSFX;

    In the function that triggers action, like jumping, add the command:
                    jumpSFX.Play();

    For an action that will be repeated a lot, to prevent sounds oevrlapping, add a condition:
                    if (jumpSFX.isPlaying == false){
                            jumpSFX.Play();
                    }

    e. DRAG THE AUDIOSOURCE COMPONENTS UP INTO THE SCRIPT SLOTS
    Drag each AudioSource components (selecting them by their header bar) into the script slots.
    Identify them by the Audio clip. Hit [Play] and perform the actions to hear the sounds.




     EXAMPLE 2: Footstep Variation:
    The same footstep sound with every move can get tiresome on the ears.
    Try creating 6 seoparate footstep MP3s with subtle differences, apply them to teh player as explained above, and randomizing which one is played when the player is moving:


    add to the Player Move script:

    //1. Add these variables to the top of the class
           private AudioSource StepToPlay;
          
    public AudioSource step1;
           public AudioSource step2;
           public AudioSource step3;
           public AudioSource step4;
           public AudioSource step5;
           public AudioSource step6;


    //2. In the Update() function, whenplayer is moving, add calls to PlaySteps and StopSteps:
                  if ((Input.GetAxisRaw("Horizontal")!= 0) || (Input.GetAxisRaw("Vertical")!= 0)){
                         PlaySteps();
                  } else {
                         StopSteps();
                  }
          
    //3. Add these two functions:
           public void PlaySteps(){
                 if ((StepToPlay !=null)&&(StepToPlay.isPlaying)){
                      
    return;
                 } else {
                       int StepNum = Random.Range(1, 6);

                       if (StepNum == 1){ StepToPlay = step1;}
                       else if (StepNum == 2){ StepToPlay = step2;}
                       else if (StepNum == 3){ StepToPlay = step3;}
                       else if (StepNum == 4){ StepToPlay = step4;}
                       else if (StepNum == 5){ StepToPlay = step5;}
                       else if (StepNum == 6){ StepToPlay = step6;}

                       StepToPlay.Play();
                 }
           }

           public void StopSteps(){
                 if ((StepToPlay != null) && (StepToPlay.isPlaying)){
                       StepToPlay.Stop();
                 }
           }





      PLAY MUSIC AT A SPECIFIC TIMESTAMP
    SUMMARY: Interrupt music and resume at the same part (source).

    Add the following script to the Audio Manager

    AudioInterrupt.cs:
    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;

    public class
    AudioInterrupt : MonoBehaviour {

            public AudioSource audioSource;
            private float stopTimestamp = 12.5f;
           
            void Update(){
                    if (Input.GetKeyDown("i")) {
                            PlayMusicAtBegin();
                    }
                    if (Input.GetKeyDown("o")) {
                            StopMusic();
                    }
                    if (Input.GetKeyDown("p")) {
                            PlayMusicAtTime(stopTimestamp);
                    }
            }

            public void PlayMusicAtBegin(){
                    audioSource.time = 0.0f;
                    audioSource.Play();
            }

            public void StopMusic(){
                    stopTimestamp = audioSource.time;
                    Debug.Log("Stopped audio at: " + stopTimestamp);
                    audioSource.Stop();
            }

            public void PlayMusicAtTime(float timeStamp){
                    if (timeStamp > audioSource.clip.length){
                            return;
                    } else {
                            audioSource.time = timeStamp;
                            audioSource.Play();
                    }
            }
    }




     MUSIC MANAGER: CONTINUOUS PLAY
    SUMMARY: A prefab should be added to all scenes for playing level music.
    It should include an Audio Source Component.
    If the Music Mixer exists, add it to the Inspector in the Output slot by hitting the hotspot circle on the far right.
    By default, this music will restart in each level. Want the music to continue where the previous scene left off?

    a. Use "DontDestroyOnLoad()" to preserve the object with the music (source).
    Add the following script to the Audio Manager

    BGSoundScript.cs:
    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;

    public class
    BGSoundScript : MonoBehaviour {

            private static BGSoundScript instance = null;

            public static BGSoundScript Instance{
                    get {return instance;}
            }

            void Awake(){
                    if (instance != null && instance != this){
                            Destroy(this.gameObject);
                            return;
                    } else {
                            instance = this;
                    }
                    DontDestroyOnLoad(this.gameObject);
            }
    }



    b. To pause this music, create a script with this command:

    StopMusic.cs:
    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;

    public class
    StopMusic : MonoBehaviour {

            void Start() {
                    BGSoundScript.Instance.gameObject.GetComponent<AudioSource>().Pause();
            }
    }





     SPATIAL AUDIO (source)
    SUMMARY: Audio Source Components on objects in the game can eb set to only be audible within a certain distance. This way, a noisy drill in the street or a radio on a table can feel more believable and interesting to the player.

    a. In the Audio Source Component of the object set Spatial Audio from 2D to 3D (even for a 2D game).

    b. Set Max Distance range down from the default 500 (covers entire Scene) to something much smaller, like 20.



     
    [2K]: INTRO ANIMATIONS
    Opening Comic   |   Player Delivery (Vehicle)
    To help the player understand the experience they are about to enter, it is helpful to ORIENT the player with information about who their character is, where they are, what has happened (an inciting incident) and what they are meant to accomplish (the stakes). There are many ways to accomplish this!

    MAIN MENU TEXT: The simplest way to introduce the premise is to show 1-3 sentences in the MainMenu. Like a story pitch, these should be very concise: "Your village has been attacked by the dreaded Penguin Horde! Can you find enough Puffin allies to save your stollen eggs?"

    INTRO COMIC: More interesting is to include an opening cinematic. An efficient way to add visual storytelling to the start of your game is a quick Intro Comic of 4-5 full-screen panels.
    Draw / paint each at 1280 x 720, 72 ppi, just like the splash screens for the Main Menu, Credits, etc.
    (NOTE: Unity has tools for full cut-scene animation using Cinemachine and Timeline, but that is more work.)

    IN-GAME NPC CHATS: Finally, introductory information can be dlivered in each game Scene through friendly NPCs who share information as the player moves through introductory spaces.
    These spaces can be narrow, like a path or pipe, to direct playr movenment towards the criotical information.
    Do not overload the player with too much information or too many abiolities at once. Introduce just a single idea or feature, and let the Player practice that extensively, and then give more information later.

    An event in game, like the appearance of a major enemy, can be activated by an isTrigger Collider in the area.
    For example, the enemy can be moved into place with LERP to an Empty Game Object, timed with an IEnumerator, accompanied by Cameera-shake (see the Player Delivery example below).

    Consider using all three: a sentence in the main menu, and openign comic, and chatting NPCs to guide the player at teh start of each level.


     OPENING COMIC:
    SUMMARY: Create four or more full-screen comic panels (1280 x 720, 72ppi) and display them as a sequence.
    The following assumes these game Prefabs have been built: GameHandler, Pause Menu, Music Manager.

    1. Create a new framing SCENE for your game:
    a).
    File > New Scene, then File > Save As to name it "LevelComic" in the Scenes folder.
    b). In File > Build Settings, add this scene.
    c). In the GameHandler script, change the Restart() function to go to "LevelComic".
    d). Create a Canvas and Placeholder text:
        GameObject > UI > Canvas. Set to Scale with Screen Size, 1280 x 720.
        RightClick the Canvas to create a UI > Legacy > Text. In the Inspector name it text_Placeholder.
    e). Load in the usual Prefabs for you game:
        Drag into the Hierarchy your GameHandler Prefab. Add text_Placeholder to Text script slots (health, etc).
        Drag onto Canvas your PAUSE_MENU prefab. Drag it into the GameHandler script slot. Activate the OnClick events for all Pause Menu buttons and slider with the GameHandler.
        Add your MusicManager Prefab.

    2. Create the STORY BUTTONS:
    a).
    With the Canvas selected, add a UI > Legacy > Button (ideally, drag into the Canvas a Button_Menu prefab, used for all menu buttons!).
    b).
    Duplicate the button twice for a total of three buttons.
      In the Inspector, name them "button_Next", "button_Back", and "button_Skip".
      Make sure Navigation is chnaged from "Automatic" to "None".
      At the bottom of each click the [+] to create an OnClick() event.
      Open each button, select the text, and change the text field to display button function: NEXT, BACK, SKIP.

    3. Create the COMIC PANELS:
    a).
    RightClick the Hiearchy to create an Empty GameObject.
      In the Inspector name it "Panel" and reset transforms.
    b).
    RightClick Panel to add a 2D Object > Sprite > Square.
      In Inspector name it "panel_art" and set Scale: X = 1.4, Y = 1.4.
    c). In Photoshop, create your comic panel art.
    Each panel should be a full screen: 1280 x 720, 72 ppi.
    (for quick demo purposes, create two panels that can alternate, like these: 01 | 02).
      Import the art panels into the Unity Project panel Assets folder.
      Select the panel_art object in the Hierarchy and drag the panel art into the Source Image slot.
      Duplicate the Panel object for as many panels you want (try 5).
      Move each Panel 20 units to the right: x= 20, 40, 60, 80, etc (or more, if you want more gutter space)