作品基本介绍
分享链接: http://pan.dlut.edu.cn/share?id=ffmfkuu9dpz4
视频链接:https://www.bilibili.com/video/BV1NT421Q7EG/?spm_id_from=333.999.0.0&vd_source=21ef5dd001904577c706b7897134152a
这是一款可以支持自定义关卡即DIY的小游戏,由Unity制作,这里我会写一下相对重要的功能实现。
作品拆解
DIY部分实现
首先是支持DIY的部分,这部分其实非常简单,只需要知道unity的streamingAsset文件夹在构建打包完作品之后里面的内容不会改变即可。实际原理就是在这个文件中做一个按照某项规则创建的文件,可供程序的关卡部分读取实现即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class CSVReading : MonoBehaviour { private void OnEnable() { string filePath = Path.Combine(Application.streamingAssetsPath, "mydata.csv");
StreamReader _reader = new StreamReader(filePath); while (!_reader.EndOfStream) { string line = _reader.ReadLine(); string[] strings = line.Split(',');
foreach (string cell in strings) { CSVKeys.Instance.keys.Add(cell); } }
_reader.Close(); CSVKeys.Instance.StartFashingData(); } }
|
这里我写了一个CSV读取,至于为什么选择CSV,是相较于Excel更方便读取,而且可读性要比json要好一点,所以选择了CSV文件,然后用CSVKeys
这一个单例类来储存每个数据。
游戏输入检测
这个游戏的输入检测可以说是比较重要的部分了。 这里我用一个哈希集合来实现每一帧的输入检测。便于之后的查找。(虽然感觉26个英文字母在各个数据类型的查找区别都不大)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class InputControl : MonoSingleton<InputControl> { public HashSet<KeyCode> pressedKeys = new HashSet<KeyCode>();
private void FixedUpdate() { if (!GameManager.Instance.isPause) { pressedKeys.Clear();
foreach (KeyCode keyCode in System.Enum.GetValues(typeof(KeyCode))) { if (keyCode >= KeyCode.A && keyCode <= KeyCode.Z && Input.GetKey(keyCode)) { pressedKeys.Add(keyCode); } } } } }
|
核心游戏循环
这一段代码是关于游戏的核心上升和下降的代码,在主循环中按照关卡时间调用GetLevelResult()
函数即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
| public class LevelKeysManager : Singleton<LevelKeysManager> { ELevelState _eLevelState;
private int _level = 0;
public int level => _level;
public void ReStart() { _level = 0; } #region 关卡变化的逻辑
private void LevelUp() { if (_level >= CSVKeys.Instance.GetKeysLength() - 1) { return; }
_level++;
UIManager.Instance.UpdateKeyUI(_level); GameScreen.Instance.ShowUpImage(_level);
if (CSVKeys.Instance.GetData(EKeysEnums.isSafety, _level) == "1") { MusicManager.Instance.PlaySFX("Safe"); } else { MusicManager.Instance.PlaySFX("Up"); } }
private void LevelStay() { }
private void LevelDown() { if (_level == 0) { return; } while (CSVKeys.Instance.GetData(EKeysEnums.isSafety, _level - 1) == "1" && _level > 1) { _level--; } _level--;
UIManager.Instance.UpdateKeyUI(_level); GameScreen.Instance.ShowDownImage(_level);
MusicManager.Instance.PlaySFX("Down"); }
#endregion
public void GetLevelResult() { _eLevelState = ELevelState.Up;
if (CSVKeys.Instance.GetData(EKeysEnums.Downlevelkeys, _level).Length == InputControl.Instance.pressedKeys.Count) { foreach (var levelkey in CSVKeys.Instance.GetData(EKeysEnums.Downlevelkeys, _level)) { if (!InputControl.Instance.IsKeyPressed(levelkey)) { _eLevelState = ELevelState.Stay; break; } } } else { _eLevelState = ELevelState.Stay; }
if (CSVKeys.Instance.GetData(EKeysEnums.isSafety, _level) != "1") { foreach (var levelkey in CSVKeys.Instance.GetData(EKeysEnums.DontDownlevelkeys, _level)) { if (InputControl.Instance.IsKeyPressed(levelkey)) { _eLevelState = ELevelState.Down; break; } }
foreach (var levelkey in CSVKeys.Instance.GetData(EKeysEnums.DontUplevelkeys, _level)) { if (!InputControl.Instance.IsKeyPressed(levelkey)) { _eLevelState = ELevelState.Down; break; } } }
switch (_eLevelState) { case ELevelState.Up: LevelUp();
break; case ELevelState.Down: LevelDown();
break; case ELevelState.Stay: LevelStay(); break; } } }
|
音乐管理
这里用到了一个非常简单的全局音效管理,只能说是比较粗糙:
首先用到一个Music的类(当然也可以用结构体)
1 2 3 4 5 6 7
| [System.Serializable] public class Music { public string name; public AudioClip clip; [Range(0f, 1f)] public float volume = 1f; }
|
然后根据这个Music来写一个MusicManager,在Unity中创建一个MusicManager的空物体和两个带着AudioSource的子物体用来实际播放音乐。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| public class MusicManager : MonoSingleton<MusicManager> { public List<Music> musicSounds, sfxSounds;
[SerializeField] private AudioSource musicSource, sfxSource; public float musicVolume => musicSource.volume; public float sfxVolume => sfxSource.volume; public void PlayMusic(string name) { Music s = musicSounds.Find(x => x.name == name); if (s == null) { Debug.Log("没有找到音乐"); } else { musicSource.clip = s.clip; musicSource.Play(); } }
public void PlaySFX(string name) { Music s = sfxSounds.Find(x => x.name == name); if (s == null) { Debug.Log("没有找到音效"); } else { sfxSource.PlayOneShot(s.clip); } }
public void PauseMusic() { musicSource.Pause(); }
public void UnPauseMusic() { musicSource.UnPause(); }
public void StopSFX() { sfxSource.Stop(); }
public void ToggleMusic() { musicSource.mute = !musicSource.mute; }
public void ToggleSFX() { sfxSource.mute = !sfxSource.mute; }
public void MusicVolume(int volume) { float V = volume / 10f; musicSource.volume = V; }
public void SFXVolume(int volume) { float V = volume / 10f; sfxSource.volume = V; } }
|
音乐可视化
这里就是属于一个装饰性的小代码段,主要是用LineRenderer来实现类似于音乐播放器的线跳动,但是可能很多人感兴趣,所以我写在这里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| public class AudioVisualizer : MonoBehaviour { public AudioSource audioSource; public LineRenderer lineRenderer;
[Header("相关参数")] public int linepoint; public float frushTime; private float _timeCount; public float amplitudeMultiplier = 30f; public float length = 6; public int nullPoint;
private List<float> _x; private List<float> _y; private void Start() { _x = new List<float>(); _y = new List<float>(); }
void Update() { _timeCount -= Time.deltaTime;
if (_timeCount <= 0) { float[] spectrumData = new float[256]; audioSource.GetSpectrumData(spectrumData, 0, FFTWindow.BlackmanHarris); lineRenderer.positionCount = linepoint;
for (int i = 0; i < linepoint; i++) { _x.Add((float)i / linepoint * length); }
for (int i = 0; i < linepoint; i++) { if (i < nullPoint || i >= linepoint - nullPoint) { _y.Add(0f); } else { _y.Add(spectrumData[i - nullPoint] * amplitudeMultiplier); } }
for (int i = 0; i < linepoint; i++) { lineRenderer.SetPosition(i, new Vector3(_x[i], _y[i], 0)); }
_timeCount = frushTime; _x.Clear(); _y.Clear(); } } }
|