毕业时制作的游戏demo
毕业时制作的游戏demo
利用Unity制作的毕业设计
游戏设计整体构思
游戏主体
首先游戏需要一个开场的渐变动画,之后再显示游戏开始选项界面,界面上面得有开始游戏,继续游戏和退出游戏这些基本按键。第一次进入游戏要加载主角前往岛屿的过场动画,过场动画播放完成之后主角正式登上岛屿。游戏采用上帝视角,角色可以前后左右移动,点击鼠标右键可以进行镜头的移动。游戏界面上方需要有道具卡,道具卡的右下方得显示道具卡的剩余数量。当鼠标移动到道具卡上面时显示道具信息,鼠标移开后道具信息隐藏。在岛屿上面的每个守护者都要有一个巡逻路线,守护者都按照指定的巡逻路线行走,守护者们带有触发器,当主角进入守护者的触发器范围,守护者就是奔跑到主角身边,之后触发对话,在对话结束之后便进入挑战。
追逐挑战
进入挑战时利用对话框介绍游戏的玩法,之后便开始倒计时,倒计时解释后游戏开始。游戏玩法为在规定时间内,保持主角永远跑在守护者前面,守护者会不定时的加速。在游戏画面的中间有个按键提示图,玩家要准确的按出按键提示图中的按钮主角才会加速,反之如果按错了,主角就会减速。在追逐的过程中,人物会随机的进行对话。当主角的速度到达规定值的时候,主角就会骑上摩托车。
炫舞挑战
进入挑战后,游戏镜头要从远处移动到人物所在的位置,最终聚焦于人物身上。之后也是通过对话介绍玩法,倒计时后正式开始挑战。游戏界面的下方分批显示按键提示,玩家按对按键就会加一分。每批按键都有限制时间,过了限制时间就会切换到下一批按键,玩家要在规定时间内按出对应的按键。根据玩家在规定时间内按对的按键数,显示完美,一般般或者差等提示。当玩家在规定时间内全部按对按键主角就会成为C位,反之主角退出C位。玩家的最终得分要超过总分的百分之六十才能算是挑战成功。
问答挑战
进入挑战后同样是通过对话介绍游戏玩法。该游戏玩法就是问答,玩家要准确的回答出问题的答案,遇到不会的问题,玩家可以消耗道具券来查看答案,但是道具券的数量是有限的,每用一次就消耗一个道具卡,当道具卡使用完了就不能查看答案了。玩家要回答出所有的题目才算是完成挑战。
挑战结束
当挑战结束时要判断是否挑战成功,挑战成功后就弹出胜利的弹窗,反之弹出失败的图标。挑战结束后可以消耗道具卡重新挑战一次。挑战成功后主角可以获得该挑战的祝福,挑战失败就没有祝福。
游戏详细设计
logo的淡出淡入
利用Animation给图片创建一个动画,动画内容就是修改其透明度让其在1秒内从0到1再到0,这就出现渐变效果了,最后在动画播放结束时触发事件触发器,展示游戏开始界面
关键代码:
public void LoadOtherScene()
{
this.gameObject.SetActive(false);//图标隐藏
canvas.gameObject.SetActive(true);//开始界面显示
}
游戏开始界面是一个Panel面板,选择了一个图片作为Panel面板的背景,之后为面板添加3个按钮作为它的孩子对象,这3个按钮分别是开始游戏、继续游戏和退出游戏按钮。这3个按钮也相应的换上漂亮的背景。之后为3个按钮绑定点击事件。
实现效果:
按钮点击事件关键代码:
/// <summary>
/// 开始游戏
/// </summary>
private void GameStart()
{
panel.SetActive(false);
darkPanel.SetActive(true);
flowchart.ExecuteBlock("New Block");//执行对话,利用了fungus插件
}
/// <summary>
/// 继续游戏
/// </summary>
private void GameContinue()
{
SceneManager.LoadScene("QAForest");//直接进入游戏主画面
}
/// <summary>
/// 退出游戏
/// </summary>
private void QuitGame()
{
#if UNITY_EDITOR//编辑模式
UnityEditor.EditorApplication.isPlaying = false;
#else//游戏运行模式
Application.Quit();
#endif
}
游戏环境设计
游戏资源都是从网上找的,游戏的主环境是一个岛屿,首先创建一个地形Terrain,为地形设置绿色的草地贴图,然后设置地形中间凸起,使其像个岛屿。最后将下载的树木和建筑模型布置到地形上面。岛屿旁边还得有水,由于不会Sharder编程,只能去网上寻找现成的水资源,然后将它布置在岛屿旁边,最终整个岛屿就完成了。
游戏主界面UI设置
道具卡UI
游戏首次进入的时候界面的左上角有两个道具卡,分别是再来一次和洞察,再来一次道具卡的作用是再次挑战一次,洞察道具卡的作用是查看问题答案。这两个道具卡分别有自己的图标。实际上,他们就是两个image,在每个图片的右下角添加文字控件Text显示该道具剩余的数量。之后再创建一个文本框,用来显示道具说明。为这些道具图片添加触摸事件,当鼠标移动上去的时候显示道具说明,鼠标移开则隐藏道具说明文本框。
最终效果图:
关键代码:
/// <summary>
/// 为图片添加鼠标事件
/// </summary>
private void ImageAddPointEvent()
{
for(int i=0;i<images.Length;i++)
{
EventTrigger eventTrigger = images[i].GetComponent<EventTrigger>();//获取图片的EventTrigger控件
int k = i;
UnityAction<BaseEventData> callback = new UnityAction<BaseEventData>(data=>PointEnterImage(k,data));//创建一个事件
EventTrigger.Entry entry = new EventTrigger.Entry
{
eventID = EventTriggerType.PointerEnter//指定事件类型为鼠标移入事件
};//创建Entry,主要为了添加事件并指定事件类型
entry.callback.AddListener(callback);//添加触发函数
eventTrigger.triggers.Add(entry);//将Entry加入EventTrigger
entry = new EventTrigger.Entry
{
eventID = EventTriggerType.PointerExit//指定事件类型为鼠标移入事件
};
entry.callback.AddListener(new UnityAction<BaseEventData>(data=>tip.gameObject.SetActive(false)));//添加触发函数
eventTrigger.triggers.Add(entry);//将Entry加入EventTrigger
}
}
游戏设置界面UI
在游戏当中,按下esc键就会弹出设置窗体并且暂停游戏。设置窗体中分为两个分页,一个是操作界面,一个是祝福界面。其中操作界面中可以执行游戏重新开始、返回游戏、退出游戏、调节游戏音量等操作。而祝福界面主要是显示目前获得的祝福情况。
关键代码:
/// <summary>
/// 音量调节,当slider change value时触发
/// </summary>
private void ChangeVolumn()
{
CameraFollow.instance.ChangeVolume(volumnSlider.value);
}
/// <summary>
/// 展示操作界面,利用按钮实现tab
/// </summary>
private void ShowControlPanel()
{
controlButton.image.sprite = clickSprite;
blessButton.image.sprite = unClickSprite;
controlPanel.SetActive(true);
blessPanel.SetActive(false);
}
/// <summary>
/// 展示祝福界面
/// </summary>
private void ShowBlessPanel()
{
controlButton.image.sprite = unClickSprite;
blessButton.image.sprite = clickSprite;
controlPanel.SetActive(false);
blessPanel.SetActive(true);
}
/// <summary>
/// 初始化祝福
/// </summary>
public void InitBlessTextShow()
{
if (QAForestConfig.blessing.Equals(""))
return;
//获取配置文件的祝福数据
string[] strs = QAForestConfig.blessing.Split(',');
for(int i=0;i<strs.Length;i++)
{
for(int j=0;j<blessTexts.Length;j++)
{
if(strs[i].Equals(blessTexts[j].text))
{
//获取所有孩子的image组件
cupImgs =Util.GetObject<Image>(blessTexts[j].transform);
//修改图片资源,实现高亮效果
cupImgs[0].sprite = sprite;
cupImgs[0].color = new Color(1,239f/255f,0,93f/255f);
cupImgs[1].gameObject.SetActive(true);
}
}
}
}
游戏对话设计
首先前往Unity商城下载Fungus插件,下载完后点击import将其导入Unity3D引擎。导入成功后在Tools面板下面就可以看到Fungus,之后点击Create->FlowChart就是可以创建一个对话框了。之后我们打开FlowChart窗体就可以看见一个对话面板了,默认名字为New Block。然后我们可以利用这个Block编辑对话内容。
对话控件Block:
点击NewBlock,在Inspector面板就可以看到它拥有的组件了。其中Commands就是用来编辑对话操作的。点击下方的加号就可以添加内容了,其中Say代表添加对话内容,If和Else代表条件判断,SetVariable代表参数设置,InvokeEvent代表执行指定函数,LoadScene代表加载场景。
Block控件的Inspector面板:
人物以及敌人控制
主角移动与视野转换
在游戏当中,按下WASD这几个按键主角就会向前、向左、向后、向右移动。在主角移动的时候,摄像机要跟随主角以保证主角永远在视野范围内,这个就是视野的跟随。当点击鼠标右键并且拖动鼠标就可以实现摄像机围绕着主角旋转,这样可以让玩家选择一个适合自己的视野。当按住shift键时,主角就会切换为奔跑状态,移动速度也会相应的增加,放开shift键主角又会切换回行走状态,速度也会随之降低。
摄像机围绕主角旋转关键代码:
private void Update()
{
if(Input.GetMouseButton(1))
{
rotateAngle = Input.GetAxis("Mouse X");//获取鼠标在x轴滑动的距离
}
CameraRoate();
}
private void LateUpdate()
{
this.transform.position = hero.transform.position - posi;//摄像机跟随
}
/// <summary>
/// 摄像机旋转函数
/// </summary>
private void CameraRoate()
{
if(rotateAngle!=0 && HeroMove.instance.canWalk)//当角色可以移动的时候才能旋转屏幕
{
this.transform.RotateAround(hero.position, Vector3.up, rotateAngle);//表示绕着主角的y轴旋转
hero.transform.Rotate(0,rotateAngle,0);//让主角也跟着旋转
posi = hero.position - this.transform.position;//重新计算摄像机与主角的距离
rotateAngle = 0;
}
}
主角移动关键代码:
public void Move()
{
Run();
hNum = Input.GetAxis("Horizontal");//左右
vNum = Input.GetAxis("Vertical");//前后
eulerAnglesY = Camera.main.transform.eulerAngles.y;//获取摄像机的y轴的旋转角度
//hNum和vNum不为0 ,说明按了方向键
if (canWalk && (Mathf.Abs(hNum) > 0 || Mathf.Abs(vNum) > 0))
{
UpdateLookAtPosi();
this.transform.LookAt(lookAtPosi);//调整主角的朝向
if (Mathf.Abs(anim.GetFloat("speed") - speed) > 0)//速度不一致,才修改animation的speed参数
{
anim.SetFloat("speed", speed);//切换状态机
}
transform.Translate(new Vector3(0, 0, speed * Time.deltaTime));//上面调整了主角的朝向,所以无论按什么按键主角都往前走就对了
}
else
{
anim.SetFloat("speed", 0);//切换状态机,不移动则播放idle动画
}
}
private void Run()
{
//按下Shit键
if (Input.GetKeyDown(KeyCode.LeftShift))
{
speed += 2f;
}
else if (Input.GetKeyUp(KeyCode.LeftShift))//放开Shit键
{
speed -= 2f;
}
}
/// <summary>
/// 决定角色的正面朝向
/// </summary>
private void UpdateLookAtPosi()
{
lookAtPosi = new Vector3(vNum, 0, -hNum);
lookAtPosi = Quaternion.AngleAxis(eulerAnglesY - rotateAngle, Vector3.up) * lookAtPosi;//将获得的向量旋转,以保证他跟随着主角的旋转,(旋转角度为摄像机相对于初始化的旋转角度)
lookAtPosi += transform.position;//加上当前主角坐标,形成新的坐标,作为主角的朝向
}
效果:
敌人AI
在游戏中要尽量使敌人看起来更加的智能一点,所以就需要一点点AI。首先敌人要巡逻,巡逻用到的就是寻路算法,寻路算法常用的就是A*算法,但是Unity自带了自己的寻路组件,也就是Nav Mesh Agent组件,它允许您使用从场景自动创建的导航网格来创建可以在游戏世界中智能移动的角色几何,动态障碍使您可以在运行时更改角色的导航,而离网链接则使您可以建立特定的动作。所以我们在敌人身上添加Nav Mesh Agent组件,之后将场景的地面设置为Static,然后打开Navigation窗体,选择bake分页,在分页上面设置好各种参数,其中Agent Radius用来定义网格和地形边缘的距离,Agent Height用来定义可以通行的最高度,Max Slope定义可以爬上楼梯的最大坡度,Step Height定义可以登上台阶的最大高度,Drop Heights设置允许最大下落距离,Jump Distance设置允许最大的跳跃距离。最后点击Bake按钮,这样就会开始烘焙导航网格了,烘焙完导航网格后就可以跟根据网格来进行寻路的计算了。之后我们在地图上面选取几个地点作为敌人的寻路地点,然后通过代码切换地点就可以实现敌人的巡逻了。当然敌人也需要一个触发器作为他们的视野,当主角进入他们的视野后他们就要前往主角所在的位置,之后触发对话,对话结束后进入挑战。
敌人移动关键代码:
public bool Move(Vector3 posi,bool isHero)
{
//在摄像机视野内才移动
if (InCameraView() && CanWalk)
{
RefreshPosi(posi);
//判断是否到了停止前进的距离
if (Vector3.Distance(nowPosi, goalPosi) > meshAgent.stoppingDistance)
{
meshAgent.speed = isHero ? runSpeed : aWakeSpeed;//如果是遇到主角的话,要跑起来
meshAgent.stoppingDistance = isHero ? distanceBetweenObject : 0.1f;//角色之间要留点距离
meshAgent.SetDestination(goalPosi);
if (Mathf.Abs(anim.GetFloat("speed") - meshAgent.speed) > 0)
{
anim.SetFloat("speed", meshAgent.speed);//切换动画状态机的参数
}
return true;
}
anim.SetFloat("speed", 0);
return false;
}
else
{
return true;//返回true,让EnemyMotor不要切换移动目标
}
}
/// <summary>
/// 将目标点的y轴更新为当前物体位置的y轴,避免当前物体与目标点处于不同平面,导致寻路预估不准确
/// </summary>
/// <param name="posi">目的地坐标</param>
private void RefreshPosi(Vector3 posi)
{
nowPosi= this.transform.position;
goalPosi.x = posi.x;
goalPosi.z = posi.z;
goalPosi.y = nowPosi.y;
}
/// <summary>
/// 进入触发器
/// </summary>
/// <param name="other"></param>
private void OnTriggerStay(Collider other)
{
if(other.transform==heroMove.transform && !isFistTalk && !isTalk)
{
CanvasControl.instance.UpdateTipImgPosi(this.transform.position+new Vector3(0,5f,0));//设置提示按钮的坐标
CanvasControl.instance.ShowTipImg(true);//显示提示按钮
PushKeyToTalk();//按下E键对话
}
else if(other.transform == heroMove.transform && isTalk)
{
isTalk = false;
goToHero = false;
isFistTalk = false;
UpdateCollderSize();
EnableComponent(false);
talkControl.InvokeRepeating("Talking",0f,0.01f);//启动对话函数,每0.01执行一次
}
}
/// <summary>
/// 修改碰撞器的大小
/// </summary>
public void UpdateCollderSize()
{
sCollider.radius = 2f;//通过修改半径,修改触发器的范围,
sCollider.center = Vector3.zero;//修改触发器的中心点
}
/// <summary>
/// 禁用指定组件
/// </summary>
/// <param name="state">If set to <c>true</c> state.</param>
public void EnableComponent(bool state)
{
sCollider.enabled = state;//禁用碰撞器
this.enabled = state;//禁用该脚本
}
对话关键代码:
public bool Talking()
{
if (!talkingNow && flowChart.HasBlock(blockName))//判断是否在对话当中,并且判断是否拥有该对话
{
flowChart.ExecuteBlock(blockName);
}
talkingNow =flowChart.GetBooleanVariable(isTalkingVariable);//获取对话结束的标识
//判断对话是否结束
if(!talkingNow)
{
HeroMove.instance.canWalk = true;
enemyMotor.isTalk = false;
enemyMotor.EnableComponent(true);
enemyMotor.eMove.CanWalk = true;
//enemyMotor.Enmity = flowChart.GetBooleanVariable("enmity");//获取是否敌对
enemyMotor.GotoChallenge = flowChart.GetBooleanVariable(gotoChallenge);//是否前往挑战
CancelInvoke("Talking");//取消Talking函数的持续执行
}
return talkingNow;
}
挑战设计
追逐挑战
追逐挑战主要是赛跑,在规定时间内挑战者必须要一直领跑。游戏开始时先有个对话,然后是开始倒计时,最后游戏开始。玩家要想保持领跑,必须得准确的按出提示按键,按对了玩家就加速,按错就减速
按键提示设计
在追逐挑战中,敌人随时会加速,所以要想保持领跑的状态必须得根据提示按键按出正确的字符。提示的按键放在整个游戏画面的正中间,本质就是一个图片不断的修改图源,提示按键的刷新机制是当玩家在键盘按下任何一个按键时就刷新,然后根据玩家按下的键判断是否按对了,按对了角色就加速,按错了角色就减速。
关键代码:
/// <summary>
/// 获取随机字符
/// </summary>
public static void ChangeWorld()
{
int index = Random.Range(0, 26);//取随机数
theWorld = allChar[index];
}
/// <summary>
/// 更新字母的图片
/// </summary>
public void UpdateImgSprite()
{
PursueConfig.ChangeWorld();
Sprite sprite = Resources.Load<Sprite>("world/" + PursueConfig.theWorld);//从资源中取出对应的字符图片
tipTextImg.sprite = sprite;
}
地形设计
在挑战中,两个角色都要不停的往前跑,所以游戏的地图要竟可能的无限大,但是如果做的太大又会比较消耗内存,进而降低了游戏的性能,所以要模拟无限地图。首先创建三个空的地板,分别在上面添加一些树木,花朵,建筑等点缀,然后将这3个地形紧挨在一起,我们称它们为A、B、C地形,再将它们的位置存储起来,最后利用碰撞检测判断角色是否跑到了B地形上面,是的话将A地形接到C地形后,当角色跑到C地形上时将B地形接到A地形后。如此反复,就可以实现无线地形了。
关键代码:
/// <summary>
/// 改变地形位置
/// </summary>
public void Change()
{
nowFloor = planeArr[0];
nowFloor.position += distancePosi;
planeArr.Remove(nowFloor);
planeArr.Add(nowFloor);
}
/// <summary>
/// 碰撞检测
/// </summary>
/// <param name="collision"></param>
private void OnCollisionEnter(Collision collision)
{
if(collision.transform!=nowFloor)
{
nowFloor = collision.transform;
if (nowFloor.parent == LimitlessFloor.instance.planeArr[2])
LimitlessFloor.instance.Change();//改变环境的位置,实现无线地形
}
}
炫舞挑战
炫舞挑战顾名思义就是跳舞的PK,玩家要控制自己的角色跳舞,通过按键的正确率来决定最终得分,得分达到总分的百分之六十就代表挑战成功。其实就是对炫舞游戏的拙劣的模仿
提示按键设计
在游戏画面的下方要放一个按键的提示框,里面显示着按键的信息。这些按键都是上后左右方向键的组合。按键提示有刷新机制,每隔5秒刷新一次,每次刷新的按键个数和类型都是随机的,而且每次按键提示的刷新都有倒计时,而这个倒计时是用滚动条模拟的,当滚动条滚到末尾的时候就是刷新的时机了。如果玩家按下的按键和提示的一样,那么该提示就会变色,否则出现红色的叉号。当玩家在规定时间内全部按对了这一批次的提示按键,那么就会从游戏画面底部飘出标志着完美的图标,如果只是按对一部分则飘出标志着一般般的图标,如果全部按错则出现标志着糟糕的图标。
关键代码:
/// <summary>
/// 刷新显示的箭头
/// </summary>
public void RefreshShowArrow()
{
showArrowTipNum = Random.Range(0, 6);//取随机数
int i = 0;
int j;
nowArrowIndex = 0;//重置当前的索引
System.Array.Clear(DancingConfig.ArrowData, 0, DancingConfig.ArrowData.Length);//清空箭头数组
while (i < arrowImages.Length)
{
if (i <= showArrowTipNum)
{
j = Random.Range(0, 4);//0:上,1:下,2:左,3:右
arrowImages[i].gameObject.SetActive(true);
arrowImages[i].sprite = greenArrows[j];//刷新图片
DancingConfig.ArrowData[i] = j;//存储生成的按键信息
}
else
{
arrowImages[i].gameObject.SetActive(false);
}
i++;
}
}
/// <summary>
/// 判断方向键按的是否正确
/// </summary>
/// <returns><c>true</c>, if arrow key down was istrued, <c>false</c> otherwise.</returns>
public bool IstrueArrowKeyDown(int num)
{
if (nowArrowIndex > showArrowTipNum)//超出索引
return false;
if (num == -1 || num != DancingConfig.ArrowData[nowArrowIndex])
{
arrowImages[nowArrowIndex].sprite = errorSprite;//按错按键则显示错误图标
nowArrowIndex++;
return false;
}
else//按键正确
{
arrowImages[nowArrowIndex].sprite = yellowArrows[num];//将图标换成黄色
nowArrowIndex++;
mySore++;//按对,分数加1
}
return true;
}
// Update is called once per frame
void Update () {
if (DancingConfig.startDancing)
{
if (Input.anyKeyDown)
{
AnyKeyDownListener();
}
animatorStateInfo = animator.GetCurrentAnimatorStateInfo(0);//获取第0层正在播放的动画
if (animatorStateInfo.IsName("all_vmd")&& animatorStateInfo.normalizedTime>=1f)//跳舞结束
{
GameOver();
}
}
}
/// <summary>
/// 按键监听
/// </summary>
private void AnyKeyDownListener()
{
if (!Input.GetMouseButtonDown(0) && !Input.GetMouseButtonDown(1))//不是鼠标点击
{
if (Input.GetKeyDown(KeyCode.UpArrow))//上
{
arrowIndex = 0;
}
else if (Input.GetKeyDown(KeyCode.DownArrow))//下
{
arrowIndex = 1;
}
else if (Input.GetKeyDown(KeyCode.LeftArrow))//左
{
arrowIndex = 2;
}
else if (Input.GetKeyDown(KeyCode.RightArrow))//右
{
arrowIndex = 3;
}
else
{
arrowIndex = -1;//其他
}
//isInputTrue = DancingCanvasControl.instance.IstrueArrowKeyDown(arrowIndex);
//if (!isInputTrue && this.transform.position.z>princess.position.z)//如果按键错误,并且角色是在公主前面的
//{
// goalPosi = princess.transform.position;
// nowPosi = this.transform.position;
// InvokeRepeating("ExChangePosi",0f, 0.01f);//与公主交换位置
//}
//else if(isInputTrue)//按键正确
//{
// rightInputNum++;
//}
if(DancingCanvasControl.instance.IstrueArrowKeyDown(arrowIndex))
{
rightInputNum++;
}
}
}
让角色跳起舞来
首先将下载的MMD插件导入Unity中,之后选择一个漂亮的模型pmd文件导入Unity,再下载一个动作vmd文件复制进入Unity。最后点击自动生成的.MMD4Mecanim文件,将vmd动作包拉入Inspector面板对应的框中,点击Process按钮生成模型和动画。最后利用状态机将模型和动画绑定起来,一个会跳舞的角色就产生了。在游戏运行的过程中,规定处于前面的角色为舞蹈团的C位,当玩家把当前批次的按键全部按对时,玩家就变为C位,否则则退出C位。
关键代码:
/// <summary>
/// 交换两个物体的位置
/// </summary>
private void ExChangePosi()
{
this.transform.position = Vector3.Lerp(this.transform.position, goalPosi, 0.2f);
princess.transform.position = Vector3.Lerp(princess.position, nowPosi, 0.2f);
if(this.transform.position==goalPosi)
{
CancelInvoke("ExChangePosi");
}
}
问答挑战
挑战就是答题挑战,玩家必须使出浑身解数答完所有的题目,并且要全部正确才算是挑战成功。
提问模块
这个模块就是通过对话的方式进行问题的提问,也就是问题都是显示在对话框中的。选项使用按钮来显示。首先将问题、选项和答案存储在xml文件当中,之后用代码将xml文件的数据取出来存储在哈希表中。最后按需求将哈希表的内容显示在对话框中。
关键代码:
/// <summary>
/// 加载xml文件
/// </summary>
private void LoadXml()
{
string path = Application.dataPath + "/XMLData/Answer.xml";//问题的xml的地址
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.Load(path);
xmlNodeList = xmlDocument.SelectSingleNode("item").ChildNodes;//item的获取所有的子节点
indexList = new List<int>(xmlNodeList.Count);
for (int i = 0; i < xmlNodeList.Count; i++)
{
indexList.Add(i);//将索引存进去
}
}
/// <summary>
/// 刷新问题
/// </summary>
private void RefreshQuestion()
{
int index = Random.Range(0,indexList.Count);
int i = 0;
lookAnswer.gameObject.SetActive(true);
foreach(XmlElement xmlElement in xmlNodeList[indexList[index]])
{
switch(xmlElement.Name)
{
case "question":
questionText.text = xmlElement.InnerText;//问题
break;
case "answer":
answer = xmlElement.InnerText;//答案
break;
case "select":
buttonTexts[i].text = xmlElement.InnerText;//选项
i++;
break;
}
}
indexList.RemoveAt(index);//将当前已经遍历过的索引去除
}
实现效果:
挑战结束
挑战结束后要根据分数计算玩家是否挑战成功,假如挑战成功则弹出挑战成功的弹窗,否则弹出挑战失败的弹窗。无论是挑战成功还是挑战失败,玩家都可以执行两个操作,一个是重玩,一个是退出挑战。点击重玩后,玩家可以消耗道具卡重新进行一次挑战,假如道具卡已经用完了,则会出现道具卡已用完的提示。
关键代码:
/// <summary>
/// 答题结束
/// </summary>
private void GameOver()
{
gameStartUI.gameObject.SetActive(false);
gameStopPanel.gameObject.SetActive(true);
if(isWin==0)//代表输了
{
failTip.SetActive(true);
}
else
{
winTip.SetActive(true);
}
}
/// <summary>
/// 再来一次
/// </summary>
public void TryAgain()
{
if (QAForestConfig.chanceNum > 0)//还有再来一次的机会
{
QAForestConfig.chanceNum--;
ResetData();//重置数据
}
else
{
tryAgainButton.SetActive(false);
tryAgainTip.SetActive(true);//显示没有次数了
}
}
数据存储
每次进入挑战和挑战结束后都要进行一次角色数据的存储,主要存储的是角色的状态信息和获得的祝福。存储数据是使用了Json,并将生成的.json文件存储在本地,等下次继续游戏的时候可以读取json文件的数据,然后以此来决定角色的个人信息。
代码:
/// <summary>
/// hero数据类
/// </summary>
[Serializable]
public class HeroData
{
public Vector3 nowPosi;//hero当前位置
public Vector3 lookAtPosi;//hero的朝向
public Vector3 cameraPosi;//摄像机位置
public string name;
public bool firstTalk;//是否初次对话
public int chanceNum;//剩余机会次数
public string hasBlessing;//拥有的祝福
public Quaternion cameraRotation;//摄像机的旋转角度
public Vector3 shipPois;//船的位置
public int passChance;//pass的次数
}
/// <summary>
/// 存档
/// </summary>
public void SaveHeroData()
{
UpdateHeroData();//更新数据
string path = Application.dataPath + "/JsonDataFolder/" + heroName + ".json";//角色的json文件的地址
StreamWriter sw = new StreamWriter(path,false);//表示覆盖原文件内容
string jsonDataStr = JsonUtility.ToJson(heroData);//将对象转换为json
sw.Write(jsonDataStr);//写入文件
sw.Close();//关闭写入流
#if UNITY_EDITOR
AssetDatabase.Refresh();//刷新文件夹
#endif
}