用unity做仿马里奥游戏

效果预览

在这里插入图片描述
在这里插入图片描述
踩死人物时会有烟雾上升
在这里插入图片描述

搭建场景

首先下载资源https://pan.baidu.com/s/1QhfsEbu7RD22rQhZAx6ysA
然后导入到unity中,搭建好场景(导入env和天空盒),添加人物,把人物放到空物体下,给人物添加角色控制器,设置好人物的胶囊碰撞体积
在这里插入图片描述

角色控制器带有部分的刚体功能,并且自带胶囊碰撞器

书写控制移动的脚本

接下来创建脚本,由于需要玩家和敌人,我们可以让玩家和敌人共同继承自一个character基类。

character.cs:

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

public class Character : MonoBehaviour
{
    protected CharacterController cc;
    protected Animator animator;
    public float runSpeed;
    public float jumpPower=10;
    Vector3 pendingVelocity;//包含了速度和方向

    // Start is called before the first frame update
    void Awake()
    {
        cc = GetComponent<CharacterController>();
        animator = GetComponentInChildren<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        //移动
        pendingVelocity.z = 0f;
        cc.Move(pendingVelocity * Time.deltaTime);

        //模拟重力
        pendingVelocity.y += cc.isGrounded ? 0f : Physics.gravity.y * 10f * Time.deltaTime;
    }
    public void Jump()
    {
        if (cc.isGrounded)
        {
            pendingVelocity.y = jumpPower;
        }
    }
    public void Move(float inputX)
    {
        pendingVelocity.x = inputX * runSpeed;
    }
    public void Rotate(Vector3 lookDir,float turnSpeed)
    {

    }
    public void Death()
    {

    }
    public void TakeDamage(Character inflicter,int damage)//承受伤害
    //inflicter是加害者,受害者是自己
    {

    }

    public void AttackCheck()//攻击检测
    {

    }
    public void GrabCheck()
    {

    }
}

playercharacter.cs:

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

public class PlayerCharacter : Character
{


    // Update is called once per frame

}

playercontroller.cs:

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

public class PlayerController : MonoBehaviour
{
    PlayerCharacter character;
    // Start is called before the first frame update
    void Start()
    {
        character = GetComponent<PlayerCharacter>();
    }

    // Update is called once per frame
    void Update()
    {
        var h = Input.GetAxis("Horizontal");
        character.Move(h);
        if (h != 0)
        {
            var dir = Vector3.right * h;
            character.Rotate(dir, 10);
        }
        if (Input.GetKeyDown(KeyCode.Space))
        {
            character.Jump();
        }
        if (Input.GetKeyDown(KeyCode.G))
        {
            character.GrabCheck();
        }
    }
}

以上脚本写完后 就可以实现移动和跳跃的效果了

这里需要注意一点:charactercontroller自带Move函数,参数是vec3向量,效果是提供速度,但并不会提供重力,因此想要实现重力的效果需要自己模拟:

	private float gravity=10f;
        //模拟重力
        pendingVelocity.y -= cc.isGrounded ? 0f : gravity * 10f * Time.deltaTime;

动画效果

跳跃的动画效果:
当人物不处于地面时则播放跳跃的动画,当人物处于地面时,结束跳跃的动画进入地面的动画,因此在move和jump之间添加bool变量:grounded
在这里插入图片描述
move由blend tree组合而成:
在这里插入图片描述
需要注意 speed参数是自己添加的参数,范围为0到20,这里的20是设置好的最大速度:
在这里插入图片描述

动画状态间的切换:
在这里插入图片描述
然后还需要在update函数中更新speed和grounded的值即可:

    void Update()
    {
        //移动
        pendingVelocity.z = 0f;
        cc.Move(pendingVelocity * Time.deltaTime);

        animator.SetFloat("Speed", cc.velocity.magnitude);
        animator.SetBool("Grounded", cc.isGrounded);

        //更新重力
        pendingVelocity.y -= cc.isGrounded ? 0f : gravity * 10f * Time.deltaTime;
    }

跑步:
在这里插入图片描述
跳跃:在这里插入图片描述

流畅的旋转

旋转需要在两个角度间进行球面插值

    public void Rotate(Vector3 lookDir,float turnSpeed)
    {
        rotateComplete = false;
        var targetPos = transform.position + lookDir;
        var characterPos = transform.position;

        //去除y轴影响
        characterPos.y = 0;
        targetPos.y = 0;

        //角色面朝目标的向量
        Vector3 faceToDir = targetPos - characterPos;

        //角色面朝目标方向的四元数
        Quaternion faceToQuat = Quaternion.LookRotation(faceToDir);

        //球面插值
        Quaternion slerp = Quaternion.Slerp(transform.rotation, faceToQuat, turnSpeed * Time.deltaTime);
        if (slerp == faceToQuat)
        {
            rotateComplete = true;
        }
        transform.rotation = slerp;
    }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

相机跟随

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

public class PlayerCamera : MonoBehaviour
{
    public Transform target;
    public float distance = 8.0f;
    public float height=-1.0f;

    // Start is called before the first frame update


    // Update is called once per frame
    void LateUpdate()
    {
        if (!target) return;
        transform.position = target.position;
        transform.position -= Vector3.forward * distance;
        transform.position = new Vector3(transform.position.x, height, transform.position.z);
    }
}

直接将这个脚本添加给相机,然后给相机添加上目标即可
在这里插入图片描述

踩踏攻击

    public int damage = 100;
    public int health = 100;
    public GameObject deathFX;
	void Update()
	    {
	    	……
	        AttackCheck();
	    }

    public void TakeDamage(Character inflicter,int damage)//承受伤害
    //inflicter是加害者,受害者是自己
    {
        inflicter.Jump();
        health -= damage;
        if (health <= 0)
        {
            Death();
        }
    }

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


    }

死亡时的粒子特效

创建一个材质(在project窗口中),设置属性
在这里插入图片描述

然后再创建一个粒子系统(在和人物那些所在的窗口中)

设置好一系列的属性:
在这里插入图片描述
在这里插入图片描述
在人物死亡时,产生一个粒子:

    public void Death()
    {
        //fx:特效
        var fx = Instantiate(deathFX, transform.position, Quaternion.LookRotation(new Vector3(0,1,0)));
        //Destroy(fx, 2);
        Destroy(gameObject);
    }

抓取物体脚本

由于抓取只在player中,因此在playercharacter中实现grab函数:
PlayerCharacter.cs:

	public Transform grabSocket;
    public Transform grabObject;
	
	public void GrabCheck()
    {
        if (grabObject != null && rotateComplete)
        {
            grabObject.transform.SetParent(null);
            grabObject.GetComponent<Rigidbody>().isKinematic = false;
            grabObject = null;
        }
        else
        {
            var dist = cc.radius + 1f;

            RaycastHit hit;
            if(Physics.Raycast(transform.position,transform.forward,out hit, dist))
            {
                if (hit.collider.CompareTag("GrabBox"))
                {
                    grabObject = hit.transform;
                    grabObject.SetParent(grabSocket);
                    grabObject.localPosition = Vector3.zero;
                    grabObject.localRotation = Quaternion.identity;
                    grabObject.GetComponent<Rigidbody>().isKinematic = true;
                }
            }
        }
    }

抓取物体动画

抓取物体的动画需要单独在手上实现,因此我们可以新开一个动画层来实现
在这里插入图片描述
然后我们需要将基类中的update函数类型设置为protected型,然后在子类playerCharacter中修改动画控制器中的bool值,以及别忘了修改另外一个动画层的layer值:

    private void Start()
    {
        animator.SetLayerWeight(armsAnimatorLayer, 1f);//除了基层以外,其他的层次初始权重默认是0,这里需要手动设置。
    }
    private void Update()
    {
        animator.SetBool("Grab", grabObject ? true : false);
        base.Update();
    }

查看动画的遮罩

另外,我们还可以点击这里
在这里插入图片描述
在这里可以查看动画,以及每个动画的遮罩信息:
在这里插入图片描述
在这里插入图片描述

完整代码

Character.cs:

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

public class Character : MonoBehaviour
{
    protected CharacterController cc;
    protected Animator animator;
    public float runSpeed=20;
    public float jumpPower=20;


    public int damage = 100;
    public int health = 100;
    public GameObject deathFX;

    private float gravity = 10f;
    Vector3 pendingVelocity;//包含了速度和方向

    protected bool rotateComplete = true;

    // Start is called before the first frame update
    void Awake()
    {
        cc = GetComponent<CharacterController>();
        animator = GetComponentInChildren<Animator>();
    }

    // Update is called once per frame
    protected void Update()
    {
        //移动
        pendingVelocity.z = 0f;
        cc.Move(pendingVelocity * Time.deltaTime);

        animator.SetFloat("Speed", cc.velocity.magnitude);
        animator.SetBool("Grounded", cc.isGrounded);

        //更新重力
        pendingVelocity.y -= cc.isGrounded ? 0f : gravity * 10f * Time.deltaTime;
        AttackCheck();
    }
    public void Jump()
    {
        if (cc.isGrounded)
        {
            pendingVelocity.y = jumpPower;
        }
    }
    public void Move(float inputX)
    {
        pendingVelocity.x = inputX * runSpeed;
    }
    public void Rotate(Vector3 lookDir,float turnSpeed)
    {
        rotateComplete = false;
        var targetPos = transform.position + lookDir;
        var characterPos = transform.position;

        //去除y轴影响
        characterPos.y = 0;
        targetPos.y = 0;

        //角色面朝目标的向量
        Vector3 faceToDir = targetPos - characterPos;

        //角色面朝目标方向的四元数
        Quaternion faceToQuat = Quaternion.LookRotation(faceToDir);

        //球面插值
        Quaternion slerp = Quaternion.Slerp(transform.rotation, faceToQuat, turnSpeed * Time.deltaTime);
        if (slerp == faceToQuat)
        {
            rotateComplete = true;
        }
        transform.rotation = slerp;
    }
    public void Death()
    {
        //fx:特效
        var fx = Instantiate(deathFX, transform.position, Quaternion.LookRotation(new Vector3(0,1,0)));
        //Destroy(fx, 2);
        Destroy(gameObject);
    }
    public void TakeDamage(Character inflicter,int damage)//承受伤害
    //inflicter是加害者,受害者是自己
    {
        inflicter.Jump();
        health -= damage;
        if (health <= 0)
        {
            Death();
        }
    }

    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 GrabCheck()
    {

    }
}

PlayerController.cs:

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

public class PlayerController : MonoBehaviour
{
    PlayerCharacter character;
    // Start is called before the first frame update
    void Start()
    {
        character = GetComponent<PlayerCharacter>();
    }

    // Update is called once per frame
    void Update()
    {
        var h = Input.GetAxis("Horizontal");
        character.Move(h);
        if (h != 0)
        {
            var dir = Vector3.right * h;
            character.Rotate(dir, 10);
        }
        if (Input.GetKeyDown(KeyCode.Space))
        {
            character.Jump();
        }
        if (Input.GetKeyDown(KeyCode.G))
        {
            character.GrabCheck();
        }
    }
}

PlayerCharacter:

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

public class PlayerCharacter : Character
{
    public Transform grabSocket;
    public Transform grabObject;
    public int armsAnimatorLayer = 1;
    private void Start()
    {
        animator.SetLayerWeight(armsAnimatorLayer, 1f);//除了基层以外,其他的层次初始权重默认是0,这里需要手动设置。
    }
    private void Update()
    {
        animator.SetBool("Grab", grabObject ? true : false);
        base.Update();
    }

    // Update is called once per frame
     public void GrabCheck()
    {
        if (grabObject != null && rotateComplete)
        {
            grabObject.transform.SetParent(null);
            grabObject.GetComponent<Rigidbody>().isKinematic = false;
            grabObject = null;
        }
        else
        {
            var dist = cc.radius + 1f;

            RaycastHit hit;
            if(Physics.Raycast(new Vector3(transform.position.x, transform.position.y+0.5f, transform.position.z), transform.forward,out hit, dist))
            {
                if (hit.collider.CompareTag("GrabBox"))
                {
                    grabObject = hit.transform;
                    grabObject.SetParent(grabSocket);
                    grabObject.localPosition = Vector3.zero;
                    grabObject.localRotation = Quaternion.identity;
                    grabObject.GetComponent<Rigidbody>().isKinematic = true;
                }
            }
        }
    }
}

PlayerCamera:

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

public class PlayerCamera : MonoBehaviour
{
    public Transform target;
    public float distance = 8.0f;
    public float height=-1.0f;

    // Start is called before the first frame update


    // Update is called once per frame
    void LateUpdate()
    {
        if (!target) return;
        transform.position = target.position;
        transform.position -= Vector3.forward * distance;
        transform.position = new Vector3(transform.position.x, height, transform.position.z);
    }
}

AIController:

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

public class AIController : MonoBehaviour
{
    Character character;
    float lastCheckStateTime = 0;

    float simulateInputX;
    bool simulateJump;
    // Start is called before the first frame update
    void Start()
    {
        character = GetComponent<Character>();
    }

    // Update is called once per frame
    void Update()
    {
        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;
        }
    }
}

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页