lalin0209

导航

基于Unity3D游戏的简单修改

项目来源于:https://github.com/meta-42/UnityGameplayTutorials

 

关于源项目

  这个项目是在b站up主“皮皮关做游戏”发布的视频【游戏制作教程】30分钟制作一款游戏 (6)【Unity3D】_哔哩哔哩_bilibili中由up主本人制作,原作者将项目源码上传至Github便于其他人学习。本篇博客将基于这个项目,对其进行阅读分析,找出项目中的缺陷并对其加以简单改进。

 

一.源项目简介

  源项目是基于Unity3D制作的一款简单的横板3D过关游戏,这类游戏中最出名的就是由任天堂制作出品的“超级马里奥兄弟”,源项目与其基本类似,玩家需操控角色小人在场景障碍中进行跳跃行走,躲避NPC的攻击并跳踩攻击NPC,最终到达终点则视为过关。以下动图为运行时截图。

  

二.源项目功能及相应代码分析

  在运行过这个项目并阅读项目源码后,我们可以大致看出这个项目具有的几个功能:1)角色的移动与跳跃 2)NPC的随机移动 3)角色与NPC的攻击 4)摄像机对角色的实时跟随。接下来将对于这几个功能进行具体的分析。

 

1)角色与NPC的移动与跳跃

   以下是控制角色移动和跳跃的代码:

 public void Jump()
    {
        if (cc.isGrounded)
        {
            pendingVelocity.y = jumpPower;
        }
    }

    public void Move(float inputX)
    {
        pendingVelocity.x = inputX * runSpeed;
    }

  这部分代码较为简单,主要是通过判断角色是否在地面上来决定跳跃,然后通过提前设置好的跳跃能力值改变角色的垂直方向位置。同样的道理,根据设置好的移动速度更改水平方向上的位置来实现角色横向移动。

 

2)NPC的随机移动

  在游戏中,NPC会在场景中随机移动跳跃,对角色发起攻击。NPC的移动方向和是否跳跃都是由生成随机数决定的,以下是代码:

void Update ()
    {
        //每隔一秒检查一次AI状态,确认接下来的行为
        if (Time.time > lastCheckStateTime + 2)
        {
            lastCheckStateTime = Time.time;

            simulateInputX = Random.Range(-1f, 1f);
            simulateJump = Random.Range(0, 2) == 1 ? true : false;

        }

        //实际执行行为
        MoveControl(simulateInputX);
        JumpControl(simulateJump);
    }

    void MoveControl(float inputX)
    {
        character.Move(inputX);
        if (inputX != 0)
        {
            var dir = Vector3.right * inputX;
            character.Rotate(dir, 10);
        }
    }

    void JumpControl(bool jump)
    { 
        if (jump)
        {
            character.Jump();
            simulateJump = false;
        }
    }

  从代码中可以看出,如果玩家控制角色发生移动,NPC的移动函数将随机生成一个数字,并根据这个随机生成的数字决定NPC的下一步行动。

 

3)角色与NPC的攻击

   由于角色和NPC继承于同一个父类,所以两者的攻击和计算伤害函数相同,代码如下:

 public void AttackCheck()
    {
        var dist = cc.height / 2;
        //向下射线检测
        RaycastHit hit;
        if (Physics.Raycast(transform.position, Vector3.down, out hit, dist + 0.05f))
        {
            if (hit.transform.GetComponent<Character>() && hit.transform != transform)
            {
                hit.transform.GetComponent<Character>().TakeDamage(this, damage);
            }
        }
}
 public void Death()
    {
        var fx = Instantiate(deathFX, transform.position, Quaternion.Euler(Vector3.zero));
        Destroy(fx, 2);
        Destroy(gameObject);

    }

    public void TakeDamage(Character inflicter, int damage)
    {
        inflicter.Jump();
        health -= damage;

        if(health <= 0)
        {
            Death();
        }
    }

   从代码中我们可以看出,攻击的主要方式是以人物为中心向垂直地面向下的方向发出射线检测,因为角色和NPC都继承于同一父类,所以如果检测到碰撞到的对象带有父类的脚本,则碰撞到的对象将会调用承受攻击函数,相应计算血量值的减少。如果血量值小于等于0,则调用死亡函数将对象销毁,并触发死亡动画。

 

4)摄像机对角色的实时跟随

  在横板游戏中,最具特色的就是固定角度只会横向移动的摄像机,为了做到摄像机横向移动并实时跟随角色,将角色的实时位置传给摄像机的位置函数,根据玩家的位置实时调整摄像机的位置,实现摄像机对角色的跟随效果,代码如下:

 void LateUpdate()
    {
        if (!target)
            return;

        transform.position = target.position;
        //Z轴间隔距离
        transform.position -= Vector3.forward * distance;
        transform.position = new Vector3(transform.position.x, hight, transform.position.z);
    }

 

三.对项目的简单修改补充

1)增加关卡内容

  在多次运行项目并分析其代码后,我发现项目并不完整,还有很多可以改进的地方。首先,作为一个游戏,现有的关卡显然太过短和简单,只有几个障碍,如下图:

  因此,改进的第一步即为增加关卡场景的长度和难度,为保持内容风格一致,复制原场景中的素材进行拼贴增加。修改后效果如下图:

 

2)增加游戏玩法

  第二,在横板游戏中,仅有单纯的跳跃障碍未免过于缺少游戏性,因此在项目中添加横板过关游戏的经典玩法,吃金币。首先在Unity官方商城中找到金币的素材,将其添加入项目之中。随后将模型放置到关卡中,并添加脚本使金币能够在原地旋转。效果如下图:

  接下来需要为金币和角色设置碰撞器,如果人物碰撞到金币,则金币将会消失。同时,还需要在屏幕上设置显示一个计数器,记录已经获得金币的数量。具体代码如下:

 void OnTriggerEnter(Collider collider)    //检测接触
    {
        if (collider.tag == "Coin")
        {
            score++;
            text.text = score.ToString();
         }
            Destroy(collider.gameObject);
    }

  效果如下:

 

3)增加结束判断

   为了让这个项目变成一个完整的游戏,还需要为它设置一个结束判定,也就是游戏的最终目的——通关。由于游戏的主要玩法是跳跃吃金币,于是将游戏的结束判定设置为收集完场景中的所有45个金币。一旦玩家在关卡尽头收集完全部金币,屏幕中间就会显示胜利的文字。于是对金币的计数代码进行修改,每计数一次就判断数量是否已经到达45,具体代码如下:

void OnTriggerEnter(Collider collider)    //检测接触
    {
        if (collider.tag == "Coin")
        {
            score++;
            text.text = score.ToString();
            if (score == 45)             //判断数量是否已经达到全部收集
            {
                winText.SetActive(true); //显示胜利文字         
} Destroy(collider.gameObject); } }

  具体效果如下:

 

4)其他简单添加

  除了上述这些功能玩法上的添加,还做了一些简单的小添加让这个游戏更加完整,例如设置了运行时的背景音乐,玩家NPC攻击时的特殊音效,死亡时的特殊音效等。

 

四.总结

   通过这次的项目修改的过程,我产生了几点感悟:

  1)注释非常重要。在阅读他人代码时,注释能帮我快速的了解代码的作用,极大的提高了我阅读理解代码的效率。少部分缺少注释的地方则消耗了我大量时间查找理解。

  2)在遇到问题时百度比自己死磕更能解决问题。有时项目出现bug的时候,与其自己尝试修改,不如在网上搜索一下他人的经验和方法,也许马上就能有思路。

  3)Debug的过程更能加深对代码的理解。比起写代码,debug的时候更能深入理解代码的作用, 学习到函数的具体使用方式。

  

  以上内容均为本人个人理解,如有问题欢迎批评指正,也欢迎留言交流学习。

posted on 2022-03-01 20:59  lalin0209  阅读(2767)  评论(0编辑  收藏  举报