见缝插针


  考验耐心的小游戏,有一些关卡设计的小技巧。后续有时间可以完成多一点的关卡。

  • 一、游戏框架组成

1.中心球

主要功能,可以旋转(顺时针和逆时针方向),挂载关卡小球,主要的字段有:

public bool isMove;//控制是否运动,当发射小球碰到关卡小球后改变
public int turn ;//控制中心球旋转方向,1顺时针,-1逆时针
public float speed;//控制中心求旋转速度

在Awake方法中初始化,在Updata方法中控制控制球旋转就可以了。

2.关卡球

关卡球用来做障碍物,当发射的球碰到时,游戏失败,所以可以设置一个数组来存储关卡球,每个关卡球只有一根连接中心球的连线,所以挂载在关卡球的脚本只有一个控制LineRenderer组件的方法。

3.发射球

发射小球生成后,朝着中心球运动,当到达一定距离后,围绕着中心球旋转,主要的字段有:

    public float speed;//控制小球运动速度
    private bool isUp;//判断是否继续向上运动
    private Transform centerTransform;//中心球Transform
    private LineRenderer ballLine;//小球的lineRenderer组件
    private LevelManage manage;//关卡管理脚本,用来控制小球发生碰撞后的动作
    private BigBallMove bigMove;//中心球的运动脚本,当小球发生碰撞时,停止运动

主要的方法有:Awake ,FixedUpdate,OnTriggerEnter,ReLoad(小球发生碰撞重载关卡)

4.UI和背景球

如下图,UI有4个Text组成,Level显示关卡,Text1-3显示剩余的小球个数,Ball1-3放在游戏屏幕下方中间,Text1-3分别在Ball1-3上显示,当Text显示0的时候,删除掉相应的Ball。


见缝插针_第1张图片
UI和背景球
5.游戏控制(关卡设计和生成小球)

在层级视图里面创建一个空对象用来挂载游戏控制脚本:

  • 生成小球

用来控制小球的生成,主要字段有:

    public GameObject prefanBall; //预设的小球对象
    public int number;//小球发射的个数
    private Text text;//显示在小球上的3个文本
    private LevelManage manage;//关卡管理脚本,当小球发射完毕后做相应的动作
    private bool toNextLevel;//进入下一关卡的开关

主要的方法有:Awake ,Update,LevelUp(进入下一关卡)。

  • 关卡设计

关卡设计是用来控制加载关卡的,主要包含的字段有:

    public GameObject[] levelsObject;//存储关卡的数组
    public Transform centerTransform;//中心球的Transform
    private int level;//关卡号
    private Text levelText;//显示关卡的文本

方法有:Awake,LoadNextScence(进入下一关卡),LoadAgain(继续本关卡)。

  • 二、代码思路

1.中心球和关卡球

包含中心球,和两个关卡小球,小球围绕着中心球旋转,代码如下:

  • 控制中心球旋转:
public bool isMove;//控制是否运动,当发射小球碰到关卡小球后改变
public int turn ;//控制中心球旋转方向,1顺时针,-1逆时针
public float speed;//控制中心求旋转速度
//初始化相关字段数据
private void Awake()
    {
        turn = 1;
        speed = 120;
        isMove = true;
    }
void FixedUpdate () {
        if(isMove)//判断是否可以运动
//通过transform.Rotate来控制旋转
        transform.Rotate(Vector3.back * turn * speed * Time.deltaTime);
    }
  • 关卡小球与中心球连线
private LineRenderer ballLine;
void Start ()
 {
    ballLine = this.GetComponent();//小球LineRenderer组件
}
void Update ()
{
    ballLine.SetPosition(1, transform.position);//更新LineRenderer的目的坐标
}

完成后,将关卡小球设置为中心球的子对象,这样就可以让小球绕着中心球旋转了。(注意,由于关卡小球需要和发射小球需要判断触发非实体碰撞,所以需要加上rigidbody组件)

2.UI和背景球

UI和背景球设置如下:


见缝插针_第2张图片
UI和背景球
见缝插针_第3张图片
UI和背景球.png

根据上图做好拖拽即可。

3.发射球

发射球需要触发非实体碰撞,同样需要rigidbody组件,同时,在生成后(生成小球由后续游戏控制管理),小球首先朝着中心球运动,到达一定位置后,在围绕旋转,代码如下:

    public float speed;//控制小球运动速度
    private bool isUp;//判断是否继续向上运动
    private Transform centerTransform;//中心球体Transform
    private LineRenderer ballLine;//小球的lineRenderer组件
    private LevelManage manage;//关卡管理脚本,用来控制小球发生碰撞后的动作
    private BigBallMove bigMove;//中心球体的运动脚本,当小球发生碰撞时,停止运动
//初始化数据
    void Awake () {
        speed = 20;
        isUp = true;
        bigMove = GameObject.Find("CenterBigBall").GetComponent();
        centerTransform = GameObject.Find("CenterBigBall").transform;
        ballLine = this.GetComponent();
        manage = GameObject.Find("GameManage").GetComponent();
    }
    void FixedUpdate ()
    {
        if(isUp)//判断是否可以继续向中心球运动
        {
            transform.Translate(Vector3.up * speed * Time.deltaTime);
            if(transform.position.y>=-2.5f)//当Y坐标大于时,停止设置isUp为false
            {
                transform.position = new Vector3(0, -2.5f, 0);
                transform.parent = centerTransform;//设置中心球为父对象,围绕旋转
                isUp = false;
            }
        }
        else//停止之后,设置LineRenderer终点
        {
            ballLine.SetPosition(1, transform.position);
        }    
    }
//判断是否触发碰撞
    private void OnTriggerEnter(Collider other)
    {
//如果碰撞到的tag是Ball的话,说明游戏失败,重新开始这个关卡
        if (other.CompareTag("Ball"))
        {
            bigMove.isMove = false; //中心球停止运动
            Invoke("ReLoad", 1);//1s后调用reload方法
        }
    }
    private void ReLoad()
    {
        manage.LoadAgain();
    }

完成代码后,将其作为一个Prefab,为后续控制小球生成做对象,注意位置和背景小球1一致。

见缝插针_第4张图片
发射小球
4.游戏控制

在层级视图中建立一个空对象来挂游戏管理的脚本:

  • 小球生成
    public GameObject prefanBall; //预设的小球对象
    public int number;//小球发射的个数
    private Text text;//显示在小球上的3个文本
    private LevelManage manage;//关卡管理脚本,当小球发射完毕后做相应的动作
    private bool toNextLevel;//进入下一关卡的开关
    void Awake ()//初始化相关数据
    {
        number = 10;
        manage = this.GetComponent();
        toNextLevel = true;
    }
    void Update ()
    {
        if (number > 0)//当Number>0时才生成小球
        {
            if (Input.GetKeyDown(KeyCode.Mouse0))
            {
                GameObject ball= Instantiate(prefanBall);//实例化一个小球
                number--;//每次生成小球后number-1
            }
        }
        else //小球生成完毕,进入下一关
        {
            //由于是一个时刻都在运行的方法,所以做一个开关
            //进入下一关只需要发生一次即可
            if (toNextLevel)
            {
                Invoke("LevelUp", 2);
                toNextLevel = false;
            }
        }
        //控制text文本的显示,以及显示为0的背景球消失
        for (int i = 1; i <= 3; i++)
        {
            //如果小球上的text内容大于0,则显示内容
            if(number-i+1>0)
            {
                GameObject.Find("Text" + i).GetComponent().text = (number - i + 1).ToString();
            }
            //否则使背景小球消失,text内容至空
            else
            {
                if(GameObject.Find("Ball" + i))
                {
                    GameObject.Find("Text" + i).GetComponent().text = "";
                    GameObject.Find("Ball" + i).SetActive(false);
                }
            }        
        }    
    }
    //过关方法
    void LevelUp()
    {
        manage.LoadNextScence();
    }
  • 关卡设计
      使用到了PlayerPrefs——用于本地持久化保存与读取的类,这里用来保存关卡号。另外,把关卡建立成一个新的对象,用数组保存,这样方便代码的编写和后续的关卡扩展:
见缝插针_第5张图片
关卡数组

代码如下:

    public GameObject[] levelsObject;//存储关卡的数组,在外部拖拽赋值
    public Transform centerTransform;//中心球体的Transform
    private int level;//关卡号
    private Text levelText;//显示关卡的文本

    //初始化关卡,先判断是否有本地保存的关卡,然后读取相应的关卡对象运行
    void Awake ()
    {
        //判断是否有lvl的保存,如果没有则新建立一个
        if(PlayerPrefs.HasKey("lvl")==false)
        {
            PlayerPrefs.SetInt("lvl", 1);
        }
        //设置存储的关卡号
        level = PlayerPrefs.GetInt("lvl");
        levelText = GameObject.Find("Level").GetComponent();
        levelText.text = level.ToString();
        //读取关卡
        GameObject levles=Instantiate(levelsObject[level - 1]);
        //读取关卡后设置关卡的父对象为中心球
        centerTransform = GameObject.Find("CenterBigBall").transform;
        levles.transform.parent = centerTransform;
    }
    //载入下一关,如果是最后一关则重新开始
    public void LoadNextScence()
    {
        if(level>=3)//判断是否是最后一关
        {
            level = 1;   
        }
        else
        {
            level++;   
        }
        //保存下一关的关卡,重新加载场景后,进入下一关
        PlayerPrefs.SetInt("lvl", level);      
        SceneManager.LoadScene(0);
    }
    //重新载入本关卡
    public void LoadAgain()
    {
        SceneManager.LoadScene(0);
    }

到这里游戏就算简单完成啦,当然后续可以做更多的关卡,只需要修改保存关卡的数组以及关卡管理的进入下一关方法就可以了。

  • 三、总结

写的过程中,注意以下几点:
1.在写游戏成功进入下一关卡和游戏失败重新本关卡时,要注意,失败时的延迟调用时间要小于进入下一关的延时调用时间,如果不注意的话就会产生在最后一个球发射后,即使失败了也会进入下一关卡。

if (toNextLevel)
{
    Invoke("LevelUp", 2);
    toNextLevel = false;
}
 if (other.CompareTag("Ball"))
{
    bigMove.isMove = false; 
    Invoke("ReLoad", 1);
}

2.在进入下一关的时候,做了增加关卡号的操作,但是进入下一关的判断是在Update中执行的,所以有时候会出现跳关,关数不是按顺序出现,这里需要加入一个开关保证进入下一关的操作只执行一次,具体在代码注释中有解释。
3.关卡设计,这里只做了3关,后续增加关卡的难度的话可以通过中心球的旋转速度,方向以及发射球的个数等方式,做完后需要在关卡管理脚本上加上if条件语句做相应的初始化即可。(这个可以用状态设计模式,把if判断逻辑转化成子类关卡完成。)

你可能感兴趣的:(见缝插针)