Robbie(回顾)

TileMap使用

sorting layers用于层级排序,越下越靠镜头

Rules Tiles可以更便捷地绘制地图

Tilemap Collider更快地为tiles添加碰撞体

人物设置

2D闯关游戏一般会锁定人物Z轴

using System.Collections;
using System.Collections.Generic;
using UnityEditor.Experimental.GraphView;
using UnityEngine;

public class PlayerMoving : 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;

    float jumpTime;


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

    [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;

    public float xVelocity;

    //按键设置
    bool jumpPressed;
    bool jumpHeld;
    bool crouchHeld;
    bool crouchPressed;
    //碰撞体尺寸
    Vector2 colliderStandSize;
    Vector2 colliderStandOffset;
    Vector2 colliderCrouchSize;
    Vector2 colliderCrouchOffset;


    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 / 2f);//蹲下高度
        colliderCrouchOffset = new Vector2(coll.offset.x, coll.offset.y / 2f);//蹲下位置

    }

    
    void Update()
    {
        if (GameManager.GameOver())
            return;



       jumpPressed = Input.GetButtonDown("Jump");
     
        jumpHeld = Input.GetButton("Jump");
        crouchHeld = Input.GetButton("Crouch");
        crouchPressed = Input.GetButtonDown("Crouch");
    }
    private void FixedUpdate()
    {
        if (GameManager.GameOver())
            return;
        PhysicsCheck();
        GroundMovement();
        MidAirMovement();
    }

    void PhysicsCheck()//射线检测
    {
        RaycastHit2D leftCheck = Raycast(new Vector2(-footOffset, 0f), Vector2.down,groundDistance ,groundLayer );//从角色向左下发射了一条射线,判断是否接触到groundLayer
        RaycastHit2D rightCheck = Raycast(new Vector2(footOffset, 0f), Vector2.down, groundDistance, groundLayer);

        if (leftCheck || rightCheck )
            isOnGrund = true;
        else isOnGrund = 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 (!isOnGrund && rb.velocity.y < 0f && ledgeCheck && wallCheck && !blockedCheck)//rb.velocity.y<0f代表下落时检测
        {
            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 && isOnGrund)
            Crouch();
        else if (!crouchHeld && isCrouch && !isHeadBlocked )
            StandUp();
        else if (!isOnGrund && isCrouch)
            StandUp();

        xVelocity = Input.GetAxis("Horizontal");

        if (isCrouch)
            xVelocity /= crouchSpeedDivisor;

        rb.velocity = new Vector2(xVelocity * speed, rb.velocity.y);//将新速度赋给rb
        FilpDirction();
    }

    void MidAirMovement()
    {
        if(isHanging )
        {
            if(jumpPressed )
            {
                rb.bodyType = RigidbodyType2D.Dynamic;//表示rb的刚体类型是可以受物理引擎影响的
                rb.velocity = new Vector2(rb.velocity.x, hangingJumpForce);//给刚体赋予一个向上的速度
                isHanging = false;//取消悬挂
            }
            if(crouchPressed)//在悬挂时按下蹲下取消悬挂
            {
                rb.bodyType = RigidbodyType2D.Dynamic;
                isHanging = false;
            }
        }
        if(jumpPressed && isOnGrund && !isJump && !isHeadBlocked)
        {
            if(isCrouch  && isOnGrund  )
            {
                StandUp();
                rb.AddForce(new Vector2(0f, crouchJumpBoost), ForceMode2D.Impulse);//给一个向上的力,ForceMode2D 是一个枚举,定义了施加力的模式,impulse是指瞬间力
            }

            isOnGrund = false;
            isJump = true;

            jumpTime =  Time.time + jumpHoldDuration;//计算跳跃时间

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

            AudioManger.PlayJumpAudio();
        }

        else if(isJump )
        {
            if (jumpHeld)
                rb.AddForce(new Vector2(0f, jumpHoldForce), ForceMode2D.Impulse);//长按再加一个力
            if (jumpTime < Time.time)
                isJump = false;//当跳跃时间结束就取消跳跃
        }
    }


    void FilpDirction()//左右人物反向
    {
        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;
    }

    RaycastHit2D Raycast(Vector2 offset,Vector2 rayDiraction,float length,LayerMask layer)//射线检测
    {
        Vector2 pos = transform.position;//初始位置
        RaycastHit2D hit = Physics2D.Raycast(pos + offset, rayDiraction, length, layer);//射线判断是否接触

        Color color = hit ? Color.red : Color.green;//如果没碰到为绿色,碰到则为红色


        Debug.DrawRay(pos + offset, rayDiraction * length,color);
        return hit;
    }
    
}

在实现悬挂功能时运用了射线检测,射线检测详情见:

声音控制

public class AudioManger : MonoBehaviour
{
    static AudioManger 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[] crouchStepClips;
    public AudioClip jumpClip;
    public AudioClip deathClip;

    public AudioClip jumpVoiceClip;
    public AudioClip deathVoiceClip;
    public AudioClip orbVoiceClip;

    //为物体添加声音组件
    AudioSource ambientSource;
    AudioSource musicSource;
    AudioSource fxSource;
    AudioSource playerSource;
    AudioSource voiceSource;


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

    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;
        playerSource.outputAudioMixerGroup = playerGroup;
        musicSource.outputAudioMixerGroup = ambientGroup;
        fxSource.outputAudioMixerGroup = FXGroup;
        voiceSource .outputAudioMixerGroup = voiceGroup;

        StartLevelAudio();
    }

    public static void PlayDoorOpenAudio()//播放开门的声音
    {
        current.fxSource.clip = current.doorFXClip;
        current.fxSource.PlayDelayed (1f);
    }

     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.doorFXClip;
        current.fxSource.Play();
    }


    public static void PlayerWonAudio()
    {
        current.fxSource.clip = current.winClip;
        current.fxSource.Play();
        current.playerSource.Stop();
    }



    public static void PlayFootstepAudio()//播放脚步声音并在animation调用播放
    {
        int index = Random.Range(0,current.walkStepClips.Length);

        current.playerSource.clip = current.walkStepClips[index];
        current.playerSource.Play();
    }
    public static void PlayCrouchstepAudio()
    {
        int index = Random.Range(0, current.crouchStepClips.Length);

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

    }
    public static void PlayJumpAudio()
    {
        current.playerSource.clip = current.jumpClip;
        current.playerSource.Play();

        current.voiceSource.clip = current.jumpVoiceClip;
        current.voiceSource.Play();
    }
    public static void PlayerDeathAudio()
    {
        current.playerSource.clip = current.jumpClip;
        current.playerSource.Play();

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

        current.fxSource.clip = current.deathFXClip;
        current.fxSource.Play();
    }
    public static void PlayOrbAudio()
    {

        current.fxSource.clip = current.orbFXClip;
        current.fxSource.Play();

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

角色动画控制

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

public class PlayerAnmation : MonoBehaviour
{
    Animator anim;
    PlayerMoving moving;
    Rigidbody2D rb;

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

   
    void Start()
    {
        anim = GetComponent<Animator>();
        moving = GetComponentInParent<PlayerMoving>();
        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(moving.xVelocity));
        anim.SetBool(groundID , moving.isOnGrund);
        anim.SetBool(hangingID, moving.isHanging);
        anim.SetBool(crouchID, moving.isCrouch);
        anim.SetFloat(fallID, rb.velocity.y);
    }
    public void StepAudio()
    {
        AudioManger.PlayFootstepAudio();
    }
    public void CrouchStepAudio()
    {
        AudioManger.PlayCrouchstepAudio();
    }
}

GameManager

死亡机制

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

public class Playerhealths : MonoBehaviour
{
    public GameObject deathVFXPrefab;

    int trapsLayer;//陷阱的图层
    void Start()
    {
        trapsLayer = LayerMask.NameToLayer("Traps");
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.layer == trapsLayer)//当触碰到陷阱时
        {
            Instantiate(deathVFXPrefab, transform.position, Quaternion .Euler (0,0,Random .Range (-45,90)));//将死亡时的特效播放

            gameObject.SetActive(false);
            AudioManger.PlayerDeathAudio();//播放死亡声音
            //SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);死亡后重加载

            GameManager.PlayerDied();
        }
    }

}

道具收集

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

public class Orb : MonoBehaviour
{
    int player;
    public GameObject explosionVFXPrefab;//特效

    void Start()
    {
        player = LayerMask.NameToLayer("Player");//记录player图层编号

        GameManager.RegisterOrb(this);
    }

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


            gameObject.SetActive(false);

            AudioManger.PlayOrbAudio();//播放声音

            GameManager.PlayerGrabbedOrb(this);

        }
    }
}

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 lockdoor;

    float gameTime;
    bool gameIsOver;

    public int orbNum;//道具数量
    public int deathNum;//死亡次数


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

    public static void RegisterDoor(Door door)
    {
        instance.lockdoor = door;

    }

    public static void RegisterSceneFader(SceneFader obj)
    {
        instance.fader = obj;
    }
    public static void RegisterOrb(Orb orb)//道具注册
    {
        if (instance == null)
            return;
        if (!instance.orbs.Contains(orb))//contains是list自带查找函数
            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.lockdoor .Open();
        UIManager.UpdateOrbUI(instance.orbs.Count);//将现在的道具数量实时传到UI上
    }
    public static void PlayerWon()
    {
        instance.gameIsOver = true;
        UIManager.DisplayGameOver();
        AudioManger.PlayerWonAudio();
    }
    public static bool GameOver()
    {
        return instance.gameIsOver;
    }


    public static void PlayerDied()
    {
        instance.fader.FadeOut();//调用sceneFader中的fadeout函数
        instance.deathNum++;
        UIManager.UpdateDeathUI(instance.deathNum);//将死亡数量实时传入UI
        instance.Invoke("RestartScene", 1.5f);//延迟1.5s后调用RestarScene
    }
    void RestartScene()//重新加载场景
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
    }
}

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;//UI Text


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

        DontDestroyOnLoad(this);


    }

    public static void UpdateOrbUI(int orbCount)//更新道具数量UI
    {
        instance.orbText.text = orbCount.ToString();
    }
    public static void UpdateDeathUI(int deathCout)//更新死亡数量UI
    {
        instance.deathText.text = deathCout.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()//放出gameover的UI
    {
        instance.gameOverText.enabled = true;
    }
}

转场动画

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);//设置触发参数
    }
}

游戏胜利

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

public class WinZone : MonoBehaviour
{
    int playerLayer;
    
    void Start()
    {
        playerLayer = LayerMask.NameToLayer("Player");
    }
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.layer == playerLayer)
            Debug.Log("Player won!");
        GameManager.PlayerWon();
    }

}

游戏观感优化

安装插件Post Processing

为摄像机添加组件Post Process Layers

再添加一个空项目挂载Post Process Volume

总结

机关主要是通过角色触碰到特定的Layer实现的

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值