前言
21年年底的时候参加了社团的新年gj,和小队的成员一起花七天完成了一个简单的2D平台跳跃加解谜(应该是?)的游戏。当时虽然写了一份反思但是没写完就干别的去了,后来就这样搁置了。最近考研复习情况不是很理想打算重操旧业,再回顾以往的项目时又重新复盘了这次经历,决定把上次没写完的补充完整。
项目启动
这次GJ题目公布好像是23号的上午,但是因为我之前一直在忙其他的事,一直到下午五六点我才发现题目已经给出.有一点很大的不足是我想在题目给出后应该是一个全员脑暴的阶段,可是我们似乎觉得这是策划的事,完全没有参与进游戏玩法设计的谈论中,这也导致了策划可能在设计策划案的时候并不知道程序到底能做到什么,最后就导致了游戏比较中规中矩,没有什么比较离谱的地方,这不是什么缺点,但显然也不是什么优点。
而刚开始的两天我也还想尝试兼顾算法集训和GJ,导致前期准备不足.虽说有提前根据策划给出的思路去学习相关知识的考虑,但实际上并没有学多少.现在回顾,应该在第一天开始就和策划敲定大体思路,然后开始将功能实现思路写出来 ,并开始设计大体的框架。
完成内容
- 自己设计了一个简单的框架,使用manager类管理游戏整体的进程,音频,对话等
- 学习了unity结合live2D的模型与动画作为角色
- 设计了一个包含背景的对话组件
- 基于委托的的碰撞监听器工具(参考了别人的代码)
- 关卡跳转的实现
- 基于tween动画实现的一些简单陷阱类
详细
需求
这次gj设计的是通过在解谜场景中点击物品后进入平台跳跃的关卡,在关卡中收集完了指定的物品后就算通关。一共有三个解谜场景,收集完一个解谜场景中的全部物品后可以转向下一个场景,中间会穿插对话剧情。
角色控制
由于美术同学提供的角色模型是live2D模型,所以需要接入live2D的sdk,地址:https://www.live2d.com/download/cubism-sdk/。
后续的操作由于时间太久远实在记不清了,只记得是在B站找的unity接入live2D的教程。
角色控制脚本使用InputManager来读取输入,通过几个bool值来判断角色状态。没什么好说道的,很平庸的代码。附上一段多段跳实现代码
//isGround的值在fixedUpdate中更新
isGround = Physics2D.OverlapCircle(groundCheak.position, 0.01f, ground);
void Jump()//跳跃
{
if (isGround)//跳跃结束判断
{
jumpCount = 2;
anim.SetBool("jump",false);
}
if (isGround&&jumpPressed)//第一次跳跃
{
rb.velocity = new Vector2(rb.velocity.x, jumpForce);
jumpCount--;
jumpPressed = false;
anim.SetBool("jump",true);
}
else if(isGround == false &&jumpPressed && jumpCount > 0)//空中多段跳实现,用isGround使得当从空中坠落时依然可以触发多段跳
{
rb.velocity = new Vector2(rb.velocity.x, jumpForce);
jumpCount--;
jumpPressed = false;
anim.SetBool("jump",true);
//jumpAudio.Play();
}
}
改进思路
角色控制这块的代码很粗糙,可以考虑用状态机优化。
游戏进程
游戏进程使用gameManage类进行管理,这个类中主要是关于根据物品收集进度进入不同场景的代码,用一个flagData的数据类来获取游戏的收集进度。
manage类与这个数据类都应该是一个单例,管理全图的数据,不必继承mono,用[System.Serializable]序列化,用了懒加载的方式实例化。
//flagData初始化
private static readonly FlagData _instance = new Lazy<FlagData>().Value;
public static FlagData Instance{
get => _instance;
}
在场景切换方面,因为是一个2D游戏,因此将所有的平台跳跃关卡和解谜关卡都放在一个场景中,通过移动角色和切换摄像机来实现关卡的切换,但是直接移动会显得很突兀,因此我在这个过程中间插入一段虚假的加载动画来避免这种突兀感。
//gamemanage部分代码
/// <summary>
/// 切换摄像机
/// </summary>
/// <param name="_old"></param>
/// <param name="_new"></param>
void CameraChange(int _old,int _new){
cameras[_old].gameObject.SetActive(false);
cameras[_new].gameObject.SetActive(true);
FlagData.Instance.CameraId = _new;
}
/// <summary>
/// 从关卡进入下一房间,条件不满足则返回上一房间
/// </summary>
/// <param name="_id">房间id,从1开始</param>
public void RoomChange(int _id){
switch (_id)
{
case 1:
//此处加入进入下一房间判断条件
if(true){
FlagData.Instance.RoomId = _id;
}
break;
case 2:
//此处加入进入下一房间判断条件
if(FlagData.Instance._flag["关卡1"]
&& FlagData.Instance._flag["关卡2"]
){
FlagData.Instance.RoomId = _id;
}
break;
case 3:
//此处加入进入下一房间判断条件
if(FlagData.Instance._flag["关卡3"]
&& FlagData.Instance._flag["关卡4"]){
FlagData.Instance.RoomId = _id;
}
break;
case 4:
//此处加入进入下一房间判断条件
if(FlagData.Instance._flag["关卡5"]
&& FlagData.Instance._flag["关卡6"]){
}
break;
default:
break;
}
FlagData.Instance.PlayerStart = false;
FlagData.Instance.Loading = true;
AudioManage.Instance.MusicChange(FlagData.Instance.RoomId);
CameraChange(FlagData.Instance.CameraId,FlagData.Instance.RoomId);
}
/// <summary>
/// 进入指定关卡
/// </summary>
/// <param name="_id">关卡id,从0开始</param>
public void CustomPassChange(int _id){
FlagData.Instance.CustomPassId = _id;
AudioManage.Instance.MusicChange(0);
SceneManage.Instance.PassReset();//重置关卡,并将人物移动过去
CameraChange(FlagData.Instance.CameraId,0);
FlagData.Instance.PlayerStart = true;
}
//关卡进入的加载页面
IEnumerator jumpPanel(){
AudioManage.Instance.MusicStop();
AudioManage.Instance.EffectPlay(2);
_jumpPanel.SetActive(true);
yield return new WaitForSeconds(2.0f);
_jumpPanel.SetActive(false);
FlagData.Instance.Loading = false;
AudioManage.Instance.MusicPlay();
}
public void Loading(){
if(FlagData.Instance.PlayerStart){
_loadingPanel.SetActive(true);
}else{
StartCoroutine(jumpPanel());
}
}
平台跳跃关卡
关卡内部用一个SceneManage类管理,只做了重置关卡的功能,由gamemanage调用。
public class SceneManage : MonoBehaviour
{
private static SceneManage _instance;
public static SceneManage Instance{
get => _instance;
}
public GameObject[] customPass;//所有关卡 0-相框 1-花 2-衣服 3-衣柜 4-食物 5-灶台
private GameObject _customPass;//当前关卡
public Transform Player;
void Awake() {
_instance = this;
}
/// <summary>
/// 重置当前关卡
/// </summary>
public void PassReset(){
FlagData.Instance.Loading = true;
_customPass = customPass[FlagData.Instance.CustomPassId];//获取当前关卡
Player.GetComponent<Rigidbody2D>().velocity = Vector2.zero;
Player.transform.position = _customPass.transform.Find("playerSpawn").transform.position;
_customPass.BroadcastMessage("_reset",null,SendMessageOptions.DontRequireReceiver);
}
}
重置关卡的实现是通过在场景中的机关类中写一个_reset方法,游戏开始时记录初始的状态,然后通过SceneManage向这个关卡广播一个消息,让每一个机关都调用_reset方法,把状态变回初始时的状态,从而实现重置关卡。
音频管理
通过AudioManage来管理游戏中的音乐与音效的切换和播放,由于是最后做的功能时间比较紧所以没做其他的东西,比如音量调整之类的。
public class AudioManage : MonoBehaviour
{
private static AudioManage _instance;
public static AudioManage Instance{
get => _instance;
}
public AudioSource musicSource;
public AudioSource soundSource;
public AudioSource effectSource;
public AudioClip[] _music;
public AudioClip[] _sound;
void Awake() {
_instance = this;
}
public void MusicChange(int _id){
musicSource.clip = _music[_id];
}
public void MusicStop(){
musicSource.Stop();
}
public void MusicPlay(){
musicSource.Play();
}
public void MusicPause(){
musicSource.Pause();
}
public void SoundPlay(int _id){
//soundSource.Stop();
soundSource.clip = _sound[_id];
soundSource.Play();
}
public void EffectPlay(int _id){
effectSource.clip = _sound[_id];
effectSource.Play();
}
}
对话管理
外面太阳要没了,我要先去晒一下太阳,回来再看心情写