Robbie(Unity2D)项目复盘

场景制作

地图绘制

1.创建不同的2D Object–Tile map

2.给不同层设置Sorting Layer (越靠下越在前面显示)
同一个Sorting Layer 的 Order in Layer 可以不同(可实现透明效果的覆盖)

3.window–2D–Tile Palette
Palette 选择不同绘制的图案
Active Tile map 选择在哪个Layer上绘制

背景布置

1.给需要添加的背景元素设置设置统一的 Sorting Layer

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

物理组件

地图

1.给 platforms 添加组件 Tile map Collider 2D
勾选 use by Composite 并添加 Composite Collider

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

人物

1.为人物添加Rigibody

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

3.添加并设置人物的 Layer 为Player

4.添加并设置 Platforms 的 Layer 为 Ground

角色移动(Player Movement)

在最开始获得 Rigidbody

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

在Start中

 rb= GetComponent<RigidBody2D>();

添加移动参数
上面写[Header(“”)]可在unity面板中显示

获取虚拟轴

代码:

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类型
1.GetButton(GetKey):按键激活状态,按键按下后持续有效

2.GetButtonUp(GetKeyUp):按键松开状态,仅在按键弹起时有效

3.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

1.在 Animator 空白部分右击 Create State–Form New Blend Tree

2.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);
    }
}

音效控制(Audio Manager)

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);
	}
}
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值