开篇骚话
前排组队友!有没有美术大佬要去7月份CIGC的Game Jam啊...现场组队好怕不靠谱...不知道还能不能报名现在...(组不到我就专心在学校复习雅思了...卑微orz
这个项目就是一个比较粗糙的音游吧...因为网上貌似很少能找到相关的资料,所以做完这个项目还是斗胆写个流水账日志...万一能对别人能有一丢丢帮助/启发呢...(虽然是组队的作业,但是99%的代码都是我写的,100%的谱面设计&美术设计都是我室友做的......大学生组队.jpg)目前只导出了apk(没有mac的卑微大学生本人了),也放在github了。不过我是再也不想碰这个项目了,学校里的事情总是不能让人愉快...写完这篇日志就跟它说拜拜ヾ(•ω•`)o
先放个gayhub地址,都用的Unity写的,具体的看项目吧:
做谱面的关卡编辑器:https://github.com/anlideer/MusicGameDesigner
游戏:https://github.com/anlideer/MusicGameDemo
制作谱面
json存储
写了个音符类,用来在json里存储,游戏那边也能按这个来读取。
[System.Serializable] public class MusicIcon { public bool type; // false - 点击式, true - 长按式 public int track; // 轨道编号 public float pos; // 在小节中的位置 public int code; // 唯一标识符 public int lastPoint = 0; public int nextPoint = 0; // 用唯一标识符来表示节点间的关系,因为貌似json不支持引用那种... }
(诺娃的代码块真的好难用啊!实名吐槽!你这样会失去我的!
然后通过各种魔性骚操作把这个类跟Unity里实际使用的两种继承MonoBehaviour音符(对,我们这个只有点击式的和长按式的)的类互相转换就行,具体的不写了。
json文件用的是C#普通的文件读写,固定路径(注意使用的时候必须保证D盘有Dream这个文件夹,不然没法保存(虽然也就是代码里加几行的事情...我是真不想动这项目了...
string fileName=@"D:\Dream\myData.json";
音乐、bpm、小节数也都是引擎里写好了固定好的,没有精力写更动态灵活的了,我系真的好忙我枯了。
音符编辑
就,在谱面上放置那些音符。
虽然我知道我的实现方式很low(做好单个拍子6个轨道的Prefab,然后开始的时候加载好),但是还是能用的......
放音符、连线啥的都挺简单的,我反倒觉得更重要的是功能让使用者觉得舒服,够用就行,具体的还是看项目吧...一边写一边抓着室友问东问西的,所以做音游还是得多懂点音乐(像我只会听歌弹吉他,乐理啥都不懂hhh跟室友艰难沟通后终于写出来了hhh
连线的就是用的LineRenderer,由前面的音符负责连到后面的音符,每帧更新LineRenderer的位置即可(不然拖了进度条它们会留在原地),关于LineRenderer可以看我前面的日志,这个截图里还没让LineRenderer保持在最前面,所以有点被挡住的感觉,github上那个版本已经修好了。
效果大概就这样,从我发给室友给她说咋用的视频(视频也扔github了)里截了个图:(不要嘲笑我的审美水平555我害是个海子
进度
据室友反馈说我这个要是从头放到尾不会有啥问题,但是拖了进度条之后到比较后面的地方音乐和我那条移动的线之间就会有半拍左右的误差...我也想不明白为啥...
这个也没太多可说的,注意播放的时候更新上面的slider的value。然后拖进度条之后,也就是Slider的onValueChanged调用的函数:
public void AdjustShownBars() { GetComponent<InsMetres>().showBars(sb.value); if (!LineMoving.isMe) StopMusic(); musicNow = sb.value; line.GetComponent<LineMoving>().BackToOriginal(); }
其中那个LineMoving.isMe是为了防止在播放的时候我更新slider的value的时候也调用了这个自动暂停(那体验可太差了是吧hhhh
音游
最后各种延迟还是有点高,勉强能玩吧...这块时间仓促确实没有弄的非常好...虽然吐血搞到凌晨,但是音游本身还是只写了两三天吧,况且还只有我一个人......(虽然助教小姐姐好像根本不care我写了好久好久的游戏本身,反倒怼我没有关卡选择界面,我跟她说既然我们只有一关那这个东西既不关键也很好做...还是现场加了,但是最后从其他助教那听说我把她惹生气了?我好服啊我觉得她根本没有抓到重点嘛...她要说我延迟高我就低下头乖乖挨打了......越想越气哼...好了发泄完毕...
谱面读入和加载
// 游戏中使用的类 public class Icon { public bool type; // false - 点击式, true - 长按式 public int track; // 轨道编号 public float time; public Icon lastPoint = null; public Icon nextPoint = null; }
我把json文件放Resources了,直接在游戏里读取了,然后转化成上面这个类,在游戏里会比较好用一点。
加载的话,大概就像下面这样。loadP是个记录我加载到哪里了的索引,防止我每次都要再遍历一遍。我设的速度是1s掉10f的距离。这大概有两个比较需要注意的点,一是生成时的位置在哪里,二是长按式的该怎么处理。
if (!isEnd) { // 因为音符是遵循时间顺序的,所以从前往后就行,这里有个漏洞,前2s的音符无法进行加载 if (Mathf.Abs(GM.iconList[loadP].time - (Time.timeSinceLevelLoad - GM.startTime) - 2f) < 0.02f) { // 点击式 if (GM.iconList[loadP].type == false) { //Debug.Log("INS CLICK"); InsClick(); // 自己写的函数,生成点击式音符 } // 长按式 else { //Debug.Log("INS LAST"); InsLast(); // 自己写的函数,生成长按式音符 } loadP++; } else if (GM.iconList[loadP].time - (Time.timeSinceLevelLoad - GM.startTime) - 2f < 0) { loadP++; } if (loadP < loaded.Count && loaded[loadP]) { loadP++; } if (loadP >= loaded.Count) { isEnd = true; } }
生成时的位置的话,直接加固定的会造成两个本来该在一条线上的音符有微小但肉眼可见的位置误差,所以用下面的方法,用时间再算一次。
v.y+= (GM.iconList[loadP].time- (Time.timeSinceLevelLoad-GM.startTime)) *10f;
长按的处理方式,我之前用了一个List存每个音符有没有被加载过,所以长按式音符会加载它的所有next的音符,并且用上面的方法换算位置,记录好这个已经被加载过了,连好线(加好LineRenderer组件),每帧乖乖更新线。
其他的什么给音符加计时器自己销毁自己什么的就不说了...
判定
判定的话,我做的不太好,很蠢的用了collider(听说collider会有延迟,而且挺耗资源的?
疯狂加大collider、放宽判定之后勉强能玩......
比较需要注意的就是点击式音符被点击后的破裂效果,要么用新的物体(我是这样的)在那个位置放动画,要么让原物体被点了之后别再下落。(不然很鬼畜别问我怎么知道的)还有长按式音符的圈圈特效也是一样需要用新的物体来搞。
因为没怎么做过移动端的游戏,所以对Input.touch相关的东西不太了解,最后用了个List存所有长按的东西然后根据后面的来更新(如果是null也就是已经自己销毁自己了就把它从List里开除),好在Input.touches数组的顺序貌似是一样的,至少先按下去的应该是在前面(我猜...)
另外花了我比较久时间的就是长按式音符的那个旋转的圈圈,如何圆滑的在上一个和下一个点之间移动(而且保持高度在判定线上),最后写了直线的解析式来求x坐标:
// 跟着解析式继续移动 Vector3 lastV = last.transform.position; Vector3 nextV = next.transform.position; if (lastV.x == nextV.x) { Vector3 vec = transform.position; vec.x = lastV.x; transform.position = vec; } else { Vector3 vec = transform.position; vec.x = ((lastV.x - nextV.x) / (lastV.y - nextV.y)) * (vec.y - lastV.y) + lastV.x; transform.position = vec; }
反正在这只能很粗糙的说,感兴趣还是看项目吧...赶作业去了...
暂无关于此日志的评论。