【Unity】使用Unity编写传统ARPG游戏的人物操作方式

(Ps:本文为个人原创旧文章,原发布地址为:CSDN
首先,本人个人尤其喜欢ARPG游戏,原因在于ARPG游戏不仅可以通过修改游戏难度考验玩家的个人操作能力,还可以通过或感人,或令人发省的游戏剧情引导玩家一步一步的思考,最终明白制作者或者制作团队想要倾诉的感情。不论是在制作ARPG游戏抑或是其他什么类型的游戏,人物的操纵都应该是最优先考虑的问题。我们想做什么样的游戏,怎么操作,在这种模式下怎么使用最普通的资源实现怎么样令人眼前一亮的想法与创新是最基础也最重要的问题。今天就来说明这最基础的东西——传统ARPG游戏的6种人物行走方式。本人也是一名大三学生,课程虽已接近结束,但是还是需要为一些不知所谓的考试没日没夜的烦恼着,唉,不谈了。直接进入正题。
我将传统ARPG游戏的操作方式分为6种,区分的根据主要是按照现已存在的游戏的操作方式进过分析后加以区分归类,这时要首先感谢我这群跟我一样喜爱游戏,并且拥有十分广大游戏经验的舍友,他们在我进行游戏操作方式分析过程中给予了十分详细的经验介绍和帮助。好,我对ARPG游戏操纵方式的分类是:
1) 经典2D横版操作方式,如GBA版本的《恶魔城》系列;
2) 经典2D全屏行走操作方式,如某腾旗下的《地下城与勇士》系列;
3) 经典3D固定角度操作方式,如仙剑系列的地图行走方式;
4) 经典3D开放角度操作方式,如日本SQUARE ENIX旗下的一款《最终幻想零式》游戏。
5) 经典3D无锁定视角操作方式,我自己在写的一款游戏就是采用这种操作方式,这次正好用上了;
6) 经典3D固定面向方向操作方式,如盛大旗下代理的一款韩国游戏《龙之谷》;
好,接下来,sunset对每种类型的操作方式进行了独立分析,并使用编写了简单的实现脚本。

1) 经典2D横版操作方式

相信提到《恶魔城》这款游戏,大多人都是不陌生的甚至玩过该系列的全部版本,它是一款非常非常接近我们童年的一款ARPG游戏。首先,2D恶魔城的操作方式根据视觉感官给我们的感受就是简单按下左键进行左向移动,按下右键进行右向移动,按下跳跃键进行跳跃,实际上也是如此。在实现难度上属于较为简单的。但是有两种实现这种操作方式的方法,第一种是使用对刚体添加向左或者向右的力的方式实现,第二种则是通过修改刚体的向量的方式进行实现,首先是第一种方式的实现代码:

 public enum State
    {
        Idle,
        Crounch,
        Walk,
        Jump,
    }
    public float MoveForce = 365.0f;
    public float MaxSpeed = 5.0f;
    public float JumpForce = 360;

    //private Variable
    private float V, H;
    private Rigidbody2D rigi;
    private State state = State.Idle;
    private float CurrentForce;
    private bool Jump;
    private Transform Ground;
    private bool OnGround = false;

    
	// Use this for initialization
	void Start () 
    {
	    rigi = this.GetComponent<Rigidbody2D>();
        Ground = transform.Find("Ground");
	}
	
	// Update is called once per frame
	void Update () 
    {
        OnGround = Physics2D.Linecast(transform.position, Ground.position, 1 << LayerMask.NameToLayer("Ground"));
        V = Input.GetAxis("Vertical");
        H = Input.GetAxis("Horizontal");
        if (Input.GetKeyDown(KeyCode.J) && OnGround)
        {
            
            Jump = true;
        }
        JudgeState();
	}
    void FixedUpdate()
    {
       
        if (H * rigi.velocity.x < MaxSpeed && state != State.Crounch)
        {
            rigi.AddForce(Vector2.right * H * CurrentForce);
        }
        if (Mathf.Abs(rigi.velocity.x) > MaxSpeed && state != State.Crounch)
        {
            rigi.velocity = new Vector2(Mathf.Sign(rigi.velocity.x) * MaxSpeed, rigi.velocity.y);
        }
        if (Jump)
        {
            state = State.Jump;
            rigi.AddForce(new Vector2(0, JumpForce));
            Jump = false;
        }
        if (V < -0.3)
        {
            state = State.Crounch;
        }
        else
        {
            if (H != 0)
            {
                state = State.Walk;
            }
            else
            {
                state = State.Idle;
            }
        }
    }
    void JudgeState()
    {
        if (state == State.Idle)
        {
            CurrentForce = 0;
            //播放休闲动画
        }
        else if (state == State.Walk)
        {
            //播放行走动画
            CurrentForce = MoveForce;
        }
        else if (state == State.Jump)
        {
            //播放跳跃动画
        }
    }

第一种方式相较于第二种更类似于《超级马里奥》的那种移动方式,由于是利用力来实现运动,所以每次移动都存在物理惯性,操作过程中也许无法在我们所想要人物停止的地方停止,往往会有一定位置偏移,而《恶魔城》的操作方式则更倾向于第二种方式,按下移动键就进行移动,松开移动键则控制角色停止运动。这两种操作方式之间的代码的区别只是修改了移动的方式而已,并不难已理解,接下来是第二种方式的移动代码:

 if (H >= 0.3)
        {
            if (!FacingRight)
            {
                ChangeFacing();
            }
        }
        else if (H <= -0.3)
        {
            if (FacingRight)
            {
                ChangeFacing();
            }
        }
        *rigidbody2D.velocity = new Vector2(H * CurrentSpeed, 0);*

这两种方式里,跳跃的控制都是一样的,都是利用2D刚体添加合适的向上力来完成。代码较为简单,我就不一一介绍了,很多东西查查API就一目了然了。

2) 经典2D全屏行走操作方式##

在这种方式中,我们提到的例子是《地下城与勇士》。当然,《地下城与勇士》也是一款十分具有人气的2D游戏。我的某位舍友在短短时间内已经在上面花费了超过两千元了。接下来让我们分析一下这种移动方式:可以直观地看到这种移动方式与第一种2D横版操作方式有共同点但是也有很大区别。主要共同点在于都是左右进行移动,区别在于全屏行走操作方式可向上或向下行走。稍微有点基础的看过都会明白,修改X轴向量和Y轴向量就可以实现这种效果。为了更加贴合DNF的操作方式,我采用修改刚体向量的方式进行移动,在上文中已经介绍过了。下面是我的简单实现代码:

 public enum State
    {
        Walk,
        Run,
        Idle,
        Jump,
    }
    private Rigidbody2D rigidbody2d;
    private Animator animator;
    private float V;
    private float H;
    private State state;
    private float CurrentSpeed = 10.0f;
    private bool FacingRight = true;
    private Transform Ground;
    private bool OnGround;
    private bool Jump;
  
    //public
    public float MoveSpeed;
    public float RunSpeed;
    public float JumpForce;
	// Use this for initialization
	void Start () 
    {
        Ground = transform.Find("Ground");
        rigidbody2d = this.GetComponent<Rigidbody2D>();
        animator = this.GetComponent<Animator>();
        FacingRight = true;//保证初始状态所面朝的方向为true方向
      
        
	}
	
	// Update is called once per frame
	void Update () 
    {
        OnGround = Physics2D.Linecast(transform.position, Ground.position, 1 << LayerMask.NameToLayer("Ground"));
        V = Input.GetAxis("Vertical");
        H = Input.GetAxis("Horizontal");
        if (Input.GetKeyDown(KeyCode.J) && OnGround)
        {
            Jump = true;
        }
        SpeedAndAnimator();
	}

    void FixedUpdate()
    {
        AnimatorStateInfo animatorState = animator.GetCurrentAnimatorStateInfo(0);
        if (H > 0.3 )
        {
            state = State.Walk;
            if(!FacingRight)
                ChangeFacing();
        }
        if (H < -0.3)
        {
            state = State.Walk;
            if(FacingRight)
                ChangeFacing();
        }
       if (Jump)
        {
            state = State.Jump;
            rigidbody2d.gravityScale = 1.0f;
            rigidbody2d.AddForce(new Vector2(0, JumpForce));
            Jump = false;
        }
        if (OnGround)
        {
            rigidbody2d.gravityScale = 0f;
        }
        if (animatorState.IsName("Walk") && animatorState.normalizedTime > 0.1f && Input.GetAxis("Horizontal") > 0.5)
        {
           state = State.Run;
        }
        if (state == State.Walk || state == State.Run && rigidbody2d.velocity.y < 15.0f)
        {
            rigidbody2d.velocity = new Vector2(H * CurrentSpeed, V * CurrentSpeed);
        }
    }
    void ChangeFacing()
    {
        FacingRight = !FacingRight;
        Vector3 theScale = this.transform.localScale;
        theScale.x *= -1;
        transform.localScale = theScale;
    }

    void SpeedAndAnimator()
    {
        if (state == State.Walk)
        {
            animator.SetBool("Walk",true);
            animator.SetBool("Run", false);
            CurrentSpeed = MoveSpeed;
        }
        else if(state == State.Run)
        {
            animator.SetBool("Walk", false);
            animator.SetBool("Run", true);
            CurrentSpeed = RunSpeed;
        }
        else if(state == State.Idle)
        {
            animator.SetBool("Walk", false);
            animator.SetBool("Run", false);
            CurrentSpeed = 0;
        }
        else if (state == State.Jump)
        {
            //播放跳跃动画
        }
    }

其实,对于2D操作方式而言,实现的代码都差不多,最大的区别也仅仅是修改一些变量而已,并不难以理解,不再赘述。

3) 经典3D固定角度操作方式

这种操作方式我在介绍时采用《仙剑》系列作为举例,但是如果硬要说仙剑不是ARPG游戏,那更好的例子就是烛龙公司旗下《古剑奇谭2》里面的战斗时的操作方式了。个人之所以将这种方式命名为固定角度的原因在于,这种操作方式它在游戏中并没有转动角速度这种概念的存在,简单的说就是人物面向的角度只有右、上、左、下、右上、右下、左上、左下,即角度的0、90、180、270、45、315、135、225这几种类型。所以称为固定角度。但是与2D游戏相比,3D游戏中视角的概念是尤为突出,或许这个类型的游戏中视角是全方位的的,但是人物行走方式的角度选择却是有所限制的。也许我的表达方式不太好,不知道能不能理解。接下来在介绍一下实现这种方式所用到的方法,我们使用Unity里面的CharacterController(角色控制器)组件中的Move()方法来实现行走,这个方法也是3D游戏里面经常用到的控制运动的方法。先给出这种操作方式的实现代码吧:

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(CharacterController))]
[RequireComponent(typeof(Animator))]
public class Xianjian : MonoBehaviour {

    //定义玩家的状态枚举
    public enum State
    {
        Idle,
        Walk
    }

    //定义八个方向的枚举值
    protected enum DirectionType
    {
        Direction_Forward = 90,
        Direction_Backward = 270,
        Direction_Left = 180,
        Direction_Right = 0,
        Direction_RightForward = 45,
        Direction_LeftForward = 125,
        Direction_LeftBackward = 225,
        Direction_RightBackward = 315,
    }
    [HideInInspector]
    public State state = State.Idle;
	/// <summary>
    /// 玩家的行走速度
	/// </summary>
	public float WalkSpeed=1.5F;
	/// <summary>
	/// 重力
	/// </summary>
    public float Gravity;
    /// <summary>
    /// 角色控制器
    /// </summary>
	private CharacterController Controller;
	/// <summary>
    /// 动画组件
	/// </summary>
	private Animator animator;
	/// <summary>
    /// 玩家方向,默认向前
	/// </summary>
	private DirectionType mType=DirectionType.Direction_Forward;
    /// <summary>
    /// 横轴和竖轴量
    /// </summary>
    private float V, H;
    /// <summary>
    /// 重力偏移量
    /// </summary>
    private float y;



    void Awake()
    {
        //获取角色控制器
        Controller = this.GetComponent<CharacterController>();
        //获取动画组件
        animator = this.GetComponentInChildren<Animator>();
    }
	
	void Update () 
	{
        V = Input.GetAxis("Vertical");
        H = Input.GetAxis("Horizontal");
	}

    void FixedUpdate()
    {
        MoveManager();
    }

	//玩家移动控制
	void MoveManager()
	{
		//移动方向
        Vector3 MoveDirection = Vector3.zero;
        if (Controller.isGrounded)
		{
            state = State.Walk;
			//将角色旋转到对应的方向
			if(V >= 0.3)
			{
				SetDirection(DirectionType.Direction_Forward);
                MoveDirection = Vector3.forward * WalkSpeed;
			}
			if(V <= -0.3)
			{
				SetDirection(DirectionType.Direction_Backward);
                MoveDirection = Vector3.forward * WalkSpeed;
                
			}
			if(H <= -0.3)
			{
				SetDirection(DirectionType.Direction_Left);
                MoveDirection = Vector3.forward * WalkSpeed;
			}
			if(H >= 0.3)
			{
				SetDirection(DirectionType.Direction_Right);
                MoveDirection = Vector3.forward * WalkSpeed;
                
			}
            if (H >= 0.3 && V >= 0.3)
            {
                SetDirection(DirectionType.Direction_RightForward);
                MoveDirection = (Vector3.forward +  Vector3.right).normalized * WalkSpeed;
            }
            if (H >= 0.3 && V <= -0.3)
            {
                SetDirection(DirectionType.Direction_RightBackward);
                MoveDirection = (Vector3.back + Vector3.right).normalized * WalkSpeed;
            }
            if (H <= -0.3 && V >= 0.3)
            {
                SetDirection(DirectionType.Direction_LeftForward);
                MoveDirection = (Vector3.forward + Vector3.left).normalized * WalkSpeed;
            }
            if (H <= -0.3 && V <= -0.3)
            {
                SetDirection(DirectionType.Direction_LeftBackward);
                MoveDirection = (Vector3.back + Vector3.left).normalized * WalkSpeed;
            }
            animator.SetFloat("Walk", V);
            animator.SetFloat("Walk", H);
        }
        MoveDirection = transform.TransformDirection(MoveDirection);
        y = MoveDirection.y - Gravity * Time.deltaTime;
        MoveDirection = new Vector3(MoveDirection.x, y, MoveDirection.z);
        Controller.Move(MoveDirection * Time.deltaTime);
	}
	void SetDirection(DirectionType MyDir)
	{
        if (mType != MyDir)
		{
            transform.Rotate(Vector3.up * (mType - MyDir));
            mType = MyDir;
		}
	}
}

这种操作方法虽然在国产3D单机游戏中十分热门,实现的方式也较为简单,但是代码却比较复杂(可能是本人想的太过复杂或者编程能力还不够),而且这种方式并不是sunset个人认为最好的ARPG操作方式,因为有了限制,即使这种限制很微妙,也可能达到对某些操作的阻碍,或者说对富有想法的玩家是极大的削弱。但是,通过这种方式,我们对3D游戏人物操作方式有了一些新的更深的理解。这个代码虽然看上去较为复杂,但原理其实很简单,我也做了大致的注释,静下心来仔细看看就会理解的,不再一一赘述,如果有什么不理解,欢迎留言,我会详细对每个问题进行解答。
今天写的有点多了,而且很晚了,接下来的三种方式就放在下一次继续介绍吧。我这次写的实现代码都较为简单,实为抛砖引玉,请有更多新想法或者好想法的朋友们留言,(个人最喜欢新想法了!)如果有大大,还请不吝指教!!!
记住:未来的你,会感激今天拼命的你!

文章为个人原创,转载请注明出处( https://www.cnblogs.com/Beyond1900/articles/14484822.html )。

posted @ 2021-03-05 10:42  白杨Beyond  阅读(501)  评论(0编辑  收藏  举报