Unity3D教程:回合制游戏实现
发表于2018-04-07
回合制游戏也是玩家非常喜爱的一个品类,那么在Unity3D中怎么实现呢?下面用一个比较简单Unity3D的一对一回合制游戏来说明这个问题,实现过程当中最重要的就是理清各个处理关系。
如下图所示,绿色代表玩家操控的主角,蓝色代表遇到的敌人,分别赋予大家100HP,然后玩家打敌方一下,敌方就-40HP,玩家被敌方摸一下就-30HP。下面是玩家成功战胜敌人的情况。
当然,玩家也可以防御的,此时敌方摸玩家一下仅15HP。下图是展示玩家HP变成0,游戏失败的情况。
当然,这个例子一点不好玩,毕竟又没有药品,招式只有1个,还是1对1的对打。甚至连MP都没有。也没有根据速度计算谁先出手的问题,还有Buff与Debuff之类的。不过,为了说明在Unity3D如何制作回合制游戏。我尽可能将一些能简化的东西先简化,主要突出回合制游戏的制作核心。
一、场景布置
首先是简单的场景布置,在3D部分很简单。就几个简单的基本组件,在一个Plane上面放2个Cube,并且上不同颜色的纯色Material。唯一需要大家注意的是,请将两个Cube改好名,以免到时候编程不知道哪个跟哪个。
其次是UGUI的布置。左下角是一个名为ActionPanel的Panel,旗下有两个按钮Attack Button和Defend Button,一会儿ActionPanel将被控制,而按钮Attack Button和Defend Button则将赋予点击事件。在这个ActionPanel的下方则是一个名为PlayerHPinfo的Text,同样会被脚本控制,用于显示血量等信息。
至于右上角是个动态文本的滚动区域WarinfoPanel,里面放置的一个WarinfoText用于显示战斗信息的文本,这里需要注意上Mask组件的时候去除Show Mask Graphic,不然WarinfoText显示不出来。而在其下方,则是一个退出战斗的按钮ExitButton,当然这个东西,在实际游戏里面完全可以不要,自动切换回战斗前的场景。
并同时新建一个空物体WarControl,赋予脚本WarControl.cs。
以下是各个对象的从属关系,请注意改好名字。因为基本上上面提到的组件,都将被WarControl.cs控制。
二、脚本编写
WarControl.cs设置的变量,并且要控制的物体如下所示:
这段代码的思想如下:
由于Update()在每一帧的刷新都被执行的,在1秒就30帧的瞬间,Update()里面的代码不读完,这游戏就被卡死,所以Update()这个可视为主线程的函数,只承担以下简单任务,时刻在判断HP是否见底。
而攻击表演这些要交代给玩家看的东西,至少要占用1s的技能表演,我们则通过协程Coroutine完成。协程,其实也就是Unity3D的子线程,将通过按钮点击时间来创建。各个按钮点击之后,具体的思想如下图表示,其中实线表示玩家点击了“攻击按钮”,虚线则表示玩家点击了“防御按钮”。上例子的动画,我采用了Unity3D中极其简单的动画组件iTween来做。
这里涉及到挂起0.5s~0.9s的东西,因此,只能写在协程里面完成的,不可能写在Update()里面,不然这游戏绝对卡死。
因此,WarControl.cs如下,赋予给空物体WarControl。
using UnityEngine; using UnityEngine.UI; using System.Collections; public class WarControl : MonoBehaviour { public GameObject Player;//代表玩家的绿色立方体 private int Player_HP;//玩家的HP public GameObject Enemy;//代表玩家的蓝色立方体 private int Enemy_HP;//敌人的HP public GameObject ActionPlane;//左下角玩家操作面板,旗下有两个按钮 public GameObject PlayerHPinfo;//左下角的玩家的HP信息文本Text public GameObject WarinfoText;//右上角的战斗信息文本Text public GameObject ExitButton;//退出按钮 private int Player_Max_HP;//玩家最大血量,这个其实可以视为一个常量const /*场景初始化过程,数据初始化过程我也写在这里了*/ void Start() { Time.timeScale = 1;//打破时间结界,主要是配合下面update()中结算时的布置的时间结界Time.timeScale = 0;玩家点击“退出”重新进入场景 /*定义玩家和敌人的血量和玩家的最大血量,这部分在实际中,可以从记载游戏状态的xml等地方取,这里粗暴定义为100*/ Player_HP = 100; Enemy_HP = 100; Player_Max_HP = Player_HP; /*更新UI*/ PlayerHPinfo.GetComponent<Text>().text = "HP:" + Player_HP + "/" + Player_Max_HP;//玩家HP信息文本的更新 WarinfoText.GetComponent<Text>().text = "战斗开始!\n";//战斗信息更新 ExitButton.SetActive(false);//隐藏“退出战斗”这个按钮 } /*主线程,时刻在读取,这段由于大量代码是相同的,因此还可以优化下这个条件结构的写作*/ void Update() { if (Player_HP < 0) { PlayerHPinfo.GetComponent<Text>().text = "HP:" + Player_HP + "/" + Player_Max_HP;//由于战斗结算在下面的子线程完成,在最终的战斗结算需要再次更新UI,以免有显示BUG Time.timeScale = 0;//布置一个时间结界 ExitButton.SetActive(true);//打开“退出游戏”按钮 Player.SetActive(false);//将代表玩家这个立方体消失,实际上还可以播放下玩家死亡动画什么的 ActionPlane.SetActive(false);//关闭操作UI /*更新战斗信息*/ WarinfoText.GetComponent<Text>().fontSize = 30; WarinfoText.GetComponent<Text>().text = "玩家死亡!战斗失败!\n"; } if (Enemy_HP < 0)//同上,不赘述了 { PlayerHPinfo.GetComponent<Text>().text = "HP:" + Player_HP + "/" + Player_Max_HP; Time.timeScale = 0; ExitButton.SetActive(true); Enemy.SetActive(false); ActionPlane.SetActive(false); WarinfoText.GetComponent<Text>().fontSize = 30; WarinfoText.GetComponent<Text>().text = "敌人死亡!胜利战斗!\n"; } } /*按钮点击事件*/ public void AttackButtonOnclick() { StartCoroutine(Attack()); } public void DefendButtonOnclick() { StartCoroutine(Defend()); } public void ExitButtonOnclick() { Application.LoadLevel("Turnbase_Single"); } /*攻击协程*/ IEnumerator Attack() { ActionPlane.SetActive(false);//先关闭操作UI StartCoroutine(Player_Attack());//新建一条玩家攻击协程 yield return new WaitForSeconds(0.9f);//等待0.9s再读下面的代码,也就是等待玩家攻击技能表演完,一共0.9s yield return new WaitForSeconds(0.5f);//再等待0.5s,让玩家喘口气,表示上述动作交代完了,开始交代下述敌人攻击的技能 StartCoroutine(Enemy_Attack(false));//再新建一条敌人攻击的协程,这里的false代表玩家没有防御 yield return new WaitForSeconds(0.9f);//等待0.9s再读下面的代码,也就是等待敌人攻击技能表演完,一共0.9s PlayerHPinfo.GetComponent<Text>().text = "HP:" + Player_HP + "/" + Player_Max_HP;//更新UI ActionPlane.SetActive(true);//再打开操作UI,让玩家进行下一个回合的指令 yield return null; } /*防御协程*/ IEnumerator Defend() { ActionPlane.SetActive(false);//先关闭操作UI StartCoroutine(Enemy_Attack(true));//新建一条敌人攻击的协程,这里的true代表玩家没有防御 yield return new WaitForSeconds(0.9f);//等待0.9s再读下面的代码,也就是等待敌人攻击技能表演完,一共0.9s PlayerHPinfo.GetComponent<Text>().text = "HP:" + Player_HP + "/" + Player_Max_HP;//更新UI ActionPlane.SetActive(true);//再打开操作UI,让玩家进行下一个回合的指令 yield return null; } /*玩家攻击技能的表演,用iTween实现*/ IEnumerator Player_Attack() { iTween.MoveTo(Player, iTween.Hash("position", new Vector3(0, 0.5f, 2), "easeType", "easeInCubic", "time", 0.3f, "loolType", "none")); yield return new WaitForSeconds(0.3f); iTween.RotateTo(Player, iTween.Hash("rotation", new Vector3(0, 180, 0), "easeType", "easeInCubic", "time", 0.3f, "loolType", "none")); iTween.RotateTo(Enemy, iTween.Hash("rotation", new Vector3(30, 0, 0), "easeType", "easeInCubic", "time", 0.3f, "loolType", "none")); yield return new WaitForSeconds(0.3f); iTween.MoveTo(Player, iTween.Hash("position", new Vector3(0, 0.5f, -4), "easeType", "easeInCubic", "time", 0.3f, "loolType", "none")); iTween.RotateTo(Enemy, iTween.Hash("rotation", new Vector3(0, 0, 0), "easeType", "easeInCubic", "time", 0.3f, "loolType", "none")); iTween.RotateTo(Player, iTween.Hash("rotation", new Vector3(0, 0, 0), "easeType", "easeInCubic", "time", 0f, "loolType", "none")); yield return new WaitForSeconds(0.3f); /*攻击结算*/ WarinfoText.GetComponent<Text>().text += "玩家攻击,敌人-40HP\n"; this.Enemy_HP -= 40; } /*敌人攻击技能的表演,用iTween实现*/ IEnumerator Enemy_Attack(bool isPlayerDefend) { iTween.RotateTo(Enemy, iTween.Hash("rotation", new Vector3(0, 180, 0), "easeType", "easeInCubic", "time", 0.3f, "loolType", "none")); iTween.RotateTo(Player, iTween.Hash("rotation", new Vector3(-30, 0, 0), "easeType", "easeInCubic", "time", 0.3f, "loolType", "none")); yield return new WaitForSeconds(0.3f); iTween.RotateTo(Enemy, iTween.Hash("rotation", new Vector3(0, 0, 0), "easeType", "easeInCubic", "time", 0f, "loolType", "none")); iTween.RotateTo(Player, iTween.Hash("rotation", new Vector3(0, 0, 0), "easeType", "easeInCubic", "time", 0.3f, "loolType", "none")); /*攻击结算*/ if (!isPlayerDefend) { WarinfoText.GetComponent<Text>().text += "敌人攻击,玩家-30HP\n"; this.Player_HP -= 30; } else { WarinfoText.GetComponent<Text>().text += "敌人攻击,玩家-15HP\n"; this.Player_HP -= 15; } yield return null; } }
这里的攻击动画,用到了iTween实现,具体可以参考《【iTween】利用协程完成多个动作、iTween的动作序列》。同时,这里的玩家死亡和敌人死亡其实也可以加入一个立方体碎裂的动画,让游戏更加生动。
同时赋予Attack_Button、Defend_Button和ExitButton,三个按钮点击事件分别为WarControl.cs的AttackButtonOnclick()、DefendButtonOnclick()和ExitButtonOnclick()则大功告成!
来自:https://blog.csdn.net/yongh701/article/details/77341926