一个卡牌游戏的DEMO(0)

最近有一个卡牌游戏的创意,打算用Unity把Demo实现出来练练手。我尽量尝试每天记录一下开发的进度。
打算在这里主要记录游戏设计和开发的过程,作为自己的一个摸索的记录。
虽然只有一个人在做,但还是打算用git来做一下版本控制,毕竟回退之类的相当方便
https://github.com/LouisHuangGT/Yggdrasills-dream

这个游戏类似于卡牌游戏和类《神界:原罪》的回合制RPG结合。玩家扮演一个召唤师,可以将随从召唤到战场上(类似炉石和万智牌),而在战场上则遵循回合制战棋游戏的规则。玩家每个回合的资源由Action Point决定。做任何的动作:移动随从,攻击、打出卡牌都会消耗Action Point,因此玩家必须合理的规划自己的Action Point来进行对战。

此外,我们的Action Point不像炉石或万智牌一样每回合增长,而更像战场女武神,每一个玩家召唤的强力角色(例如一个英雄单位)都会提高总的Action Point。为了增加趣味性,还为每个队伍增加一个Magic Point资源,玩家作为召唤师,召唤到战场上的随从是靠法力进行维持的,因此在战场上的每一个单位都会占用一定的Magic Point,玩家需要规划自己召唤出的随从:例如可以召唤一个强力的随从,但这会占用全部的MP;也可以召唤许多脆弱的随从。游戏设计上大致就是这样。一些具体的细节规则我还在进行调整。

可以看到,从实现的角度来看,我们主要要开发两套系统:一个战棋类回合制RPG和一个卡牌系统。两者其实耦合程度很低,完全可以分开来进行实现。我打算先从比较核心的战棋类回合制游戏入手。

在写代码之前我们先分析一遍战棋类回合制RPG的战斗流程:
首先是回合内的流程:

Created with Raphaël 2.1.2玩家回合开始选择一个单位AP是否耗完End弹出菜单,选择这个单位的动作执行动作动作是否执行完成?yesnoyesno

之后是回合间的流程

Created with Raphaël 2.1.2游戏开始玩家回合玩家回合结束?敌方回合敌人回合结束?循环,直到达到胜利条件yesnoyesno

这个流程我们把它拆成两块来实现。首先一个全局的TurnManager负责管理回合的进行:例如回合的切换等,这个类也负责用鼠标选取的方式生成一个controllingUnit,使得玩家可以对其进行操作。即第一张流程图到选取单位这个部分的内容在TurnManager中实现。而对于单个单位的操作可以抽象出来,由一个controller来进行。controller负责接收按钮的回调,确定玩家下的指令,并分配当前这个controller正在操作的unit来执行这个指令。Controller主要通过一个Run函数进行实现,这个函数输入一个选中的单位,然后对这个单位进行一系列的玩家操作。返回操作是否成功/完成。如果操作成功则进入下一个选单位流程,否则不做任何操作,继续这个操作(如流程图1所示)。

玩家的单位目前用一个基类Unit来处理,玩家的具体操作以及一系列的判定就在unit中进行实现。

首先是TurnManager的update()实现:
整个游戏进程的执行相当于全部在这个update中实现。这个update的作用相当于选中单位和对应的操作后等待操作完成,操作完成后选下一个单位,如果没有完成则阻塞等待操作完成(这里的阻塞利用了update每帧循环调用的特性来实现,不需要自己写一个死循环来阻塞)

void Update () {
        if (selectedUnit != null)
        {
            if (controller.run(selectedUnit))
            {
                team[currenTeamIndex].ActionPoint--;
                selectedUnit = selectUnit();
            }
            //选中一个单位,把它放到controller进行操作,并返回一个操作是否完成的布尔值。
            //如果操作完成,那么这个队伍的AP减少1,并选一个新的单位
            //如果操作还没完成,那么不做任何事情,下一帧继续这个操作
        }
        if (team[currenTeamIndex].ActionPoint <= 0)
        {
            team[currenTeamIndex].ActionPoint = team[currenTeamIndex].maxActionPoint;
            currenTeamIndex = 1 - currenTeamIndex;
        }
        //当一个队伍的AP小于等于0时,我们重置这个队伍的AP并将当前的控制权转移到另一支队伍上面(即改变当前索引值)
    }

其中的SelectUnit函数实现比较简单,目前用了一个鼠标raycast的方法来选中对应物体

GameObject selectUnit()
    {
        if (Input.GetMouseButtonUp(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;

            if (Physics.Raycast(ray, out hit))
            {
                if (isIn(hit.transform.gameObject, team[currenTeamIndex].units))
                //判断选中的这个单位是不是在(isIn)当前的队伍列表中,只有选中当前队伍的单位才返回
                    return hit.transform.gameObject;
                else
                    return null;
            }
            else
                return null;
        }
        else
            return null;
    }

有了TurnManager以后,我们就可以开始实现Controller里面的Run函数。这个函数负责给selectedUnit分配一个操作,并执行。

public bool run(GameObject selectedUnit)
    {
        controllerMenu.SetActive(true);
        Unit unit = selectedUnit.GetComponent<Unit>();

        bool result = false;
        switch (currentState)
        {
            case State.IDLE:
                result = false;
                break;
            case State.MOVE:
                result = unit.Move();
                break;
            case State.ATTACK:
                result = unit.Attack();
                break;
            case State.SKILL:
                result = unit.Cast();
                break;
            case State.SUMMON:
                result = unit.Summon();
                break;
            default:
                result = false;
                break;
        }
        if (result == true)
        {
            currentState = State.IDLE;
            controllerMenu.SetActive(false);
        }
        return result;
    }

目前暂定有四种操作:移动、攻击、使用技能(一些随从有主动技能)和召唤(即使用卡牌)。在unit类中进行具体实现。
其中currentState由四个按钮的回调函数来设置对应的值,例如按下Move按钮对应的值就是State.MOVE。最后这个函数需要返回一个执行的结果,如果进行了操作,那么返回操作的结果,如果没有按下任何按钮(没有输入,即IDLE状态),则返回false。

最后,在Unit类中,实现了一个移动的功能

 public bool Move()
    {
        if (Input.GetMouseButtonUp(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;

            if (Physics.Raycast(ray, out hit))
            {
                agent.SetDestination(hit.point);
            }
        }
        if (agent.remainingDistance < agent.radius && agent.hasPath)
            return true;
        else
            return false;
    }

一个比较简单的点击地面移动的实现。每次执行的时候检测是否有鼠标按键抬起,如果有则射一道光束到地面,并用NavMeshAgent将当前单位移动过去。设置完目的地后判断一下剩余距离是否小于agent的半径,如果是的说明到达目的地,返回true说明操作结束,可以进行下一个单位的选择和操作;如果没有,说明操作没有结束,返回false,下次update继续这个操作。
这边的实现其实会有一点bug,如果选择move以后在到达目的地前不停点击鼠标会使单位不停的重新定向,可以考虑在input里面加一个flag使得在到达目的地前只能接受一次鼠标的输入。另外在移动过程中我个人倾向于还是需要把菜单暂时关闭,这个需要调整一下controller中的实现。
代码写完以后场景就比较好处理了。我做了一个贼简单的场景,其中蓝色方块是己方单位,红色方块是敌方单位。选中效果还没有加上,但目前已经可以接近回合制式的移动各个单位了。
这里写图片描述
今天基本上把回合制的框架搭了个七七八八,下面可以往里面填unit中的几个操作的实现了。下面一个比较麻烦是一套RPG系统,例如实现血量、攻击等等机制。另外ActionPoint这个机制也要做更复杂的实现,每一个技能、每一个动作都应该有一个自己消耗的ActionPoint。最后,其实这样去实现一个回合制的流程动作应该还有不少bug,还要等后面慢慢进行重构。

发布了5 篇原创文章 · 获赞 2 · 访问量 1万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览