由于unity项目过于复杂已发送给测评组
Bilibili视频地址:
原型
游戏
一、结对探索
1.1 队伍基本信息
结对编号:32
队伍名称:Book私议队;
学号 | 姓名 | 作业博客链接 | 具体分工 |
---|---|---|---|
032002521 | 梁佳莺 | 博客 | unity编程学习,组件运动 , AI设计 |
032002519 | 赖怡澄 | 博客 | 原型设计,组件摆放 |
1.2 描述结对的过程
舍友关系好,肥水不流外人田。
1.3 非摆拍的两人在讨论设计或结对编程过程的照片
二、原型设计
2.1 原型工具的选择
- 采用的原型开发工具:墨刀
- 原因:因为墨刀简单易上手,功能也非常齐全
2.2 遇到的困难与解决办法
- 困难描述:最难的是设计,要如何设计一个交互性较强的原型
- 解决尝试:借鉴其他游戏的设计,扩宽自己的思路,设计完成后,让队友参与游玩,交流感受,不断改进
- 是否解决:大体解决
- 有何收获:但思路卡顿的时候,不妨停下来去看看别人怎么完成的,集思广益而不是单单埋头苦干。
2.3 原型作品链接
2.4 原型界面图片展示
(1)主界面
- 因为是unity开发的游戏,因此进入主界面前会有加载界面
- 用户进入页面,有【开始游戏】与【游戏说明】两个选项
- 【开始游戏】设置抖动动画,激发用户点击欲望
- 选择【开始游戏】,将直接进入加载界面
- 选择【游戏说明】,将进入游戏说明界面
- 页面设置有背景音乐,当音乐文件加载完毕便开始循环播放音乐
(2)游戏说明界面
- 介绍游戏规则,最下方有【开始游戏】选项,点击后直接跳转到加载页面
- 页面设置有背景音乐
(3) 加载界面
- 用户进入页面,等待游戏开始,大约2s后自动跳转到游戏界面
- 页面设置有背景音乐
(4)游戏界面
- 用户进入页面,将出现两个对战方框和中间一个骰子,点击骰子可以转出随机点数。右上角设有【暂停键】
- 点击【暂停键】,进入到暂停页面
- 页面设置有背景音乐
(5)暂停界面
-
用户进入页面后,将有三个选项【继续游戏】、【重新开始】、【返回主菜单】
- 选择【继续游戏】,将返回原来的游戏界面
- 选择【重新开始】,将返回新的游戏界面
- 选择【返回主菜单】,将返回主菜单界面
-
页面设置有背景音乐
(6)游戏结束界面
-
根据游戏结果对应有【游戏成功】与【游戏失败】两个结局
- 【游戏成功】界面中设有【再来一次】选项,点击将进入新的游戏界面,如果不想参加,点击任意位置,将返回主界面
- 【游戏失败】界面中设有【重新挑战】选项,点击将进入新的游戏界面,如果不想参加,点击任意位置,将返回主界面
- 【重新挑战】与【再来一次】设有抖动动画,激发用户点击欲望
-
页面设置有背景音乐
三、编程实现
3.1 网络接口的使用
unity游戏开发引擎封装接口
interface ISampleInterface
{
void SampleMethod();
}
class ImplementationClass : ISampleInterface
{
// 显式接口成员实现:
void ISampleInterface.SampleMethod()
{
// 方法实现
}
static void Main()
{
// Declare an interface instance.
ISampleInterface obj = new ImplementationClass();
//给会员打电话
obj.SampleMethod();
}
}
3.2 代码组织与内部实现设计
使用Tools在VS里调试unity
3.3 说明算法的关键与关键实现部分流程图
算法实现思路:
使用贪心算法:对问题求解时,总是做出在当前看来是最好的选择,在每次尝试放入之后选择计算分数,如果数据比上次放入的大,就保存数据,继续测试,最后记录的就是当前得分最大的数据,该位置可以作为要摆放的位置。
由于unity语法复杂,对c#语言了解不够透彻,写上去一直报错,所以还未投入游戏开发中,用java先打出思路:
public Integer nextStep(List<Integer> ownBoard, List<Integer> otherBoard, Integer figure) {
int index = -1;
int maxNum = -999;
for(int j = 0;j < 9; j ++){//9个格子轮流匹配
if(ownBoard.get(j) != 0) continue;//如果我们有输入数据,那就不放数据
Integer[] aMap = ownBoard.toArray(new Integer[0]);//将自己棋盘转化成数组
Integer[] bMap = otherBoard.toArray(new Integer[0]);//将对手棋盘转化成数组
aMap[j] = figure;//我们的数据
for (int i = j / 3 * 3; i < j / 3 * 3 + 3; i++) {//判断在第几行,从头判断到尾
if (bMap[i] == figure) {//对手与我相同,可把对手消除
bMap[i]=0;//消除
}
}
//计算总分
int a_score = 0;
int b_score = 0;
for (int i = 0; i < 3; i++) {
int a = aMap[i * 3];
int b = aMap[i * 3 + 1];
int c = aMap[i * 3 + 2];
if (a == b && b == c) a_score += a * 9;
else if (a == b) a_score += a * 4 + c;
else if (a == c) a_score += a * 4 + b;
else if (b == c) a_score += b * 4 + a;
else a_score += (a + b + c);
}
for (int i = 0; i < 3; i++) {
int a = bMap[i * 3];
int b = bMap[i * 3 +1];
int c = bMap[i * 3 + 2];
if (a == b && b == c) b_score += a * 9;
else if (a == b) b_score += a * 4 + c;
else if (a == c) b_score += a * 4 + b;
else if (b == c) b_score += b * 4 + a;
else b_score += (a + b + c);
}
int temp = 0;
temp = a_score - b_score;//计算分差
if (temp > maxNum){
index = j;
maxNum = temp;//分差越大,效果越好
}
}
return index;
}
3.4 贴出重要的/有价值的代码片段并解释
实现思路:
随机次数,就算先看骰子随机了多少次就转多少次,然后每转一次就随机打开一个面,最后一次打开的面会被记录,是按for的i顺序放的,格子里面要有一个随机转动的骰子,随机转动的骰子每次转动又会是一个随机的面,里面的这个函数是关闭所有的面,因为前面打开的面没有到随机的最后一次的面时,都要关闭为下一个打开的面让出来位置,直到最后一个面打开,会被收录与记录,这样就能在unity里面实现骰子的转动。
public IEnumerator Spin()
{
playerCut = true;
int randomNumber_s = Random.Range(5, 15);//循环5-15次
for (int i = 0; i < randomNumber_s; i++)//循环5-15次
{
if (i < randomNumber_s)
{
Close_s();
}
randomNumber = Random.Range(0, diceImage.Length);//1-6随机一个面
diceImage[randomNumber].gameObject.SetActive(true);//打开循环到的面
dicesprite = diceImage[randomNumber].sprite;//将面赋值给dicesprite
yield return new WaitForSeconds(0.1f);
}
for (int i = 0; i < playerGridBool.Length; i++)
{
if (playerGridBool[i] == false)
{
playerindex = i;
break;
}
}
for (int i = 0; i < playerindex+1; i++)
{
if (playerindex < 3)
{
if (playerGridBool[i] == false)
{
playerGrid[i].sprite = dicesprite;//将 playerGrid[i].sprite替换为dicesprite
for (int ii = 0; ii < playerGrid.Length; ii++)
{
if (playernub[i] == 0)
{
playernub[i] = randomNumber + 1;//循环到的最后一个面的变量+1,因为从0开始,就是这个格子的数
}
for (int iii = 0; iii < enemyGrid.Length; iii++)
{
if (playernub[i] == enemynub[ii])
{
enemynub[iii] = 0;
enemyGridBool[iii] = false;
enemyGrid[iii].sprite = null;
}
}
}
playerGridBool[i] = true;
break;
}
}
if (playerindex < 6)
{
print(i);
if (playerGridBool[i] == false)
{
playerGrid2[x].sprite = dicesprite;//将 playerGrid[i].sprite替换为dicesprite
for (int ii = 0; ii < playerGrid.Length; ii++)
{
if (playernub[i] == 0)
{
playernub[i] = randomNumber + 1;//循环到的最后一个面的变量+1,因为从0开始,就是这个格子的数
}
for (int iii = 0; iii < enemyGrid.Length; iii++)
{
if (playernub[i] == enemynub[ii])
{
enemynub[iii] = 0;
enemyGridBool[iii] = false;
enemyGrid2[iii].sprite = null;
}
}
}
playerGridBool[i] = true;
x++;
break;
}
}
if (playerindex < 9)
{
if (playerGridBool[i] == false)
{
playerGrid3[x2].sprite = dicesprite;//将 playerGrid[i].sprite替换为dicesprite
for (int ii = 0; ii < playerGrid.Length; ii++)
{
if (playernub[i] == 0)
{
playernub[i] = randomNumber + 1;//循环到的最后一个面的变量+1,因为从0开始,就是这个格子的数
}
for (int iii = 0; iii < enemyGrid.Length; iii++)
{
if (playernub[i] == enemynub[ii])
{
enemynub[iii] = 0;
enemyGridBool[iii] = false;
enemyGrid3[iii].sprite = null;
}
}
}
playerGridBool[i] = true;
x2++;
break;
}
}
}
playerzf = 0;
for (int i = 0; i < playernub.Length; i++)
{
playerzf += playernub[i];
playerzftext.text = playerzf +"";
}
enemybool = true;
}
3.5 性能分析与改进
改进思路:能继续优化算法,用多个判断语句来增加特殊情况的判断,能够在一定程度上继续优化
消耗最大的函数:public IEnumerator Spin()(3.4有)用于投掷骰子并利用AI函数判断放置的位置
3.6 单元测试
测试思路:
由于UnityTest在Play Mode中的行为类似于协同程序,因此在编辑模式下可使用
与Assert类测试条件,使用yield跳过一帧,从而测试整个游戏
测试代码:
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
public class MyTestScript
{
// A Test behaves as an ordinary method
[Test]
public void MyTestScriptSimplePasses()
{
// Use the Assert class to test conditions
Debug.Log(nameof(MyTestScriptSimplePasses));
}
// A UnityTest behaves like a coroutine in Play Mode. In Edit Mode you can use
// `yield return null;` to skip a frame.
[UnityTest]
public IEnumerator MyTestScriptWithEnumeratorPasses()
{
// Use the Assert class to test conditions.
// Use yield to skip a frame.
yield return null;
Debug.Log(nameof(MyTestScriptWithEnumeratorPasses));
}
}
测试结果:
3.7 贴出GitHub的代码签入记录,合理记录commit信息
由于在unity上开发,不是很会迁入github,中间并没有很多迁入的记录
四、总结反思
4.1 本次任务的PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时 (分钟) | 实际耗时 (分钟) |
---|---|---|---|
Planning | 计划 | 40 | 40 |
· Estimate | · 估计这个任务需要多少时 间 | 40 | 40 |
Development | 开发 | 2周 | 2周半 |
· Analysis | · 需求分析 (包括学习新技 术) | 4天 | 1周 |
· Design Spec | · 生成设计文档 | 80 | 80 |
· Design Review | · 设计复审 | 40 | 40 |
· Coding Standard | · 代码规范 (为目前的开发 制定合适的规范) | 20 | 20 |
· Design | · 具体设计 | 120 | 160 |
· Coding | · 具体编码 | 3天 | 5天 |
· Code Review | · 代码复审 | 120 | 120 |
· Test | · 测试(自我测试,修改代 码,提交修改) | 70 | 70 |
Reporting | 报告 | 2天 | 2天 |
· Test Report | 报告 | 240 | 240 |
· Size Measurement | · 计算工作量 | 30 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改 进计划 | 100 | 100 |
· 合计 | 3周 | 3周 |
4.2 学习进度条(每周追加)
梁佳莺:
第N 周 | 新增代码 (行) | 累计代码 (行) | 本周学习耗 时(小时) | 累计学习耗时 (小时) | 重要成长 |
---|---|---|---|---|---|
1 | 0 | 0 | 15 | 15 | 熟悉逍遥骰规则,研读题目与具体要求,确定开发环境 |
2 | 60 | 60 | 20 | 20 | 学习unity游戏开发引擎的c#语言并编写部分代码 |
3 | 643 | 60 | 20 | 40 | 两个人共同实现原型设计,代码优化,写AI接口 |
赖怡澄:
第N 周 | 新增代码 (行) | 累计代码 (行) | 本周学习耗 时(小时) | 累计学习耗时 (小时) | 重要成长 |
---|---|---|---|---|---|
1 | 0 | 0 | 9 | 9 | 原型设计主题确定,收集素材 |
2 | 60 | 60 | 16 | 25 | 设计原型,学习unity原型操作 |
3 | 643 | 703 | 20 | 45 | 两个人共同实现原型设计,代码优化 |
注:主要的模型设计都在unity里面生成,所有的东西都放在unity游戏开发引擎里,VS里的c#语言只是让他实现一些操作,达到一些操作的目的,比如骰子还有图形界面都是在unity里.总之就是相当于unity是一个方块,c#让方块运动,因此没有那么多代码, c#语言的每行代码都具有非常强大的功能。
4.3 最初想象中的产品形态、原型设计作品、软件开发成果三者的差距如何?
- 产品形态:一款能供大家娱乐的,具有人人模式与人机模式的投骰子小游戏
- 原型设计:一款交互性强,体验感好的原型设计
- 软件开发成果:一款具有投骰子,放置,对战等基本功能的半成品游戏APP
4.4 评价你的队友
梁佳莺:
- 值得学习的地方:我的队友非常有想法和灵感,她的审美也很好,找图片还有背景音乐以及两个人一起研究unity的过程都很认真专注,做的原型设计也非常好看,效率很高
- 需要改进的地方:我们都需要有耐心,不能生气不能生气
赖怡澄:
- 值得学习的地方:我认为我的队友学习能力很强,这次游戏开发主要是使用unity,因为是从0开始,我上手的非常慢,而我的队友擅长学习钻研,因此我遇到的问题经常咨询她,并和她一起讨论,真的非常感谢♪(・ω・)ノ!
- 需要改进的地方:越在难以攻克的问题上,越需要心平气和
4.5 结对编程作业心得体会
梁佳莺:
- 这次作业难度较大,在课程紧密的同时需要拼凑出时间来完成这个小游戏任务,完成后也是成就感拉满,但其中也出现了很多问题,因此深有体会
- 一个脚本实现一个功能,如果投入太多功能会错乱
- 在VS上debugUnity时,代码编写不当,出现死循环,会死机,unity不动,选择修改后重启继续De
- 安装环境,tools等过程需要耐心
- 导出游戏时选择空的文件夹才能正确导出
- 零基础学习 unity c#,太难了,真的看了非常多资料,终于学习了unity模型最最最最最基本的操作,unity自带很多功能强大的函数,并且能赋予每个组件脚本使其运作,感觉知识量大大超标,所以就取几个自己需要学习的部分去研究。感觉c#语言很多定义方式像c又像java,unity给它赋予了不同的定义,StartCoroutine( )协程和 MonoBehaviour 的 Update函数一样也是在MainThread中执行的,是一个分部执行,遇到条件(yield)会挂起,直到条件满足才会被唤醒继续执行后面的代码,这也是实现这个游戏的重要一环,来完成人机交互过程中每一步的操作。
- 第三周时的时候发现大家都是用网页什么的,做的非常漂亮又有很多功能,可以实现人人模式与人机模式,觉得大家都很厉害,而我们这次设计并没有很完善,unity中的服务器端没有写好,一直报错,因此人和机器下的很困难,在自己会的语法基础上完善了游戏,部分功能并没有实现,很废,其实是个半成品。但是看到made in unity游戏界面出来的时候还是很感动的,有种大型游戏的感觉。
赖怡澄:
-
第一次开发游戏,难度真的好大o(╥﹏╥)o,又是从零开始的一次学习,当然完成后真的非常有成就感。但在完成中遇到了不少问题:
-
unity素材商店加载不出来,可以自己创文件夹上传素材,上传背景图时,可以通过Pixels Per Unit设置像素,调整大小
-
快捷键可以非常大地提高效率,因此使用unity时可以提前了解它的快捷键
-
背景音乐添加后,要选择apply all以及loop
…
-
-
这次游戏设计真的非常困难,第一项用墨刀设计原型就把我难住了,不仅是从来没有使用过墨刀,而且对页游接触较少,不太明白该怎么设计交互性较强的原型,我的解决方法是去4399多玩几个游戏(绝对不是在摸鱼),玩着玩着就慢慢有思路。而墨刀其实算是比较好上手的工具,真正难点在于unity,虽然unity的功能非常强大,但它对于初学者来说其实是不太友好的,但是好在B站中有较多的unity游戏教程,因此跟着教程学可以少走一些弯路。这次游戏我们设计的较简单,因为自己的思路比较局限,不知道怎么把unity的功能和游戏结合,我曾将想给游戏添加键盘的功能,但是这个功能的添加不仅使游戏规则变得更加混乱,而且可玩性也降低了很多,因此就pass了这个想法。我认为这次游戏制作给我带来了全新的学习体验,它不仅提升了工具学习的能力,而且也开阔了我对思维,因为设计,你需要考虑的因素就很多,不单单是代码实现,还需要考虑它的观赏性与可玩性等等,我觉得受益匪浅。