Unity中制作RPG类3D游戏的套路模板

这里来记录一下用Unity做3D游戏时常用的处理方法

 


点击式移动的点击特效:

给人物挂载脚本,在update中:

        if (Input.GetMouseButtonDown(0))
        {
            //以主摄像机,将屏幕上的一个点转换为射线
            Ray ray=Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hitInfo;
            bool isCollider = Physics.Raycast(ray,out hitInfo);
            if (isCollider && hitInfo.collider.tag == tags.ground)
            {
                //实例化点击效果
                hitPoint = new Vector3(hitPoint.x, hitPoint.y+0.2f, hitPoint.z);
                GameObject.Instantiate(effect_click_prefab, hitPoint, Quaternion.identity);
            }
        }

effect_click_prefab 传入点击特效即可

复习射线检测


点击式移动的人物朝向问题:

定义一个Vector3的targetPosition变量(记录目标位置)

    public Vector3 targetPosition ;  //目标移动的位置

 

定义如下方法来变人物朝向:

    void LookAtTarget(Vector3 hitPoint)
    {
        targetPosition = hitPoint;
        //让目标位置与人物在同一个y值
        targetPosition = new Vector3(targetPosition.x, transform.position.y, targetPosition.z);
        //改变朝向
        this.transform.LookAt(targetPosition);
    }

回到该脚本的Update:

       if (Input.GetMouseButtonDown(0))
        {
            //以主摄像机,将屏幕上的一个点转换为射线
            Ray ray=Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hitInfo;
            bool isCollider = Physics.Raycast(ray,out hitInfo);
            if (isCollider && hitInfo.collider.tag == tags.ground)
            {
                isMoving = true;
                //改变朝向
                LookAtTarget(hitInfo.point);
            }
        }
        if (Input.GetMouseButtonUp(0))  //鼠标抬起
        {
            isMoving = false;
        }
        //这个判断用来处理玩家不松开鼠标的情况的朝向
        if (isMoving)  //若鼠标按下,则更新朝向
        {
            //得到要移动的目标位置
            //让主角朝向目标位置
            //以主摄像机,将屏幕上的一个点转换为射线
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hitInfo;
            bool isCollider = Physics.Raycast(ray, out hitInfo);
            if (isCollider && hitInfo.collider.tag == tags.ground)
            {
                LookAtTarget(hitInfo.point);
            }
        }
        else
        {
            //鼠标已经抬起,人物正在移动
            if (playIsMoving.isMoving)
            {
                LookAtTarget(targetPosition);
            }
        }        

注:playIsMoving 是一个控制人物移动的类的对象,它的布尔变量isMoving检测了人物的移动

 


人物的移动:

在Unity中给角色挂载组件 CharacterController

新建一个脚本控制移动

新建一个枚举类型

public enum PlayerStatue {
    Moving,
    Idle
}

点击式移动的人物朝向问题:中存放代码的脚本中的类为“PlayerDirection”

我们在新脚本中定义一个PlayerDirection的对象:dir,从而方便得到目标位置

    //控制角色的移动
    public float speed = 4;
    private PlayerDirection dir;
    public PlayerStatue state = PlayerStatue.Idle;  //角色状态
    private CharacterController controller;
    public bool isMoving = false;  //这个变量记录是否正在移动
    private void Start()
    {
        controller = this.GetComponent<CharacterController>();
        dir = this.GetComponent<PlayerDirection>();
    }
    private void Update()
    {
        //求得当前位置到目标位置的距离
        float distance = Vector3.Distance(dir.targetPosition, transform.position);
        if (distance > 0.5f)  //没有到达目标位置
        {
            isMoving = true;
            //简单移动,无需乘以时间增量
            controller.SimpleMove(transform.forward * speed);
            state = PlayerStatue.Moving;
        }
        else
        {
            isMoving = false;
            state = PlayerStatue.Idle;
        }
    }

人物移动的动画:

先给人物挂载Animation,并赋入相应的动画

然后在脚本里写个方法

    void PlayAnim(string animName)
    {
        GetComponent<Animation>().CrossFade(animName);
    }

用一个状态变量记录,在人物移动的时候,调用这个方法即可(animName是动画的名字,注意不要写错)。


相机跟随:

在相机上挂载脚本,脚本内写下如下内容:

    //处理相机对人物的跟随
    private Transform Player;
    private Vector3 offsetPosition;  //一个位置偏移
    private void Start()
    {
        Player = GameObject.FindGameObjectWithTag(角色标签).transform;
        //相机朝向人物位置
        transform.LookAt(Player.transform.position);
        //记录人物与相机的位置偏移
        offsetPosition = transform.position - Player.position;
    }
    private void Update()
    {
        //永远保持偏移距离
        transform.position = Player.transform.position + offsetPosition;
    }

注意整个场景只能有一个物体有角色标签,一般是叫Player


鼠标滚轮控制相机拉远拉进:

Unity会记录我们的鼠标滚轮信息

利用这个,我们可以实现鼠标控制视野的拉远拉进:

在上面相机跟随:的脚本的基础上这样写:

    private Transform Player;
    public float scrollSpeed = 7;
    private Vector3 offsetPosition;  //一个位置偏移
    public float distance = 0;      //相机与人物的距离

    private void Start()
    {
        Player = GameObject.FindGameObjectWithTag(tags.player).transform;
        //相机朝向人物位置
        transform.LookAt(Player.transform.position);
        //记录人物与相机的位置偏移
        offsetPosition = transform.position - Player.position;
    }
    private void Update()
    {
        //永远保持偏移距离
        transform.position = Player.transform.position + offsetPosition;
        //处理视野的拉近和拉远效果
        ScrollView();
    }
    void ScrollView()
    {
        //Input.GetAxis("Mouse ScrollWheel")向后返回负值,向前正值
        distance = offsetPosition.magnitude; //偏移的距离
        distance += Input.GetAxis("Mouse ScrollWheel") * scrollSpeed;
        //对距离做出一个限制
        distance = Mathf.Clamp(distance,4, 15);
        //用偏移位置的方向向量(normalized)乘以新距离
        offsetPosition = offsetPosition.normalized * distance;
    }

鼠标控制视野旋转:

定义变量:

    public float rotateSpeed = 5;
    private bool isRotating=false;       //标志

写方法:

    void RotateView()
    {
        //Input.GetAxis("Mouse X");  //得到鼠标在水平方向的滑动
        //Input.GetAxis("Mouse Y");  //得到鼠标在竖直方向的滑动
        if (Input.GetMouseButtonDown(1))  //按下鼠标右键
        {
            isRotating = true;
        }
        if (Input.GetMouseButtonUp(1))
        {
            isRotating = false;
        }
        if (isRotating)
        {
            //绕某一点,某一轴旋转(相机初始化会朝向人物,所以尽量将
            //人物与相机放在相同x轴上,z值为0)
            transform.RotateAround(Player.position,Player.up,rotateSpeed* Input.GetAxis("Mouse X"));
            //对上下方向的旋转要做一个范围限制
            //保存原来的数据(position,rotation)
            Vector3 originalPos = transform.position;
            Quaternion originalRot = transform.rotation;
            transform.RotateAround(Player.position,transform.right, -rotateSpeed * Input.GetAxis("Mouse Y"));
            //得到x的旋转
            float x = transform.eulerAngles.x;
            if (x < 10 || x > 80)  //超出范围,还原这一步旋转
            {
                transform.position = originalPos;
                transform.rotation = originalRot;
            }
        }
        //更新offsetPosition
        offsetPosition = transform.position - Player.position;
    }

其中有两点要注意:

  • 在Unity3D编辑中,要让相机与被跟踪物体保持位置x的相同,旋转z为0
  • 如果将RotateView方法与前面的ScrollView并用,要先调用RotateView,因为RotateView中对位置进行了归置

 


★第三人称镜头主导键控移动:

在上面做到鼠标遥控镜头视野后,我们就可以来通过镜头实现键控移动:

首先要求人物挂载CharacterController

然后就是:

    Transform MainCamera;//相机对象
    CharacterController controller;//Charactor Controller组件
    public float Speed = 6.0f;//移动速度
    void Start()
    {
        MainCamera = GameObject.FindGameObjectWithTag("MainCamera").transform;//获取主摄像机对象
        controller = GetComponent<CharacterController>();
    }
    void Update()
    {
        if ((Input.GetKey(KeyCode.W)) || (Input.GetKey(KeyCode.S)) || (Input.GetKey(KeyCode.A)) || (Input.GetKey(KeyCode.D)))
        {
            if (Input.GetKey(KeyCode.A))        //检测按键
            {
                //根据主相机的朝向决定人物的移动方向
                controller.transform.eulerAngles = new Vector3(0, MainCamera.transform.eulerAngles.y + 270f, 0);
            }
            if (Input.GetKey(KeyCode.D))
            {
                controller.transform.eulerAngles = new Vector3(0, MainCamera.transform.eulerAngles.y + 90f, 0);
            }
            if (Input.GetKey(KeyCode.W))
            {
                controller.transform.eulerAngles = new Vector3(0, MainCamera.transform.eulerAngles.y, 0);
            }

            if (Input.GetKey(KeyCode.S))
            {
                controller.transform.eulerAngles = new Vector3(0, MainCamera.transform.eulerAngles.y + 180f, 0);
            }
            controller.Move(transform.forward * Time.deltaTime * Speed);  //向朝向移动
        }
        else
        {
            //未移动状态。
        }
    }

大功告成!

找个合适的地方加个人物行走动画就完美了!

 


点击了NPC或者某物体:

直接在物体上挂载这个方法即可:

    //当鼠标位于这个collider之上的时候,会在每一帧调用这个方法
    private void OnMouseOver()
    {
        if (Input.GetMouseButtonDown(0)) { 

        }
    }

 


第一人称控制视角

第一人称,首先在场景中创建一个空物体,然后给物体挂载组件——角色控制器(charactor controller)

然后挂载脚本

   public int life = 5;

    private new Transform transform; // 此处使用new关键字隐藏父类的同名成员
    private CharacterController controller;
    private float speed = 3.0f;
    private float gravity = 2.0f;




    private Transform cameraTransform; // 摄像机的Transform组件
    private Vector3 cameraRotation; // 摄像机旋转角度
    private float cameraHeight = 1.7f; // 摄像机高度(即主角的眼睛高度)


    /// <summary>
    /// 初始化,获取Transform和Character Controller组件
    /// </summary>
    void Start()
    {
        transform = GetComponent<Transform>();
        controller = GetComponent<CharacterController>();

        // 获取摄像机
        cameraTransform = Camera.main.GetComponent<Transform>();
        // 设置摄像机初始位置
        Vector3 position = transform.position;
        position.y += cameraHeight;
        cameraTransform.position = position;
        // 设置摄像机的初始旋转方向
        cameraTransform.rotation = transform.rotation;
        cameraRotation = cameraTransform.eulerAngles;
        // 锁定鼠标
        Cursor.lockState = CursorLockMode.Locked;

    }

    /// <summary>
    /// 控制主角行动,如果生命为0则什么也不做
    /// </summary>
    void Update()
    {
        if (life <= 0)
        {
            return;
        }
        Control();
    }

    /// <summary>
    /// 在Unity编辑器中为主角显示一个图标
    /// </summary>
    void OnDrawGizmos()
    {
        Gizmos.DrawIcon(GetComponent<Transform>().position, "Spawn.tif");
    }

    /// <summary>
    /// 控制主角的重力运动和前后左右移动
    /// </summary>
    private void Control()
    {
        /*通过控制鼠标来旋转摄像机的方向,使主角跟随摄像机的方向绕Y轴旋转。在移动主角的时候,又使摄像机跟随主角运动。
         */

        // 获取鼠标移动距离
        float rh = Input.GetAxis("Mouse X");
        float rv = Input.GetAxis("Mouse Y");
        // 旋转摄像机
        cameraRotation.x -= rv;
        cameraRotation.y += rh;
        cameraTransform.eulerAngles = cameraRotation;
        // 使主角的面向方向与摄像机一致
        Vector3 rotation = cameraTransform.eulerAngles;
        rotation.x = 0;
        rotation.z = 0;
        transform.eulerAngles = rotation;

        // 控制主角运动
        float x = 0, y = 0, z = 0;
        // 重力运动
        y -= gravity * Time.deltaTime;
        // 前后移动
        if (Input.GetKey(KeyCode.W))
        {
            z += speed * Time.deltaTime;
        }
        else if (Input.GetKey(KeyCode.S))
        {
            z -= speed * Time.deltaTime;
        }
        // 左右移动
        if (Input.GetKey(KeyCode.A))
        {
            x -= speed * Time.deltaTime;
        }
        else if (Input.GetKey(KeyCode.D))
        {
            x += speed * Time.deltaTime;
        }
        // 使用Character Controller而不是Transform提供的Move方法
        // 因为Character Controller提供的Move方法会自动进行碰撞检测
        //transform.TransformDirection方法将以当前游戏体为参考的坐标系内的向量转换为游戏世界坐标系内的向量。
        controller.Move(transform.TransformDirection(new Vector3(x, y, z)));


        // 使摄像机的位置与主角一致
        Vector3 position = transform.position;
        position.y += cameraHeight;
        cameraTransform.position = position;
    }

这是一段不错的代码,出处是我最近学习的一个射击游戏,里面还有生命值等属性,大家注意。

 


基于导航系统的僵尸机制

这个放到这里做个参考,下面这段代码需要你先烘焙好导航系统,并且在base layout将动画配置好

    /*Vector3类是三维空间向量的封装类,通过向量减法可以得到从敌人指向主角的方向向量。
    Vector3.RotateTowards方法通过目标点和自身的位置就可以计算出由欧拉角表示的旋转
    至目标方向的旋转角度。Quaternion类是四元数的封装类,LookRotation方法可以计算出
    由四元数表示的旋转至目标方向的旋转角度。*/
    /// 转向目标方向
    private void RotateTo()
    {
        // 获取目标方向
        Vector3 targetDirection = player.transform.position - transform.position;
        // 计算旋转角度
        Vector3 newDirection = Vector3.RotateTowards(transform.forward,
            targetDirection, rotateSpeed * Time.deltaTime, 0);
        // 旋转至新方向
        transform.rotation = Quaternion.LookRotation(newDirection);
    }
    /// 更新敌人行为
    void Update()
    {
        // 如果主角生命为0,则什么也不做
        if (player.life <= 0)
        {
            return;
        }
        // 获取当前动画状态
        AnimatorStateInfo info = animator.GetCurrentAnimatorStateInfo(0);  // 待机状态
        if (info.fullPathHash == Animator.StringToHash("Base Layer.idle")
            && !animator.IsInTransition(0))    //不在过渡
        {
            animator.SetBool("idle", false);
            // 待机一定时间
            timer -= Time.deltaTime;
            if (timer > 0)
            {
                return;
            }
            // 如果距离主角1.5米以内,则进入攻击状态
            if (Vector3.Distance(transform.position, player.transform.position) < 1.5f)
            {
                animator.SetBool("attack", true);
            }
            else
            {
                // 重置定时器
                timer = 1;
                // 恢复寻路
                agent.isStopped=false;
                // 设置寻路目标
                agent.SetDestination(player.transform.position);
                // 面向主角
                RotateTo();
                Debug.Log(agent.destination);
                // 进入行走状态
                animator.SetBool("run", true);
            }
        }
        // 行走状态
        if (info.fullPathHash == Animator.StringToHash("Base Layer.run")
            && !animator.IsInTransition(0))
        {
            animator.SetBool("run", false);
            // 每隔1秒重新定位主角的位置
            timer -= Time.deltaTime;
            if (timer < 0)
            {
                timer = 1;
                // 面向主角
                RotateTo();
                agent.SetDestination(player.transform.position);
            }
            // 如果距离主角1.5米以内,则进攻主角
            if (Vector3.Distance(transform.position, player.transform.position) < 1.5f)
            {
                // 停止寻路
                agent.isStopped=true;
                // 进入攻击状态
                animator.SetBool("attack", true);
            }
        }
        // 攻击状态
        if (info.fullPathHash == Animator.StringToHash("Base Layer.attack")
            && !animator.IsInTransition(0))
        {
            animator.SetBool("attack", false);
            // 面向主角
            RotateTo();
            // 如果动画播放完,重新进入待机状态
            if (info.normalizedTime >= 1)
            {
                animator.SetBool("idle", true);
                // 重置计时器
                timer = 2;
            }
        }
    }

 


第一人称射击游戏的要点

要在start中获取枪口位置(方便射线检测)以及锁定鼠标

        // 锁定鼠标
        Cursor.lockState = CursorLockMode.Locked;

        // 获取枪口
        muzzlePoint = cameraTransform.Find("M16").Find("weapon").Find("muzzlepoint").GetComponent<Transform>();

在update中的射击


        // 更新射击间隔时间
        shootTimer -= Time.deltaTime;
        // 鼠标左键射击
        if (Input.GetMouseButton(0) && shootTimer <= 0)
        {
            shootTimer = 0.1f;
            // 播放射击音效
            GetComponent<AudioSource>().PlayOneShot(clip);
            // 更新UI,减少弹药数量
            GameManager.Instance.SetAmmo(1);
            // 用一个RaycastHit对象保存射线的碰撞结果
            RaycastHit info;
            // 从枪口所在位置向摄像机面向的正前方发出一条射线
            // 该射线只与layer指定的层发生碰撞
            if (Physics.Raycast(muzzlePoint.position,
                cameraTransform.TransformDirection(Vector3.forward),
                out info, 100, layer))
            {
                // 判断是否射中Tag为enemy的物体
                if (info.transform.tag.Equals("enemy"))
                {
                    // 敌人减少生命
                    info.transform.GetComponent<Enemy>().OnDamage(1);
                }
            }
            // 在射中的地方释放一个粒子效果
            Instantiate(fx, info.point, info.transform.rotation);
        }

在update中的控制人物移动

        // 获取鼠标移动距离
        float rh = Input.GetAxis("Mouse X");
        float rv = Input.GetAxis("Mouse Y");
        // 旋转摄像机
        cameraRotation.x -= rv;
        cameraRotation.y += rh;
        cameraTransform.eulerAngles = cameraRotation;
        // 使主角的面向方向与摄像机一致
        Vector3 rotation = cameraTransform.eulerAngles;
        rotation.x = 0;
        rotation.z = 0;
        transform.eulerAngles = rotation;

        // 控制主角运动
        float x = 0, y = 0, z = 0;
        // 重力运动
        y -= gravity * Time.deltaTime;
        // 前后移动
        if (Input.GetKey(KeyCode.W))
        {
            z += speed * Time.deltaTime;
        }
        else if (Input.GetKey(KeyCode.S))
        {
            z -= speed * Time.deltaTime;
        }
        // 左右移动
        if (Input.GetKey(KeyCode.A))
        {
            x -= speed * Time.deltaTime;
        }
        else if (Input.GetKey(KeyCode.D))
        {
            x += speed * Time.deltaTime;
        }
        // 使用Character Controller而不是Transform提供的Move方法
        // 因为Character Controller提供的Move方法会自动进行碰撞检测
        //transform.TransformDirection方法将以当前游戏体为参考的坐标系内的向量转换为游戏世界坐标系内的向量。
        controller.Move(transform.TransformDirection(new Vector3(x, y, z)));


        // 使摄像机的位置与主角一致
        Vector3 position = transform.position;
        position.y += cameraHeight;
        cameraTransform.position = position;

 


游戏打击感的实现:

顿帧:

    IEnumerator HasHit() {
        for (int i = 0; i < 2; i++)
        {
            anim.speed = i;
            yield return new WaitForSeconds(0.4f);
        }
    }

 

 


三维世界简单血条实现:

在人物身上挂一个world世界的Canvas,然后canvas下建立一个Image,给Image的Source Image选择一张合适的图片,然后将Image Type 改为 Filled,通过调节Fill Amount即可实现掉血功能,代码

        _BloodSlider.fillAmount = _life / Totallife;

另外还可以设置Canvas始终朝向相机从而更真实

 

 

 

 

 

 

 

 

 


上面是目前对RPG类游戏的一些模板的记录,后续我还会有更新~

 

 

 

 

 

 

 

商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢

 

 

 

发表评论