最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊

在Unity中復(fù)刻《超級馬里奧》

2018-08-29 16:25 作者:皮皮關(guān)做游戲  | 我要投稿

本工程難度:★★

作者:Yukine

前言

去年的《超級馬里奧:奧德賽》再次驚艷了游戲圈,多家游戲媒體對本作打出了滿分,給予了極高的評價(jià),廣大圍觀群眾也感慨有幸在2017年見證了任天堂兩款神作的誕生。

嗯,好像沒什么不對

相信許多人對奧德賽中3D轉(zhuǎn)2D的設(shè)計(jì)感到極為驚艷,看著2D關(guān)卡又一次出現(xiàn)在屏幕上,是不是有種回到了小時(shí)候在電視機(jī)前握著手柄和水管工一起闖關(guān)的時(shí)光呢?那么在Unity中是不是也可以復(fù)刻當(dāng)年那款風(fēng)靡全球的Super Mario呢?帶著這樣的疑問,咱們利用Unity自帶的2D系統(tǒng)來實(shí)現(xiàn)一下馬里奧的基本操作以及與怪物的基本交互。

令人眼前一亮的2D關(guān)卡

實(shí)現(xiàn)流程

1.素材準(zhǔn)備

首先將準(zhǔn)備好的場景圖放入場景中,將其Layer改為Ground,并創(chuàng)建兩個(gè)空子節(jié)點(diǎn),加上Box Collider2D組件,分別作為地面和管道的碰撞體,并將馬里奧大叔和怪物的貼圖素材導(dǎo)入U(xiǎn)nity中,制作好各種狀態(tài)下的幀動(dòng)畫,并創(chuàng)建對應(yīng)的動(dòng)畫狀態(tài)機(jī)備用。

放入場景圖,并加上2D碰撞盒

2.玩家

接下來先處理玩家控制的馬里奧大叔,導(dǎo)入角色模型,在玩家組件上添加2D物理組件、碰撞盒及動(dòng)畫控制器,改變角色Tag和Layer,添加空子節(jié)點(diǎn),將其位置設(shè)置在角色腳下用來檢測地面及敵人。再創(chuàng)建角色腳本,接下來編寫角色的基本邏輯。

為馬里奧大叔添加組件

玩家類代碼整體如下:

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

 

public class PlayerCharacter : MonoBehaviour

{

    [SerializeField]

    Rigidbody2D rig2d; //玩家自身的2D物理模塊

    [SerializeField]

    Animator anim; //玩家身上的動(dòng)畫控制器

    [SerializeField]

    Transform checkPoint; //玩家子物體中的監(jiān)測點(diǎn)

 

    float curSpeed = 3f;

    float jumpHeight = 350f;

    bool isFacingRight = true;

    bool isGrounded = true;

 

    float checkDistance = 0.05f;

 

    int hitCount = 0;

    public bool isDead = false;

 

    LayerMask groundLayer; //地面層

    LayerMask enemyLayer; //敵人層

 

    Animator playerAnim;

    AnimatorStateInfo stateInfo;

 

    void Start ()

    {

        Init();

    }

 

    void Update()

    {

        stateInfo = anim.GetCurrentAnimatorStateInfo(0);

 

        if (isDead && stateInfo.IsName("Die"))

        {

            return;

        }

 

        var h = Input.GetAxis("Horizontal"); //獲取玩家在水平方向上的輸入

 

        if (!isDead)

        {

            Move(h);

        }

 

        CheckIsGrounded();

 

        if (h > 0 && !isFacingRight)

        {

            Reverse();

        }

        else if (h < 0 && isFacingRight)

        {

            Reverse();

        }

 

        if (Input.GetKeyDown(KeyCode.Space) && isGrounded)

        {

            Jump();

        }

 

        if(!isDead)

        {

            CheckHit();

        }

    }

 

    //初始化函數(shù)

    void Init()

    {

        rig2d = GetComponent<Rigidbody2D>();

        anim = GetComponent<Animator>();

        checkPoint = transform.Find("GroundCheckPoint");

        playerAnim = GetComponent<Animator>();

 

        groundLayer = 1 << LayerMask.NameToLayer("Ground");

        enemyLayer = 1 << LayerMask.NameToLayer("Enemy");    

    }

   

    //將角色的localScale取反來翻轉(zhuǎn)模型,實(shí)現(xiàn)左右轉(zhuǎn)向的效果

    void Reverse()

    {

        if (isGrounded)

        {

            isFacingRight = !isFacingRight;

            var scale = transform.localScale;

            scale.x *= -1;

            transform.localScale = scale;

        }

    }

 

    //玩家移動(dòng)函數(shù),運(yùn)用Unity2D物理自帶函數(shù)實(shí)現(xiàn)

    void Move(float dic)

    {

        rig2d.velocity = new Vector2(dic * curSpeed, rig2d.velocity.y);

        a*********oat("Speed", Mathf.Abs(dic * curSpeed));

    }

   

    //跳躍,同樣運(yùn)用Unity2D物理實(shí)現(xiàn)

    void Jump()

    {

        rig2d.AddForce(new Vector2(0, jumpHeight));

    }

 

    //射線檢測是否接觸地面,只有當(dāng)接觸地面的時(shí)候才可以跳躍以免出現(xiàn)n連跳的情況

    void CheckIsGrounded()

    {

        Vector2 check = checkPoint.position;

        RaycastHit2D hit = Physics2D.Raycast(check, Vector2.down, checkDistance, groundLayer.value);

 

        if (hit.collider != null)

        {

            anim.SetBool("IsGrounded", true);

            isGrounded = true;

        }

        else

        {

            anim.SetBool("IsGrounded", false);

            isGrounded = false;

        }

    }

 

    //運(yùn)用2D相交圓檢測腳下是否有怪物

    void CheckHit()

    {

        var check = checkPoint.position;

        var hit = Physics2D.OverlapCircle(check, 0.07f, enemyLayer.value);

 

        if (hit != null)

        {

            if (hit.CompareTag("Normal")) //若踩中普通怪物,則給予玩家一個(gè)反彈力,并觸發(fā)怪物的死亡效果

            {

                Debug.Log("Hit Normal!");

                rig2d.velocity = new Vector2(rig2d.velocity.x, 5f);

                hit.GetComponentInParent<EnemyCharacter>().isHit = true;

            }

            else if (hit.CompareTag("Special")) //若踩中特殊怪物(烏龜),則在敵人相關(guān)代碼中做對應(yīng)變化

            {

                hitCount += 1;

                if (hitCount == 1)

                {

                    rig2d.velocity = new Vector2(rig2d.velocity.x, 5f);

                    hit.GetComponentInParent<EnemyCharacter>().GetHit(1);

                }

            }

        }

    }

 

    public void InitCount()

    {

        hitCount = 0;

    }

 

    //若玩家死亡,則進(jìn)入死亡狀態(tài),出發(fā)死亡動(dòng)畫,停止移動(dòng)

    public void Die()

    {

        Debug.Log("Player Die!");

        isDead = true;

        playerAnim.SetTrigger("Die");

        rig2d.velocity = new Vector2(0, 0);

    }

}

 

將腳本掛在角色身上,試著運(yùn)行一下,我們的馬里奧大叔就可以在屏幕上動(dòng)起來啦。

3.敵人

由于敵人有不同的種類,所以敵人代碼中要對不同性質(zhì)的敵人進(jìn)行不同的處理,由于本篇文章中僅涉及兩種怪物的實(shí)現(xiàn)邏輯:蘑菇怪(普通怪)和烏龜(特殊怪),故將兩種怪物的邏輯統(tǒng)一在一個(gè)腳本中(并不推薦將所有的怪物邏輯都擠在一個(gè)腳本中,這樣做的話若再添加新怪物,對代碼的維護(hù)和拓展很不方便)。

與玩家的處理方法類似,我們同樣將怪物模型引入,統(tǒng)一Layer為Enemy,但我們要把怪物明星放置在空父節(jié)點(diǎn)下作為子節(jié)點(diǎn)并添加碰撞盒和動(dòng)畫控制器;在當(dāng)前節(jié)點(diǎn)下,再繼續(xù)添加左右兩個(gè)觸發(fā)器,當(dāng)玩家接觸該區(qū)域時(shí),玩家死亡;再添加一個(gè)空節(jié)點(diǎn),將其位置移出其他觸發(fā)碰撞區(qū)域,這個(gè)節(jié)點(diǎn)是檢測碰撞障礙物的出發(fā)點(diǎn)。需要注意的是,由于烏龜在踩中第一下時(shí)并不會(huì)直接死亡,而是變成龜殼,所以烏龜?shù)墓?jié)點(diǎn)下分別添加了普通狀態(tài)和龜殼狀態(tài)這兩種模型組件以便進(jìn)行狀態(tài)切換;而且為了區(qū)分怪物種類,我們將蘑菇怪的Tag改為Normal,烏龜?shù)腡ag改為Special。

為怪物添加組件

初代超級馬里奧初期敵人的行動(dòng)相對比較簡單,僅有簡單的移動(dòng)和折返,這些通用的功能代碼如下:

//敵人移動(dòng),并沒有運(yùn)用物理函數(shù),而是直接改變位置

void Move()

{

    this.transform.position += dir * Time.deltaTime * speed;

}

 

//向前進(jìn)方向發(fā)射射線檢測,若碰到障礙物則折返

public void CheckBorder()

{

    Vector2 checkPos = checkTran.position;

    RaycastHit2D borderHit = Physics2D.Raycast(checkPos, checkDir, checkDistance, borderLayer.value);

       

    if (borderHit.collider != null)

    {

        ChangeMoveDir();

    }

}

 

//同樣運(yùn)用射線檢測來判定是否接觸到其他怪物

void CheckCharacter()

{

    Vector2 checkPos = checkTran.position;

    RaycastHit2D characterHit = Physics2D.Raycast(checkPos, checkDir, checkDistance, enemyLayer.value);

 

    if (characterHit.collider != null)

    {

        if (characterHit.collider.CompareTag("Normal") || characterHit.collider.CompareTag("Special"))

        {

            characterHit.collider.gameObject.GetComponentInParent<EnemyCharacter>().ChangeMoveDir();

        }

 

        if (charType != EnemyType.Shell)

        {

            ChangeMoveDir();

        }

    }

}

 

//改變前進(jìn)方向

public void ChangeMoveDir()

{

    dir.x *= -1;

    checkDir.x *= -1;

    Reverse();

}

 

//角色模型翻轉(zhuǎn)方法和玩家的基本一致

void Reverse()

{

    var scale = transform.localScale;

    scale.x *= -1;

    transform.localScale = scale;

}

 

先來看一下加入敵人后的效果:


如果玩家碰到了怪物,則玩家死亡。這段邏輯我拿了出來放在單獨(dú)的腳本中掛在死亡觸發(fā)區(qū)上,代碼如下:


using System.Collections;

using System.Collections.Generic;

using UnityEngine;

 

public class DeathTrigger : MonoBehaviour

{

    [SerializeField]

    EnemyCharacter _enemy;

 

    PlayerCharacter _player;

 

    private void Start()

    {

        _player = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerCharacter>();

    }

 

    private void OnTriggerEnter2D(Collider2D collision)

    {

        if (collision.CompareTag("Player"))

        {

            Debug.Log("Hit Player");

            _player.Die();

        }

    }

 

}

這樣,當(dāng)玩家接觸到怪物身上的死亡觸發(fā)區(qū)域時(shí)進(jìn)死亡狀態(tài),效果如下:


接下來我們繼續(xù)處理怪物被馬里奧踩中時(shí)的邏輯。

在代碼中,我們使用枚舉對怪物種類和狀態(tài)進(jìn)行:


public enum EnemyType

{

    Normal, //普通蘑菇怪

    Turtle, //烏龜普通狀態(tài)

    Shell, //龜殼狀態(tài)

}


若蘑菇怪被踩中則直接觸發(fā)被踩扁的動(dòng)畫并進(jìn)入死亡狀態(tài):


void NormalEnemyHit()

{

    enemyAnim.SetTrigger("Hit");

    CloseCollidersInChild(this.transform);

    if (stateInfo.IsName("Hit") && stateInfo.normalizedTime >= 1f)

    {

        this.gameObject.SetActive(false);

    }

}

 

而烏龜?shù)倪壿嬕獜?fù)雜一些,普通狀態(tài)和龜殼狀態(tài)的代碼如下:

public void GetHit(int rStage)

{

    if(charType == EnemyType.Turtle) //若當(dāng)前為行走狀態(tài),則切換為龜殼靜止?fàn)顟B(tài),關(guān)閉身上的死亡觸發(fā)區(qū)

    {

        _turtleBody.SetActive(false);

        _turtleShell.SetActive(true);

        isHit = true;

        _dieTrigger.gameObject.SetActive(false);

        charType = EnemyType.Shell;

    }

    else if(charType == EnemyType.Shell) //在龜殼移動(dòng)狀態(tài)下被踩中則恢復(fù)為龜殼靜止?fàn)顟B(tài)

    {

        isShellMove = false;

        isShellAttack = false;

        isOnTrigger = false;

        _player.InitCount();

    }

 

    StartCoroutine("OnRecover");

}

 

//運(yùn)用協(xié)程來處理龜殼靜止?fàn)顟B(tài)時(shí)的動(dòng)畫

IEnumerator OnRecover()

{

    yield return new WaitForSeconds(3f);

    shellAnim.SetTrigger("OnRecover"); //三秒鐘內(nèi)馬里奧沒有碰到龜殼的話則進(jìn)入閃爍動(dòng)畫

    yield return new WaitForSeconds(2f);

    shellAnim.SetBool("IsRecover", true); //閃爍兩秒鐘后恢復(fù)為行走狀態(tài)

    Recover();

}

 

//若玩家沒有行動(dòng),則恢復(fù)為行走狀態(tài)

void Recover()

{

    _turtleShell.SetActive(false);

    _turtleBody.SetActive(true);

 

    Debug.Log("dir.x:" + dir.x + " transform.localScale.x:" + transform.localScale.x);

    if(transform.localScale.x * dir.x == 1)

    {

        var scale = transform.localScale;

        scale.x *= -dir.x;

        transform.localScale = scale;

    }

 

    isHit = false;

    isOnTrigger = false;

    _dieTrigger.gameObject.SetActive(true);

    charType = EnemyType.Turtle;

    _player.InitCount();

}

 

//若在龜殼靜止?fàn)顟B(tài)時(shí)檢測到玩家進(jìn)入范圍內(nèi),龜殼改變?yōu)橐苿?dòng)狀態(tài)

void CheckTrigger()

{

    Vector2 checkPos = transform.position;

    Vector2 playerPos = _player.transform.position;

    var hit = Physics2D.OverlapCircle(checkPos, 0.1f, playerLayer.value);

 

    if(hit != null)

    {

        isShellMove = true;

        isOnTrigger = true;

        isCheck = true;

        isShellAttack = true;

 

        var tempDir = checkPos - playerPos;

        //通過玩家位置和龜殼位置形成的向量來判斷龜殼的移動(dòng)方向

        if(tempDir.x > 0)

        {

            shellMoveDir = new Vector3(1, 0, 0);

            checkDir = new Vector2(1, 0);

        }

        else

        {

            shellMoveDir = new Vector3(-1, 0, 0);

            checkDir = new Vector2(-1, 0);

        }

 

        if (checkDir.x * dir.x == -1)

        {

            Reverse();

        }

 

        shellAnim.Play("Shell", 0, 0);

        StopCoroutine("OnRecover");

    }

}

 

//龜殼移動(dòng)和正常行走的邏輯相同,只不過改變了移動(dòng)速度

void ShellMove()

{

    dir.x = shellMoveDir.x;

    transform.position += shellMoveDir * Time.deltaTime * shellMoveSpeed;

}

 

//龜殼進(jìn)入移動(dòng)狀態(tài)時(shí),檢測玩家和龜殼的距離,只有當(dāng)超出規(guī)定距離后才開啟死亡觸發(fā)區(qū)

void CheckDistance()

{

    Vector2 checkPos = transform.position;

    Vector2 playerPos = _player.transform.position;

    var distance = (checkPos - playerPos).magnitude;

    if(distance > 1f)

    {

        _dieTrigger.gameObject.SetActive(true);

        _player.InitCount();

        isCheck = false;

    }

}

 

//龜殼移動(dòng)時(shí),檢測是否接觸到其他怪物

void CheckAttack()

{

    Vector2 checkPos = checkTran.position;

    RaycastHit2D hit = Physics2D.Raycast(checkPos, checkDir, 0.08f, enemyLayer.value);

 

    if(hit.collider != null)

    {

        ShellAttack(hit.collider);

    }

}

   

//對其他怪物造成傷害

void ShellAttack(Collider2D rCollider)

{

    if (rCollider.CompareTag("Normal") || rCollider.CompareTag("Special"))

    { rCollider.gameObject.GetComponentInParent<EnemyCharacter>().isDead = true; }

}

 

若龜殼在移動(dòng)狀態(tài)下?lián)糁辛似渌治铮蜁?huì)觸發(fā)龜殼擊中時(shí)的死亡動(dòng)畫進(jìn)入死亡狀態(tài):


void Die()

    {

        CloseCollidersInChild(this.transform);

        enemyAnim.SetTrigger("Die");

        if(stateInfo.IsName("Die") && stateInfo.normalizedTime >= 0.9f)

        {

            Destroy(this.gameObject);

        }

    }

   

//關(guān)閉子節(jié)點(diǎn)下的所有觸發(fā)碰撞器

void CloseCollidersInChild(Transform rTran)

{

    var tempTrans = rTran.GetComponentsInChildren<BoxCollider2D>();

    foreach(var child in tempTrans)

    {

        child.enabled = false;

    }

}

 

接下來我們看一下效果,首先是玩家踩中怪物時(shí)的效果:

接下來是龜殼在不同狀態(tài)時(shí)的效果:

最后再看一下龜殼擊中其他怪物的效果吧:

嗯,很完美!

到這里,這些基礎(chǔ)的操作和交互均已實(shí)現(xiàn)完畢。

完整的工程已上傳至我的GitHub:https://github.com/Yukimine33/MarioProject (Yukimine33/MarioProject),歡迎大家查閱。


 有想學(xué)習(xí)游戲開發(fā)的同學(xué),可移步至http://www.levelpp.com圍觀一波。同時(shí),大佬云集的游戲開發(fā)群869551769歡迎加入討(jiao)論(ji)。


在Unity中復(fù)刻《超級馬里奧》的評論 (共 條)

分享到微博請遵守國家法律
黔西| 巴东县| 富蕴县| 临高县| 丰原市| 正蓝旗| 浦县| 南宫市| 文化| 泰顺县| 阜阳市| 云梦县| 昌吉市| 克山县| 泽库县| 铜陵市| 静海县| 广南县| 廉江市| 吉安市| 岑溪市| 大丰市| 奉新县| 永仁县| 资溪县| 新蔡县| 霞浦县| 仙桃市| 永川市| 呼图壁县| 南溪县| 海淀区| 黄浦区| 布尔津县| 呈贡县| 柯坪县| 浦北县| 嘉兴市| 叙永县| 张家界市| 郁南县|