项目类型:2D平台跳跃+物理场景交互解谜
实现的技术要点:
1,参考QFramework等开源框架开发了属于自己的一套半成品框架提升开发效率
参考其架构设计编写了一些系统和工具类,初步认识了程序架构设计,更加深刻的理解了设计模式在unity开发中的应用
2,初步学习实现了模块化设计,多模块解耦
开发了多个代码模块,可独立运行测试,降低耦合性,提高代码复用率
3,初步了解了Shader的使用,以及一些特效的程序化实现
通过shader实现了玩家手中的光枪发射光束,进行折射反射的特效
具体技术实现:
1,玩家操作系统
使用射线检测+计时器实现了玩家操作手感的优化,参考《蔚蓝》制作了起步加速度,停止加速度,跳跃延迟落下,跳跃落地预输入,土狼时间等功能,且所有数值均存放于PlayerModel(MVC系统)中可供其他系统调取或更改以实现不同场景下的不同操作手感
#region 跳跃
private bool _jumpToConsume;
private bool _bufferedJumpUsable;
private bool _endedJumpEarly;
private bool _coyoteUsable;
private float _timeJumpWasPressed;
private bool HasBufferedJump => _bufferedJumpUsable && _time < _timeJumpWasPressed + _playerModel.JumpBuffer;
private bool CanUseCoyote => _coyoteUsable && !_grounded && _time < _frameLeftGrounded + _playerModel.CoyoteTime;
void HandleJump()
{
if (_grounded) _playerModel.JumpTimes.Value = 1;
if (!_endedJumpEarly && !_grounded && !_frameInput.JumpHeld && rigidBody.velocity.y*(_verticalTowards?1:-1) > 0) _endedJumpEarly = true;
if (!_grounded && !CanUseCoyote) _playerModel.JumpTimes.Value = 0 ;
if (!_jumpToConsume && !HasBufferedJump) return;
if(_playerModel.JumpTimes.Value > 0) ExecuteJump();
_jumpToConsume = false;
}
void ExecuteJump()
{
_endedJumpEarly = false;
_timeJumpWasPressed = 0;
_bufferedJumpUsable = false;
_coyoteUsable = false;
if (_playerModel.JumpTimes.Value >= 0)
{
_frameVelocity.y = (_verticalTowards?1:-1)*_playerModel.JumpForce.Value;
_playerModel.JumpTimes.Value --;
}
}
#endregion
2,场景交互系统
通过IInteractable和ITriggerable接口以及事件系统(依赖EventCenter实现,通过注册实现全局或局部的多层级事件管理结构),以及定义InteractType枚举来实现多种触发方式(光学触发,压力触发,碰撞区域触发等),通过ResetTrigger方法实现可触发物体(例如开关门)的开启关闭,通过编写各种交互物体的抽象基类和具体的交互物体类通过继承实现多种复杂可交互物体的简单实现(例如多开关联动控制的开关门,受多个不同信号控制的移动平台等)
3,框架功能开发
参考QFramework实现项目分层架构由下至上分为Core层,Model层,Logic层,下层不能调用上层,上层可以调用下层,引入Event作为下层通知上层的手段,实现了EventCenter(或EventSystem)用于全局管理事件,引入ICommand接口作为上层(表现层)改变下层(系统层)的手段
实现了使用BindableProperty整合了数据和数据变更事件,Singleton,SingletonMono作为单例脚本的基类等
因为是小项目,所以依赖unity的PlayerPrefs接口实现了游戏设置数据和游戏存档的持久化存储的存储读取系统
public class BindableProperty<T> where T: IEquatable<T>
{
T _Value=default(T);
public T Value
{
get
{
return _Value;
}
set
{
if (!value.Equals(_Value))
{
_Value = value;
OnValueChanged?.Invoke(_Value);
}
}
}
public Action<T> OnValueChanged;
}
public static class SaveSystem
{
#region PlayerPrefs
public static void SaveByPlayerPrefs(string key, object data)
{
var json = JsonUtility.ToJson(data);
PlayerPrefs.SetString(key, json);
PlayerPrefs.Save();
Debug.Log("Successfully saved data to Playerprefs.");
}
public static string LoadFromPlayerPrefs(string key)
{
return PlayerPrefs.GetString(key, null);
}
#endregion
#region Json
public static void SaveByJson(string saveFileName, object data)
{
var json = JsonUtility.ToJson(data);
var path = Path.Combine(Application.persistentDataPath, saveFileName);
try
{
File.WriteAllText(path, json);
#if UNITY_EDITOR
Debug.Log($"Successfully Save Data To {path}");
#endif
}
catch (System.Exception exception)
{
#if UNITY_EDITOR
Debug.LogError($"Failed To Save Data To {path}.\n{exception}");
#endif
}
}
public static T LoadFromJson<T>(string saveFileName)
{
var path = Path.Combine(Application.persistentDataPath, saveFileName);
try
{
var json = File.ReadAllText(path);
var data = JsonUtility.FromJson<T>(json);
return data;
}
catch (System.Exception exception)
{
#if UNITY_EDITOR
Debug.LogError($"Failed To Load Data From {path}.\n{exception}");
#endif
return default;
}
}
#endregion
#region Deleting
public static void DeleteSaveFile(string saveFileName)
{
var path = Path.Combine(Application.persistentDataPath, saveFileName);
try
{
File.Delete(path);
}
catch (System.Exception exception)
{
#if UNITY_EDITOR
Debug.LogError($"Failed To Delete {path}.\n{exception}");
#endif
}
}
#endregion
}
4,插件使用
使用了DOTween插件实现游戏内物体移动动画,因为曾经使用协程和动画曲线硬写过补间动画的实现以及拥有AE动画制作的经验,因此上手迅速,并且开发了基于该插件的Editor拓展,通过自定义结构体AnimationClip,AnimationGroup实现了通过调整场景中物体Transform参数打关键帧的方式实现场景动画的快速制作,并结合前文的场景交互系统快速实现了条件触发,循环播放等常用功能