想做一个比较完整的游戏,又不想去提前策划做什么,于是决定想到什么就做什么,看最后能做出什么来。

前段时间玩了《莱莎的炼金工坊》,觉得这游戏的移动手感挺好的,首先就做一个类似莱莎的移动系统吧。

我从AssetStore上下载了场景资源NatureStarterKit2以及人物模型资源unity-chan!,导入项目中就开始制作啦。

首先是相机跟随,一般的相机跟随很简单,只需要把相机挂载在人物上即可,但在《莱莎的炼金工坊》里,相机是可以围绕着人物转的,与人物的视角方向不一定相同,所以要另外写脚本。

unity可以开发开发的游戏底层是什么语言_Time

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

public class CameraFollow : MonoBehaviour {

    private GameObject Player;
    private Vector3 offset;//相机和玩家初始偏移
    enum RotationAxes //鼠标旋转方式
    {
        MouseXAndY,
        MouseX,
        MouseY
    }
    RotationAxes axes = RotationAxes.MouseXAndY;//将初始移动方式设置成xy方向都可以

    public float sensitivityX = 30;//镜头敏感度
    public float sensitivityY = 10;

    public float minimumY = -10;//镜头上下移动限制
    public float maximumY =  30;
    private float rotationY = 0;

    void Start()
    {
        FindPlayer();
        initDisZ = Mathf.Abs(Player.transform.position.z - Camera.main.transform.position.z);
        
    }
    void FindPlayer()
    {
        Player = GameObject.FindGameObjectWithTag(Tags.Player);
        offset = transform.position - Player.transform.position;

    }
    public void Update()
    {
        transform.position = Player.transform.position + offset;
        if (Input.GetMouseButton(1))//右键
        {
            GameManager.Instance.isMoveMouse = true;
            if (axes == RotationAxes.MouseXAndY)
            {
                float rotationX =Input.GetAxis("Mouse X");
                rotationY += Input.GetAxis("Mouse Y") * sensitivityY;
                rotationY = Mathf.Clamp(rotationY, minimumY, maximumY);
                Camera.main.transform.localEulerAngles = new Vector3(-rotationY,Camera.main.transform.localEulerAngles.y, 0);
                transform.RotateAround(Player.transform.position,Vector3.up,rotationX * sensitivityX * Time.deltaTime);//以玩家所在位置为中心,世界坐标系的向上方向为轴向(此时强调为世界坐标系,是因为玩家角色并不能保证一直处于正常的站立模式,如果是玩家自身坐标系的话,一旦玩家角色发生歪斜,相机的环绕就会出现问题),进行环绕
                GameManager.Instance.forward = new Vector3(gameObject.transform.forward.x, 0, gameObject.transform.forward.z).normalized;//将行动的前方设置成相机的前方而不是人物的前方
                offset = transform.position - Player.transform.position;

            }
            else if (axes == RotationAxes.MouseX)
            {
                Camera.main.transform.Rotate(0, Input.GetAxis("Mouse X") * sensitivityX, 0);
            }
            else
            {
                rotationY += Input.GetAxis("Mouse Y") * sensitivityY;
                rotationY = Mathf.Clamp(rotationY, minimumY, maximumY);
                Camera.main.transform.localEulerAngles = new Vector3(-rotationY, Camera.main.transform.localEulerAngles.y, 0);
            }
        }

        if (Input.GetAxis("Mouse ScrollWheel") > 0)//滚轮控制视角范围
        {
            if (Camera.main.fieldOfView >= 55)
            {
                Camera.main.fieldOfView -= 5;
            }
        }
        else if(Input.GetAxis("Mouse ScrollWheel") < 0)
        {
            if(Camera.main.fieldOfView<=85)
            {
                Camera.main.fieldOfView += 5;
            }
        }
    }
}

相机的跟随部分就完成了,只需要把该脚本挂在主相机上即可。

接下来是人物行动部分。

一开始打算用2D混合树来做移动动画

unity可以开发开发的游戏底层是什么语言_System_02


后面发现《莱莎的炼金工坊》里并没有向左向右向后的概率,所有移动都是向前,只是方向不一样,之后改成了普通的动画,在代码里调整方向。然后加入了闲置时的idle动画切换,跳跃动画,用1D混合树来制作走路与跑步的切换(虽然目前没用到混合的效果)。

为了方便,我直接在OnGUI里绘制了行走和跑步之间的切换。

为了达到切换平滑的效果,我用了DoTween插件,中间也踩了一些坑,比如最后角度需要模360度,不然会出现很鬼畜的动画。

这是最终的状态机

unity可以开发开发的游戏底层是什么语言_Time_03


unity可以开发开发的游戏底层是什么语言_Time_04


代码

using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class PlayerAct : MonoBehaviour {


    private Animator animator;
    private float ChangeIdleTimer = 0;
    private float ChangeIdleTime = 10f;
    private CharacterController CharacterController;


    /// <summary>
    /// 动画相关
    /// </summary>
   // public float speedMax = 4f;
    [Header("移动速度")]
    public float speed = 4f;
    [Header("转向速度")]
    public float turnSpeed = 1f;
    private bool isRunState = false;
    private readonly string WaitStateName = "WAIT00";
    private readonly string JumpStateName = "JUMP00B";
    private readonly string WinStateName = "WIN00";
    [SerializeField]
    private float h;
    [SerializeField]
    private float v;
    private Tween tween;//移动动画
    void Start () {
        animator = transform.GetComponent<Animator>();
        CharacterController = GetComponent<CharacterController>();
	}

    private void Update()
    {
        ChangeIdleTimer += Time.deltaTime;
        if(ChangeIdleTimer>ChangeIdleTime)
        {
            ChangeIdleState();
            ChangeIdleTimer = 0;
        }
    }

    private void ChangeIdleState()//定时切换idle状态
    {
        if (animator.GetCurrentAnimatorClipInfo(0)[0].clip.name.CompareTo(WaitStateName) != 0)//不在休闲状态则不切换到休闲动画
        {
            return;
        }
        int index = Random.Range(1, 5);
        switch(index)
        {
            case 1:animator.SetTrigger("Wait1");
                break;
            case 2:
                animator.SetTrigger("Wait2");
                break;
            case 3:
                animator.SetTrigger("Wait3");
                break;
            case 4:
                animator.SetTrigger("Wait4");
                break;
        }
    }

    void OnGUI()
    {
        Rect rect1 = new Rect(10, Screen.height - 40, 400, 30);
        isRunState = GUI.Toggle(rect1, isRunState, "Run");
    }
    void FixedUpdate()
    {
        tween.Kill();//终止动画
        GetAnimInput();
        Move();
    }

    private void GetAnimInput()
    {
        if (Input.GetKeyDown(KeyCode.Space) && animator.GetCurrentAnimatorClipInfo(0)[0].clip.name.CompareTo(JumpStateName) != 0)//避免重复触发造成动画延迟播放
            animator.SetTrigger("Jump");

        if (Input.GetKeyDown(KeyCode.O) && animator.GetCurrentAnimatorClipInfo(0)[0].clip.name.CompareTo(WinStateName) != 0 && animator.GetCurrentAnimatorClipInfo(0)[0].clip.name.CompareTo(WaitStateName) == 0)
            animator.SetTrigger("Win");
        if (Input.GetKey(KeyCode.UpArrow) || Input.GetKey(KeyCode.W)) v = 1;
        else if (Input.GetKey(KeyCode.DownArrow) || Input.GetKey(KeyCode.S)) v = -1;
        else v = 0;

        if (Input.GetKey(KeyCode.LeftArrow) || Input.GetKey(KeyCode.A)) h = -1;
        else if (Input.GetKey(KeyCode.RightArrow) || Input.GetKey(KeyCode.D)) h = 1;
        else h = 0;

    }
    private void Move()
    {
        Vector3 normal = Vector3.Cross(GameManager.Instance.forward, Vector3.up);
        float movespeed = isRunState ? speed : speed / 2.5f;
        CharacterController.SimpleMove(v * GameManager.Instance.forward * movespeed + h * (-normal) * movespeed);
        int isRun = isRunState ? 1 : 0;
        animator.SetFloat("V", isRun);
        if (h != 0 && v != 0)
        {
            float turnAng = h > 0 ? 90 : 270;
            float moveAng = v > 0 ? 0 : 180;
            float finalAng = h < 0 && v > 0 ? 315 : (turnAng + moveAng) / 2;
            
            tween = transform.DORotate(new Vector3(transform.eulerAngles.x, (Camera.main.transform.eulerAngles.y + finalAng) % 360), Time.fixedDeltaTime * turnSpeed);
        }
        else
        {
            if (h != 0)
            {
                float turnAng = h > 0 ? 90 : 270;
                tween = transform.DORotate(new Vector3(transform.eulerAngles.x, (Camera.main.transform.eulerAngles.y + turnAng) % 360), Time.fixedDeltaTime * turnSpeed);
               
            }
            if (v != 0)
            {
                float moveAng = v > 0 ? 0 : 180;
                tween = transform.DORotate(new Vector3(transform.eulerAngles.x, (Camera.main.transform.eulerAngles.y + moveAng) % 360), Time.fixedDeltaTime * turnSpeed);
            }
        }



        if (h != 0 || v != 0)
        {
         	("IsMoving", true);
        }
        else
        {
            animator.SetBool("IsMoving", false);
        }
    }
    
  
}

还有我做游戏必备的GameManager类。。虽然现在还很简单

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

public class GameManager : MonoBehaviour {

    private static GameManager _instance;
    public static GameManager Instance
    {
        get
        {
            return _instance;
        }
    }

    public Vector3 forward =new Vector3 (0, 0, 1);
    private void Start()
    {
        if (_instance == null)
        {
            _instance = this;
        }
    }

   
}

然后这就是最终效果

unity可以开发开发的游戏底层是什么语言_游戏开发_05


(格式工厂转的GIF质量太差了,网页上转的质量又太好了超出大小限制。。)