您的位置:首页 > 移动开发 > Unity3D

[Unity3D]Unity3D游戏开发之回合制游戏原型的实现

2014-06-02 21:14 615 查看
大家好,欢迎大家关注我的博客,我是秦元培,我的博客地址是blog.csdn.net/qinyuanpei
今天呢,我们来说说回合制。博主曾经坦言自己是一个喜欢国产RPG游戏的人,《仙剑奇侠传》、《轩辕剑》、《古剑奇谭》等游戏都为博主带来了许多温暖的回忆。那么什么是回合制呢?让我们将视线转移到三足鼎立的三国时代,只见张飞挺着丈八蛇矛,大喝一声:三姓家奴休走,与我大战三百回合。布大怒,举起画戟,便要刺来。这是我们记忆中最原始的关于回合印象,可见这里的回合是指对战双方武将骑着马对冲,人随着马的惯性对打,如果双方都没有死,就调转马头,再来一次。这便是最初的回合的概念。而追朔到欧洲的燧石枪时代,对战双方则是按照各自阵营站成整齐的一排,听口令按次序互相击毙。当你打完后,就必须等待我开枪,这才是回合制的精髓,在《刺客信条》等游戏中我们可以一窥当年欧洲战场之宏伟壮观。同样地,当两个人要决斗时,就是拿着手枪挨个打,直到有人倒下为止,这就是回合制,伟大的诗人普希金就是这样死的。可见回合制是体现人文精神的、尊重规则、尊重原则的一种历史产物。而在我们的生活中,篮球、扑克、象棋、麻将都是回合制的,如果没有回合制,就将没有规则和秩序。真正的侠客不在于武,而在于适可而止,我们不是在战场上胡乱厮杀,而是而是冷静地停下来,安静地思考策略,谋划下一回合的进攻和防守。而这就是回合制,如果说回合制代表了人类最原始的一种文化形态,那么这种形态可能是一种落后的形态,因为最初的游戏受到硬件水平的影响,回合制是一种最为折中的办法。从这个角度上来看,回合制非但不落后,而且有点高贵、古典的气息流淌在里面,这就是为什么国产单机游戏都喜欢使用回合制这一战斗模式的原因所在,我们不羡慕国外游戏大作的酣畅淋漓、我们不羡慕国外游戏大作的奢华精致,我们只想找回属于我们的最初的归属。那是一种流淌了五千年的血液,那是一段述说了五千年的故事,那是一个执着了五千年的梦想,因为如此,所以我们懂得了什么叫做热爱,什么叫做温暖,什么叫做责任,什么叫做感动。好了,今天的游戏我们就先聊到这里啦,下面我们开始今天的内容。





在文章开始,我们已将回合制的战斗模式讲解得很清楚了。那么,如果在Unity3D游戏中实现一个回合制游戏呢?我们从最简单的一对一模式来设计回合制游戏的原型。我们可以游戏的状态划分为下面三种状态:
1、我方角色生命值为0,则游戏结束,角色输。
2、敌方角色生命值为0,则游戏结束,角色赢。
3、如果双方角色生命值均不为0,则反复执行下面的过程:
当当前操作状态为AI时,敌人根据AI算法逻辑行动
当当前操作状态为玩家时,根据玩家操作执行行动

这就是我们今天实现回合制游戏的算法原型。我们下面将根据这一原型来实现一个回合制游戏的示例。首先我们创建一个简单的游戏场景,如图所示:



在这个游戏场景中,我们的Samuzai 将作为我们的游戏主角,而Gorilla将作为我们的敌人。我们分别为它们创建对应的脚本文件Player.cs、EnemyAI.cs以及用于全局控制的TurnGame.cs。Player.cs脚本负责玩家相关逻辑的实现,EnemyAI脚本负责敌人AI的相关逻辑,TurnGame脚本负责回合制游戏的核心部分。好了。我们下面来一起来看这三部分的脚本代码:
首先在Player脚本中,我们需要做三件事情:(1)显示和隐藏玩家的操作界面、(2)玩家攻击招式的设定、(3)玩家生命值的设计。我们来一起看代码:

using UnityEngine;
using System.Collections;

public class Player : MonoBehaviour {

	//定义玩家最大生命值为100
	public int HP=100;
	//是否等待玩家输入
	public bool isWaitPlayer=true;

	//当前回合数
	private int index=1;
	//动画组件
	private Animation mAnimation;

	void Start () 
	{
		mAnimation=GetComponent<Animation>();
	}

	//受到伤害
	void OnDamage(int mValue)
	{
		HP-=mValue;
	}

	void OnGUI()
	{
	  //如果处于等待玩家输入状态,则显示操作窗口
	  if(isWaitPlayer)
	  {
		GUI.Window(0,new Rect(Screen.width/2+150,Screen.height/2-150,200,200),InitWindow,"请选择技能或仙术");
	  }
	}

	void InitWindow(int ID)
	{

		if(GUI.Button(new Rect(0,20,200,30),"御剑术"))
		{
			mAnimation.Play("Attack");
			//将操作权交给敌人
			isWaitPlayer=false;
			Debug.Log("在第"+index+"回合:主角使用了御剑术");
			index+=1;

		}
		if(GUI.Button(new Rect(0,50,200,30),"万剑诀"))
		{
			mAnimation.Play("Attack");
			//将操作权交给敌人
			isWaitPlayer=false;
			Debug.Log("在第"+index+"回合:主角使用了万剑诀");
			index+=1;
		}
		if(GUI.Button(new Rect(0,80,200,30),"仙风云体"))
		{
			mAnimation.Play("Attack");
			//将操作权交给敌人
			isWaitPlayer=false;
			Debug.Log("在第"+index+"回合:主角使用了仙风云体");
			index+=1;
		}
		if(GUI.Button(new Rect(0,110,200,30),"化相真如"))
		{
			mAnimation.Play("Attack");
			//将操作权交给敌人
			isWaitPlayer=false;
			Debug.Log("在第"+index+"回合:主角使用了化相真如");
			index+=1;
		}
		if(GUI.Button(new Rect(0,140,200,30),"酒神"))
		{
			mAnimation.Play("Attack");
			//将操作权交给敌人
			isWaitPlayer=false;
			Debug.Log("在第"+index+"回合:主角使用了酒神");
			index+=1;
		}
	}
}
在该脚本中,我们设定角色的最大生命值为100,然后通过一个bool类型的isWaitPlayer来判断是否处于等待玩家执行下一步行动的状态,如果处于改状态则显示操作界面,这样玩家就可以施展不同的技能,这里我们使用了系统的GUI系统。最终实现的效果是类似于仙剑奇侠传游戏的效果,如图:



接下里我们来讲解敌人AI的脚本,敌人需要在玩家执行结束后随机进行一个操作,所以这里需要用到概率,我们一起来看脚本:

using UnityEngine;
using System.Collections;

public class EnemyAI : MonoBehaviour {

	//定义敌人最大生命值为100
	public int HP=100;
	public bool isWaitPlayer=true;
	//当前回合数
	private int index=1;
	//动画组件
	private Animation mAnimation;

	void Start()
	{
		mAnimation=GetComponentInChildren<Animation>();
	}

    void OnDamage(int mValue)
	{
		HP-=mValue;
	}

	/// <summary>
	/// 执行敌人的AI算法
	/// </summary>
	public void StartAI()
	{
		if(!isWaitPlayer)
		{
		   if(HP>20)
		   {
			  //80%的攻击招式一
			  if(Random.Range(1,5)%5!=1)
			  {
				  Debug.Log("在第"+index+"回合:敌人使用了攻击招式一");
				  mAnimation.Play("Howl");
				  //在这里加上特效和伤害
				  index+=1;
				  isWaitPlayer=true;
			  }
			  //20%的攻击招式二
			  else
			  {
				  Debug.Log("在第"+index+"回合:敌人使用了攻击招式二");
				  mAnimation.Play("Howl");
				  index+=1;
				  isWaitPlayer=true;
			  }
		  }else
		  {
			switch(Random.Range(1,5)%5)
			{
			    case 0:
				Debug.Log("在第"+index+"回合:敌人使用了攻击招式三");
				mAnimation.Play("Howl");
				index+=1;
			    isWaitPlayer=true;
				  break;
			    case 1:
				Debug.Log("在第"+index+"回合:敌人使用了攻击招式四");
				mAnimation.Play("Howl");
				index+=1;
				isWaitPlayer=true;
				  break;
			    case 2:
				Debug.Log("在第"+index+"回合:敌人使用了攻击招式五");
				mAnimation.Play("Howl");
				index+=1;
			    isWaitPlayer=true;
				  break;
			}
		  }
	   }
	}
}
类似地,我们在这里使用一个bool类型的变量isWaitPlayer来表示敌人是否处于等待玩家执行操作的状态,如果该值为false,则表明玩家已经执行完操作,此时敌人应该按照AI算法来实现随机的攻击,其中概率部分的代码如下:

Random.Range(1,5)%5!=1
这一句代码表示80%的概率,因为只有Random.Range(1,5)返回值为5时结果才会为1。如果我们以后希望在游戏中为敌人增加概率,我们都可以使用这种方法。游戏中使用概率的地方还是比较多的,比如在仙剑奇侠传游戏中的逃跑率、暴击率、避让率都是通过这种方式来实现的。大家可能注意到了我在这两个脚本中所有技能或者招式都是使用了一个动画,这当然是为了简化程序,让我们专注于游戏的核心实现,这一点希望大家谅解啊,而在两个脚本中的回合数index主要是为了调试程序的方便,具体应用中可以不用这个变量。好了,在介绍完玩家和敌人的脚本后,我们一起来看今天的核心脚本——TurnGame脚本:

using UnityEngine;
using System.Collections;

public class TurnGame : MonoBehaviour {

	/// <summary>
	/// 回合制游戏战斗模式原型
	/// 说明:本程序以最简单一对一回合制游戏为例,基于Unity3D游戏实现回合制游戏算法
	/// 如果需要实现多人对多人的回合制游戏算法,需要设计行动条算法
	/// 基本的思路是将游戏状态划分为三种状态:
	/// 1、我方角色生命值为0,游戏结束,玩家输
	/// 2、敌方角色生命值为0,游戏结束,玩家赢
	/// 3、双方生命值均不为0,则循环执行下列过程:
	/// 当当前操作状态为AI时,敌人根据AI算法逻辑行动
	/// 当当前操作状态为玩家时,根据玩家操作执行行动
	/// </summary>

	//定义玩家及敌人
	public Transform mPlayer;
	public Transform mEnemy;

	//定义玩家及敌人脚本类
	private Player playerScript;
	private EnemyAI enemyScript;

	//默认操作状态为玩家操作
	private OperatorState mState=OperatorState.Player;

	//定义操作状态枚举
	public enum OperatorState
	{
		Quit,//游戏结束
		EnemyAI,//AI逻辑
		Player//玩家逻辑
	}
	
	void Start () 
	{
		//获取玩家及敌人脚本类
		playerScript=mPlayer.GetComponent<Player>();
		enemyScript=mEnemy.GetComponent<EnemyAI>();
	}

	//延迟等待显示操作界面
	IEnumerator WaitUI()
	{
		yield return new WaitForSeconds(1);
		enemyScript.isWaitPlayer=true;
	}

	//延迟等待
	IEnumerator WaitAI()
	{
		yield return new WaitForSeconds(2.5F);
		enemyScript.isWaitPlayer=false;
	}

	//为AI设计延迟时间,使其在我方行动结束后2.5F秒的时间内发起攻击
	IEnumerator UpdateLater()
	{
		yield return new WaitForSeconds(2.5F);
		//敌人停止等待
		enemyScript.isWaitPlayer=false;
		//敌人执行AI
		enemyScript.StartAI();
	}

	void Update () 
	{
	   //如果敌我双方有一方生命值为0,则游戏结束
	   if(playerScript.HP==0)
	   {
			mState=OperatorState.Quit;
			Debug.Log("玩家输");
		}else if(enemyScript.HP==0)
		{
			mState=OperatorState.Quit;
			Debug.Log("玩家赢");
		}else
		{
		    switch(mState)
			{
			  case OperatorState.Player:
				//如果玩家操作结束,则立即隐藏操作界面,等待2秒钟后敌人AI操作开始
				if(!playerScript.isWaitPlayer)
				{
					//让敌人等待2秒钟再发起攻击
					StartCoroutine("UpdateLater");
					//执行完后等待玩家,5秒钟后显示操作界面,玩家可以继续操作
					StartCoroutine("WaitUI");
					mState=OperatorState.EnemyAI;
				}
				break;
			  case OperatorState.EnemyAI:
				//如果敌人AI操作结束,则玩家开始操作
			    if(enemyScript.isWaitPlayer)
				{
				    //玩家操作
					playerScript.isWaitPlayer=true;
					StartCoroutine("WaitUI");
					//操作完后执行AI
					StartCoroutine("WaitAI");
					mState=OperatorState.Player;
				}
				break;
			}
		}
	}
}
在这段脚本中,我们按照一开始在文章开头设计的算法原型,定义状态枚举OperatorState,然后根据不同的状态去执行不同的逻辑。这块的代码是博主调试了很多次才调试出来的,所以这一部分的代码可能会有点乱。前面两种状态的判定比较简单,只要在逻辑中添加相应的逻辑,比如显示游戏胜利、游戏失败等。而对于第三种状态,我们将其分为两种状态,并在这两种状态中通过改变状态值来实现状态的切换。基本的思路是:
1、轮到玩家时,如果玩家执行操作已经结束,则隐藏操作界面,延迟2秒后敌人将发起攻击,当敌人执行完攻击后5秒再次显示操作界面,为玩家的下一轮操作做好准备。
2、轮到敌人时,如果敌人AI操作已经结束,则轮到玩家操作,操作结束后将操作权交给敌人。
这段程序中博主用到了协程,不过博主感觉这里协程用得不是很好,如果大家有什么好的建议,欢迎大家给我留言,作为一个有节操的程序员,接受别人的建议比闭门造车更重要。好了,我们来看看最终程序运行的效果吧:



当然这里博主没有给脚本添加伤害部分的代码,所以到目前为止,敌我双方走没有对对方造成实际的伤害,呵呵,这个大家可以通过调用OnDamage()方法来实现,这里我们就不再说了。由于今天的截图文件比较大,所以给大家发不了GIF演示,这里给出下载地址,感兴趣的同学可以自己去下载
注意:由于在博客设计这个算法的时候存在问题,这个回合制游戏在第一次攻击的时候有Bug,玩家攻击结束后操作界面不会隐藏

参考文章:【Visual C++】游戏开发笔记十六 讲解一个完整的回合制游戏demo
学习游戏开发推荐浅墨的博客:http://blog.csdn.net/poem_qianmo

每日箴言:不要去等谁,所有的不期而遇都在路上。



好了,今天的博客就是这样了,博主去写作业了。喜欢我的博客请记住我的名字:秦元培,我博客地址是blog.csdn.net/qinyuanpei
转载请注明出处,本文作者:秦元培,本文出处:http://blog.csdn.net/qinyuanpei/article/details/28125171
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: