Robbie(Unity2D)项目复盘

场景制作

地图绘制
创建不同的2D Object--Tile map
给不同层设置Sorting Layer (越靠下越在前面显示)
同一个Sorting Layer 的 Order in Layer 可以不同(可实现透明效果的覆盖)
window--2D--Tile Palette
Palette 选择不同绘制的图案
Active Tile map 选择在哪个Layer上绘制

背景布置
给需要添加的背景元素设置设置统一的 Sorting Layer
给火炬等需要发光的物体添加光源(当背景添加材质后可被照亮)(prefab窗口里的 Overrides--Apply all 可以将其添加到预制中)
*Unity--Window--Rendering--Lighting Settings--Ambient Color 可调节主场景的灯光效果 曝光程度

物理组件

地图
给 platforms 添加组件 Tile map Collider 2D
勾选 use by Composite 并添加 Composite Collider 2D(可以将Tile map 的每个小方块变成一个整体的碰撞器)继续绘画的地方也会添加到整体碰撞器中
*添加完 Composite Collider 2D 后会自动添加组件Rigibody 2D,其中的Gravity Scale为1,此时游戏场景在开始后会因重力掉落,所以应将 Body Type 选择为Static,也可将 platforms 直接勾选为static。

人物
为人物添加Rigibody
将 Collision Detection 选择为 Continuous 连续不断判断碰撞
Interpolate(差值 游戏角色在跳起或碰撞墙面等情况时,会发生凹陷)虽不易发现,但可提升手感
添加组件 Box collider 2D,点击 Edit 可自定义碰撞框大小
在 Constraints 中选择 Freeze Rotation Z 可以锁定Z轴(锁定Z轴后,角色不会因为一部分身体在地图边缘位置而翻滚掉落)
*为避免角色在加速撞击到墙面后发生卡在墙上,或因跳跃黏贴在墙上等问题,可在Box collider 2D--Material 中添加物理材质(Friction摩擦力、Bounciness弹力为0)
添加并设置人物的 Layer 为Player
添加并设置 Platforms 的 Layer 为Ground

角色移动(Player Movement)

//在最开始获得 Rigidbody

private Rigidbody2D rb; //定义玩家刚体

在Start中

 rb= GetComponent<RigidBody2D>();

//添加移动参数
上面写[Header("")]可在unity面板中显示
定义xVelocity

获取虚拟轴
代码:

Input.GetButton("虚拟轴名称");
Input.GetButtonDown("虚拟轴名称");
Input.GetButtonUp("虚拟轴名称");

上述三种方法都可以通过虚拟轴名称来获取虚拟轴,但是他们只能判断虚拟轴绑定的按键是否被按下,无法判断正向和负向。
代码:

Input.GetAxis("虚拟轴名称")
Input.GetAxisRaw("虚拟轴名称")

这两种方法获取虚拟轴,当你按下虚拟轴绑定的正向按键,他们会返回正值(最大为1,负值最大为0),按下负向按键会返回负值,区别在于:
第一种方法,在你按下正向按键的时候,它返回的值会从0变化到1,而非瞬间变成1;
第二种方法,则是瞬间变化,按下正向按键则瞬间返回1

*通过Unity栏目:Edit->Project Setting->搜索Input Manager可以查看Unity已经设置好的虚拟轴
以Horizontal为例,首先Horizontal表示水平的意思,这只是这个虚拟轴名称,可以随意更改但是要记住因为在代码里会用到。
Descriptive Name 和 Descriptive Negative Name 分别是正负操作的语言描述。每一个虚拟轴设置都是有正向和负向之分。
Negative表示负向,Positive表示正向。
Negative Button和Alt Negative Button对应的就是负向的实际键盘对应键,二者都是设定具体按键的属性。
Alt Negative Button的值被设定为a,那么键盘上的a就是这个虚拟轴的负向按键。
为了区别正向和负向,按钮有一个值。在按钮不按的时候它为0,按下负向按键,它会逐渐变为-1,变化速度由Sensitivity值决定,值越大,变化速度越快;当你松开此按键,它又会从-1逐渐变灰0,复原速度由Gravity值决定,值越大,变化速度越快,positive同理。
Type值表示你的虚拟轴来源于什么,当前选项为Key or Mouse Button意思是,这个虚拟轴绑定的具体按键可以来源于鼠标按键或者键盘按键。
Axis则表示轴向,当前选项为X axis,意思是水平轴向。向右移动,值就会变为正值,最大为1,移动越快,数值越大,最大为1,向左同理。

移动函数

xVelocity = Input.GetAxis("Horizontal");
rb.velocity = new Vector2(xVelocity * speed, rb.velocity.y);

在FixUpdate中进行调用

人物翻转函数

 if (xVelocity < 0)
        {
            transform.localScale = new Vector3(-1, 1, 1);
        }
 if (xVelocity > 0)
        {
            transform.localScale = new Vector3(1, 1, 1);
        }

在移动函数中尽进行调用

*如果需要下蹲,可在Edit->Project Setting,将jump右击选择Duplicate Array Element进行复制,改名为Crouch,并为其设置按键

GetButton(GetKey):按键激活状态,按键按下后持续有效
GetButtonUp(GetKeyUp):按键松开状态,仅在按键弹起时有效
GetButtonDown(GetKeyDown):按键按下状态,仅在按键按下时有效
区别:

Input.GetKey(KeyCode.S)
Input.GetButton(“Crouch”)

*BoxCollider2D 才能使用.size和.offset来访问两个参数
*判断物理或环境有关的函数要放在 FixedUpdate 里调用

连续跳跃

jumpTime = Time.time + jumpHoldDuration;//(按下跳跃那一刻的瞬时时间+跳跃时长)

当 is jump 时

 if (jumpTime < Time.time)
   {
       isJump = false;
    }

Time.time 为真实时间,按下play时开始计时,所以 Time.time 一定大于 jump time

AddForce 和 Velocity 的区别

Rigidbody.Velocity= (Vector3*Speed) ;//一直都是固定的速度
Rigidbody.AddForce(Vector3*系数);//受力的影响

Rigidbody.velocity:
这个方法是瞬间给物体一个恒定的速度,将物体提升至该速度。
当按下跳跃键的时候,最多只会提升到我们规定的速度以及朝向我们规定的方向。初始速度不变的情况下,跳跃高度也是恒定的。
Rigidbody.AddForce:
这个方法瞬间给物体一个规定好的力
每按一下跳跃键,它会被施加这个恒定的力,它跳跃的初始速度会越变越大,
每次跳跃的高度和前一次相较变得越来越大(在连续跳跃的情况下)

射线检测(RaycastHit2D)

RaycastHit2D 命名= Raycast (Vector2offset, Vector2.rayDiraction, floatlength, LayerMask layer);

其中 Vector2 offset为射线起点,具体应用为new Vecotr2 (float x, float y)
Vector3.rayDiraction为射线方向,大多应用为Vector3.down; Vector3.up; Vector3.forward; Vector3.back; Vector3.left; Vector3.right
float length是射线长度,是一个float定义的数
LayerMask layer是检测识别触发的layer,需要在最开始用LayerMask 命名一个layer,并且在unity中挂载上已设置的layer。

射线可视化
//四个参数分别为:射线发射起始位置,射线方向,颜色,长度

Debug.DrawRay(Vector3 start, Vector3.dir, Color.color, float duration);

悬挂射线判断


IMG_0248.jpg

摄像机跟随
1.windows 打开 Package Manager
点左上角+ 选择add package by name
输入com.unity.cinemachine 点击 add 安装
2.在Hierarchy栏右键 创建2D camera
3.将人物拖拽到 follow️ 中
camera distance 数值调大可以使场景变大
4.add Extension cinemachineConfiner
5.创建空项目 Camera Bounds
勾选 is Trigger
edit collider 可以设置区域
6.将 Bounds 拖拽到confiner 中的 Bounding shape

PlayerMovement 完整代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
    private Rigidbody2D rb;
    private BoxCollider2D coll;//获得原有碰撞体尺寸

    [Header("移动参数")]
    public float speed = 8f;
    public float crouchSpeedDivisor = 3f;//下蹲速度参数

    [Header("跳跃参数")]
    public float jumpForce = 6.3f;//单按跳跃的力
    public float jumpHoldForce = 1.9f;//长按跳跃的力
    public float jumpHoldDuration = 0.1f;//跳跃间隔
    public float crouchJumpBoost = 2.5f;//下蹲跳跃增量
    public float hangingJumpForce = 15f;//悬挂时跳跃

    [Header("状态")]
    public bool isCrouch;
    public bool isOnGround;
    public bool isJump;
    public bool isHeadBlocked;
    public bool isHanging;

    float jumpTime;

    public float xVelocity; //定义x轴的速度

    //碰撞体尺寸 用于下蹲和站立的变化
    Vector2 colliderStandSize;//站立时尺寸
    Vector2 colliderStandOffset;//站立时坐标
    Vector2 colliderCrouchSize;//下蹲时尺寸
    Vector2 colliderCrouchOffset;//下蹲时坐标

    [Header("环境检测")]
    public float footOffset = 0.4f;//中心到左右脚距离
    public float headClearance = 0.5f;//头顶检测距离
    public float groundDistance = 0.2f;//与地面的检测距离
    float playerHeight;//头顶位置
    public float eyeHeight = 1.5f;//眼睛高度
    public float grabDistance = 0.4f;//距离墙的距离
    public float reachOffset = 0.7f;

    public LayerMask groundLayer;

    //按键设置
    bool jumpPressed;//单次按下跳跃
    bool jumpHeld;//长按跳跃
    bool crouchHeld;//长按下蹲
    bool crouchPress;

    RaycastHit2D Raycast(Vector2 offset,Vector2 rayDirection,float length,LayerMask layer)
    {
        Vector2 pos = transform.position;
        RaycastHit2D hit = Physics2D.Raycast(pos + offset, rayDirection, length, layer);
        Color color = hit ? Color.red : Color.green;
        Debug.DrawRay(pos + offset, rayDirection * length);
        return hit;
    }

    //环境判断
    void PhysicsCheck()
    {
        //左右脚射线
        RaycastHit2D leftCheck = Raycast(new Vector2(-footOffset, 0f), Vector2.down, groundDistance, groundLayer);
        RaycastHit2D rightCheck = Raycast(new Vector2(footOffset, 0f), Vector2.down, groundDistance, groundLayer);

        if (leftCheck||rightCheck)
        {
            isOnGround = true;
        }
        else
        {
            isOnGround = false;
        }

        //头顶射线 
        RaycastHit2D headCheck = Raycast(new Vector2(0f, coll.size.y), Vector2.up, headClearance, groundLayer);
        if (headCheck)
        {
            isHeadBlocked = true;
        }
        else
        {
            isHeadBlocked = false;
        }

        float direction = transform.localScale.x;//射线起点方向 左或右
        Vector2 grabDir = new Vector2(direction, 0f);//射线发射方向
        RaycastHit2D blockedCheck = Raycast(new Vector2(footOffset * direction, playerHeight), grabDir, grabDistance, groundLayer);
        RaycastHit2D wallCheck = Raycast(new Vector2(footOffset * direction, eyeHeight), grabDir, grabDistance, groundLayer);
        RaycastHit2D ledgeCheck = Raycast(new Vector2(reachOffset * direction, playerHeight), Vector2.down, grabDistance, groundLayer);

        //悬挂判断及悬挂时状态
        if(!isOnGround && rb.velocity.y<0f && ledgeCheck && wallCheck && !blockedCheck)
        {
            Vector3 pos = transform.position;
            pos.x += ((wallCheck.distance-0.05f) * direction);
            pos.y -= ledgeCheck.distance;
            transform.position = pos;

            rb.bodyType = RigidbodyType2D.Static;//悬挂后静止
            isHanging = true;
        }
    }

    //角色移动函数
    void GroundMovement()
    { 
        if (isHanging)
        {
            return;
        }

        //判断下蹲和站立状态
        if (crouchHeld && !isCrouch && isOnGround)
        {
            Crouch();
        }
        else if (!crouchHeld && isCrouch && !isHeadBlocked)
        {
            StandUp();
        }

        //角色进行移动和翻转
        xVelocity = Input.GetAxis("Horizontal");//-1f 1f 获得虚拟轴 “”中为虚拟轴名称
        if (isCrouch)
        {
            xVelocity /= crouchSpeedDivisor;
        }
        rb.velocity = new Vector2(xVelocity * speed, rb.velocity.y);
        FilpDirection();
    }

    //角色翻转函数
    void FilpDirection()
    {
        if (xVelocity < 0)
        {
            transform.localScale = new Vector3(-1, 1, 1);
        }
        if (xVelocity > 0)
        {
            transform.localScale = new Vector3(1, 1, 1);
        }
    }

    //下蹲函数
    void Crouch()
    {
        isCrouch = true;
        coll.size = colliderCrouchSize;
        coll.offset = colliderCrouchOffset;
    }

    //站立函数
    void StandUp()
    {
        isCrouch = false;
        coll.size = colliderStandSize;
        coll.offset = colliderStandOffset;
    }

    void MidAirMOvement()
    {
        if (isHanging)
        {
            if (jumpPressed)
            {
                rb.bodyType = RigidbodyType2D.Dynamic;
                rb.velocity = new Vector2(rb.velocity.x, hangingJumpForce);
                isHanging = false;
                crouchPress = false;
            }

            if (crouchPress)
            {
                rb.bodyType = RigidbodyType2D.Dynamic;
                isHanging = false;
                crouchPress = false;
            }
        }

        if (jumpPressed && isOnGround && !isJump && !isHeadBlocked)
        {
            if(isCrouch)
            {
                StandUp();
                rb.AddForce(new Vector2(0f, crouchJumpBoost), ForceMode2D.Impulse);
            }

            isOnGround = false;
            isJump = true;

            jumpTime = Time.time + jumpHoldDuration;

            rb.AddForce(new Vector2(0f, jumpForce), ForceMode2D.Impulse);
            jumpPressed = false;

            AudioManager.PlayJumpAudio();
        }
        else if (isJump)
        {
            if (jumpHeld)
            {
                rb.AddForce(new Vector2(0f, jumpHoldForce), ForceMode2D.Impulse);
            }
            if (jumpTime < Time.time)
            {
                isJump = false;
            }
        }

        
    }
    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
        coll = GetComponent<BoxCollider2D>();

        playerHeight = coll.size.y;
        colliderStandSize = coll.size;//站立时完整碰撞体尺寸
        colliderStandOffset = coll.offset;//站立时完整碰撞体坐标
        colliderCrouchSize = new Vector2(coll.size.x, (coll.size.y) * 0.5f);//添加新的尺寸
        colliderCrouchOffset = new Vector2(coll.offset.x, coll.offset.y * 0.5f);
    }
    void Update()
    {
        if (GameManager.GameOver())
        {
            return;
        }

        if (!jumpPressed)
        {
            jumpPressed=Input.GetButtonDown("Jump");
        }

        if (Input.GetButtonDown("Crouch"))
        {
            crouchPress = true;
        }

        jumpHeld = Input.GetButton("Jump");
        crouchHeld = Input.GetButton("Crouch");
        crouchPress = Input.GetButtonDown("Crouch");
    }

    private void FixedUpdate()
    {
        if (GameManager.GameOver())
        {
            return;
        }

        PhysicsCheck();
        GroundMovement();
        MidAirMOvement();
    }
}

动画的实现(Player Animation)

Parameters 包含了我们在 Animator 中使用的所有“参数”,在拥有多个动画短片的控制器中,正是通过 Parameters 中的参数实现了不同动画间的转变。
可以通过点击“+”创建4种类型的参数,它们分别是 Float、Int、Bool 和 Trigger。前三个均属于基本数据类型,最后一个 Trigger 则是一个与 Bool 类似的参数,同样拥有 True 和 False 两种状态。但是不像 Bool 在设置为 True 后会一直维持,Trigger 在被触发后会迅速重置为未触发状态。
*传递字符型的时候可能会出现问题,使用数字编号给 Animator 赋值(ID)
Blend Tree
在 Animator 空白部分右击 Create State--Form New Blend Tree
Add Motion Field 添加动画

IMG_0250.jpg

PlayerAnimation 完整代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerAnimation : MonoBehaviour
{
    Animator anim;
    PlayerMovement movement;
    Rigidbody2D rb;

    int groundID;
    int hangingID;
    int crouchID;
    int speedID;
    int fallID;

    public void StepAudio()//走路音效
    {
        AudioManager.PlayerFootstepAudio(); 
    }

    public void CrouchStepAudio()//下蹲走路声音
    {
        AudioManager.PlayerCrouchFootstepAudio();
    }

    void Start()
    {
        anim = GetComponent<Animator>();
        movement = GetComponentInParent<PlayerMovement>();//访问Player Movement
        rb = GetComponentInParent<Rigidbody2D>();

        groundID = Animator.StringToHash("isOnGround");//将字符型转换为数值型,获得变量的编号
        hangingID = Animator.StringToHash("isHanging");
        crouchID = Animator.StringToHash("isCrouching");
        speedID = Animator.StringToHash("speed");
        fallID = Animator.StringToHash("verticalVelocity");
    }

    void Update()
    {
        anim.SetFloat(speedID, Mathf.Abs(movement.xVelocity));
        //anim.SetBool("isOnGround", movement.isOnGround);
        anim.SetBool(groundID, movement.isOnGround);//传递布尔值
        anim.SetBool(hangingID, movement.isHanging);
        anim.SetBool(crouchID, movement.isCrouch);
        anim.SetFloat(fallID, rb.velocity.y);
    }
}

音效控制(AudioManager)

AudioManager 中创建的各部分音效 public 函数要在对应位置进行调用
*当前对象的引用指的是关键字 this 所代表的对象。可以使用关键字 this 来访问当前对象的属性、方法或索引器。
*若希望在场景切换时不销毁音效

DontDestroyOnLoad(gameObject);//项目保留,使声音一直存在

AudioManager 完整代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;

public class AudioManager : MonoBehaviour
{
    static AudioManager current;

    //存放声音
    [Header("环境声音")]
    public AudioClip ambientClip;
    public AudioClip musicClip;

    [Header("FX音效")]
    public AudioClip deathFXClip;
    public AudioClip orbFXClip;
    public AudioClip doorFXClip;
    public AudioClip startLevelClip;
    public AudioClip winClip;

    [Header("Robbie音效")]
    public AudioClip[] walkStepClips;
    public AudioClip[] crouchStepClip;
    public AudioClip jumpClip;
    public AudioClip deathclip;
    public AudioClip deathVoiceClip;
    public AudioClip jumpVoiceClip;
    public AudioClip orbVoiceClip;

    AudioSource ambientSource;
    AudioSource musicSource;
    AudioSource fxSource;
    AudioSource playerSource;
    AudioSource voiceSource;

    public AudioMixerGroup ambientGroup, musicGroup, FxGroup, playerGroup, voiceGroup;

    //脚步声音
    public static void PlayerFootstepAudio()
    {
        int index = Random.Range(0, current.walkStepClips.Length);

        current.playerSource.clip = current.walkStepClips[index];
        current.playerSource.Play();
    }

    //下蹲时脚步声音
    public static void PlayerCrouchFootstepAudio()
    {
        int index = Random.Range(0, current.crouchStepClip.Length);

       // current.playerSource.clip = current.crouchStepClip[index];
        current.playerSource.Play();
    }

    //环境声音
    void StartLevelAudio()
    {
        current.ambientSource.clip = current.ambientClip;
        current.ambientSource.loop = true;
        current.ambientSource.Play();

        current.musicSource.clip = current.musicClip;
        current.musicSource.loop = true;
        current.musicSource.Play();

        current.fxSource.clip = current.startLevelClip;
        current.fxSource.Play();
    }
    
    //胜利时声音
    public static void PlayerWonAudio()
    {
        current.fxSource.clip = current.winClip;
        current.fxSource.Play();
        current.playerSource.Stop();
    }

    //跳越声音
    public static void PlayJumpAudio()
    {
        current.playerSource.clip = current.jumpClip;
        current.playerSource.Play();

        current.voiceSource.clip = current.jumpVoiceClip;
        current.voiceSource.Play();
    }

    //死亡声音
    public static void PlayDeathAudio()
    {
        current.playerSource.clip = current.deathclip;
        current.playerSource.Play();

        current.voiceSource.clip = current.deathVoiceClip;
        current.voiceSource.Play();

        current.fxSource.clip = current.deathFXClip;
        current.fxSource.Play();
    }

    public static void PlayOrbAudio()
    {
        current.playerSource.clip = current.orbFXClip;
        current.playerSource.Play();

        current.voiceSource.clip = current.orbVoiceClip;
        current.voiceSource.Play();
    }

    public static void PlayDoorOpenAudio()
    {
        current.fxSource.clip = current.doorFXClip;
        current.fxSource.PlayDelayed(1.1f);//延迟1.1秒播放
    }

    private void Awake()
    {
        //避免死亡后重复添加音乐
        if (current != null)
        {
            Destroy(gameObject);
            return;
        }

        current = this;

        DontDestroyOnLoad(gameObject);//项目保留,使声音一直存在

        //添加组件
        ambientSource = gameObject.AddComponent<AudioSource>();
        musicSource= gameObject.AddComponent<AudioSource>();
        fxSource= gameObject.AddComponent<AudioSource>();
        playerSource= gameObject.AddComponent<AudioSource>();
        voiceSource= gameObject.AddComponent<AudioSource>();

        ambientSource.outputAudioMixerGroup = ambientGroup;
        musicSource.outputAudioMixerGroup = musicGroup;
        fxSource.outputAudioMixerGroup = FxGroup;
        playerSource.outputAudioMixerGroup = playerGroup;
        voiceSource.outputAudioMixerGroup = voiceGroup;

        StartLevelAudio();
    }


}

死亡机制

在场景中添加Spikes等陷阱,将其 Layer 设定为 Traps(Overrides--Apply All)
OnTriggerEnter2D:触发器
OnCollisionEnter2D:碰撞器
触发器是碰撞器的一个功能
在想要做碰撞检测时使用碰撞器
碰撞器生效的必要条件:碰撞的双方A,B都必须有Collider,其中有一方要带有rigidbody
当想要做碰撞检测却又不想产生碰撞效果时,就可以用isTrigger,在这个状态下触发检测生效,碰撞检测失效

触发死亡
在Unity项目里对某个GameObject进行隐藏时,用gameObject.SetActive(false)可以达到目的

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.layer == trapsLayer)
        {
            gameObject.SetActive(false);//将游戏角色的启用关闭
        }
    }

死亡时触发效果
Instantiate 一般用于对Prefab(预制体)的实例化
使用 Instantiate 时,其属性和原物体一致,由于是实例化一个prefab,所以可以想到它包含的参数应该有:① prefab是什么 ②位置和方向 ③挂载在哪里

Instantiate(预制体,transform.position,transform.rotation);

死亡后游戏场景重置
调用 UnityEngine 里的 SceneManagement

using UnityEngine.SceneManagement;

再在 OnTriggerEnter2D 中进行调用

SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);

File-- Building settings -- Add Open Scenes

收集物品

将物品添加到游戏场景中
使用 gameObject.SetActive(false)可以达到目的

 private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.layer == player)
        {
            gameObject.SetActive(false);
        }
    }

*同样使用 Instantiate 可触发拾取时特效

视觉效果

局部&全局特效
1.window--Package Manager--Post Processing 进行安装
2.在摄像机中添加组件 Post Processing Layer
3.创建Layer:Post Processing
4.在 Hierarchy 中创建一个空项目,将其 Layer 设置为 Post Processing Layer(用于承载特效)
5.为其添加 Post Process Volume(如果需要让其用于全局就勾选Is Global,如果需要让其用于局部就添加 collider,选择范围)
*priority 为优先级,数值越大,它就会优先在其他视觉特效之上进行播放)
6.在 Profile 中添加特效

相机抖动
1.添加2D摄像机 Virtual Camera
2.在 Extensions 中添加 Impulse Listener(用于感知)
3.在需要被感知的物体中添加Cinemachine Collision Impulse Source,并在Signal Shape--Raw Signal 中添加特效(Frequency Gain数值越大,震动频率越大;Amplitude Gain越大,震动幅度越大)
*如果没有Signal Shape,将 Impulse Type 改成 Legacy 即可出现
4.在 Trigger Object Filter 中选择触发的Layer

统筹管理(Game Manager)

GameManager 的物体的 PlayerManager 脚本的 Player 成员,可以通过另外一个脚本的实例化,进行调用其公共成员。
*在实际应用中,经常要查找Manager管理物体,用于调用参数,GameObject.FindGameObjectWithTag 通过标签或者名字查找物体,效率较低,实例化instance 则是一种高效的方法,广泛应用于调用其他的物体的情况
Invoke函数是一种用于延迟或重复执行某个方法的函数

Invoke(string methodName, float time);

methodName是要调用的方法名,time是延迟时间

GameManager 完整代码

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

public class GameManager : MonoBehaviour
{
    static GameManager instance;
    SceneFader fader;
    List<Orb> orbs;
    Door lockedDoor;

    float gameTime;
    bool gameIsOver;

   // public int orbNum;
    public int deathNum;

    public static void PlayerDied()
    {
        instance.fader.FadeOut();
        instance.deathNum++;
        UIManager.UpdateDeathUI(instance.deathNum);
        instance.Invoke("RestartScene", 1.5f);//延迟1.5秒重新加载场景
    }

    //重新加载当前场景
    void RestartScene()
    {
        instance.orbs.Clear();//场景开始时归零宝珠
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
    }

    public static void RegisterSceneFader(SceneFader obj)
    {
        instance.fader = obj;
    }

    public static void RegisterOrb(Orb orb)
    {
        if (instance == null)
        {
            return;
        }

        if (!instance.orbs.Contains(orb))
        {
            instance.orbs.Add(orb);
        }
        UIManager.UpdateOrbUI(instance.orbs.Count);
    }

    //获得宝珠
    public static void playerGrabbedOrb(Orb orb)
    {
        if (!instance.orbs.Contains(orb))
        {
            return;
        }
        instance.orbs.Remove(orb);

        if (instance.orbs.Count == 0)//当宝珠收集完门开启
        {
            instance.lockedDoor.Open();
        }
        UIManager.UpdateOrbUI(instance.orbs.Count);
    }

    //注册门
    public static void RegisterDoor(Door door)
    {
        instance.lockedDoor = door;
    }

    public static void PlayerWon()
    {
        instance.gameIsOver = true;
        UIManager.DisplayGameOver();
        AudioManager.PlayerWonAudio();
    }

    //游戏结束
    public static bool GameOver()
    {
        return instance.gameIsOver;
    }

    private void Awake()
    {
        if (instance != null)
        {
            Destroy(gameObject);
            return;
        }

        instance = this;

        orbs = new List<Orb>();

        DontDestroyOnLoad(this);
    }

    private void Update()
    {
        if (gameIsOver)
        {
            return;
        }

        //  orbNum = instance.orbs.Count;

        //显示时间
        gameTime += Time.deltaTime;
        UIManager.UpdateTimeUI(gameTime);
    }
}

UI管理(UI Manager)

界面创建
1.将UI Manager Script 添加到预制中
2.将所需显示的预制中的文本框添加到 TextMeshProUGUI 中
3.创建Update UI 的函数,用于显示计数的变化
4.在Game Manager 对应的位置进行调用

*当游戏结束一切行为都应停止,在相关函数的开头判断return

*为了使游戏时间显示更稳定,建议使用Time.deltaTime
Time.deltaTime是帧与帧相减出来的,既是后一帧时间减去前一帧时间得出来的,可以使物体的运行情况不受帧率影响,并且在相同的时间内,运行情况都相同
假设有两台电脑,一台性能优越,另一台垃圾点,各运行一秒,但他们在一秒钟运行的帧数是不一样的
性能优越的电脑:
每秒的帧数多,帧与帧间隔就短Time.deltaTime数值就小,假设这个数值是0.1,乘与速度1,那么每帧速度是0.1, 假设一秒运行30帧,那么速度就是3。
性能差些的电脑:
电脑每秒的帧数少,帧与帧间隔就长Time.deltaTime数值就大,假设这个数值是0.3,乘与速度1,那么每帧速度是0.3, 假设一秒运行10帧,速度也是3。
结果相同

UIManager完整代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class UIManager : MonoBehaviour
{
    static UIManager instance;
    public TextMeshProUGUI orbText, timeText, deathText, gameoverText;//用于存放文本框预制

    //宝石剩余数量
    public static void UpdateOrbUI(int orbCount)
    {
        instance.orbText.text = orbCount.ToString();
    }

    //死亡数量
    public static void UpdateDeathUI(int deathCount)
    {
        instance.deathText.text = deathCount.ToString();//将整形变为字符型
    }

    //时间
    public static void UpdateTimeUI(float time)
    {
        int minutes = (int)(time / 60);
        float seconds = time % 60;

        instance.timeText.text = minutes.ToString("00") + ":" + seconds.ToString("00");
    }

    //游戏结束
    public static void DisplayGameOver()
    {
        instance.gameoverText.enabled = true;//是否启动
    }

    private void Awake()
    {
        if (instance != null)
        {
            Destroy(gameObject);
            return;
        }
        instance = this;
        DontDestroyOnLoad(this);
    }
}

附其他部分代码

PlayerHealth

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

public class PlayerHealth : MonoBehaviour
{
    public GameObject deathVFXPrefab;//死亡烟雾特效

    int trapsLayer;

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.layer == trapsLayer)
        {
            Instantiate(deathVFXPrefab, transform.position, transform.rotation);//烟雾
            //Instantiate(deathVFXPrefab, transform.position,Quaternion.Euler(0,0,Random.Range(-45,45)));//残影
            gameObject.SetActive(false);//将游戏角色的启用关闭
            AudioManager.PlayDeathAudio();
            // SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);

            GameManager.PlayerDied();
        }
    }


    void Start()
    {
        trapsLayer = LayerMask.NameToLayer("Traps");//获得Traps这个Layer的图层编号
    }
}

SceneFader

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SceneFader : MonoBehaviour
{
    Animator anim;
    int faderID;

    private void Start()
    {
        anim = GetComponent<Animator>();
        faderID = Animator.StringToHash("Fade");
        GameManager.RegisterSceneFader(this);
    }

    public void FadeOut()
    {
        anim.SetTrigger(faderID);
    }
}

WinZone

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class WinZone : MonoBehaviour
{
    int playerLayer;

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.layer == playerLayer)
        {
            Debug.Log("Player won!");
            GameManager.PlayerWon();
        }
    }

    void Start()
    {
        playerLayer = LayerMask.NameToLayer("Player"); 
    }
}

Orbs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Orb : MonoBehaviour
{
    int player;
    public GameObject explosionVFXPrefab;

    void Start()
    {
        player = LayerMask.NameToLayer("Player");
        GameManager.RegisterOrb(this);
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.gameObject.layer == player)
        {
            Instantiate(explosionVFXPrefab, transform.position, transform.rotation);

            gameObject.SetActive(false);

            AudioManager.PlayOrbAudio();

            GameManager.playerGrabbedOrb(this);
        }
    }
}

Door

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Door : MonoBehaviour
{
    Animator anim;
    int openID;

    public void Open()
    {
        anim.SetTrigger(openID);//触发器

        //播放audio
        AudioManager.PlayDoorOpenAudio();
    }

    void Start()
    {
        anim = GetComponent<Animator>();
        openID = Animator.StringToHash("Open");

        GameManager.RegisterDoor(this);
    }
}

FPS

using UnityEngine;
using UnityEngine.UI;

public class FPS : MonoBehaviour
{
    public Text fpsText;

    float deltaTime;
    
    void Update ()
    {
        deltaTime += (Time.unscaledDeltaTime - deltaTime) * 0.1f;
        SetFPS();
    }

    void SetFPS()
    {
        float msec = deltaTime * 1000.0f;
        float fps = 1.0f / deltaTime;
        fpsText.text = string.Format("FPS: {0:00.} ({1:00.0} ms)", fps, msec);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容