简介
该项目最初只是因为元夜描图画了一个元夜子的icon,在交流的过程中,一个游戏项目便悄然诞生。
游戏类型:2D横版酷跑游戏
人员
程序:抹茶
美术:元夜&抹茶
策划:元夜
引擎:Unity3D Pro
IDE:Visual Studio for Mac
新技术记录
单例模式的相关基类
泛型基类(无关Mono)
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
| using System;
public class SingletonBase<T> where T : class { private static T m_instance; private static readonly object SyncObject = new object();
public static T Instance { get { if (null == m_instance) { lock (SyncObject) { if (null == m_instance) { m_instance = (T)Activator.CreateInstance(typeof(T), true); } } } return m_instance; } } }
|
会自动销毁的Mono单例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| using UnityEngine;
public class SingletonMono<T> : MonoBehaviour where T: MonoBehaviour { private static T m_instance = null;
public static T Instance { get { return m_instance; } }
protected virtual void Awake() { m_instance = this as T; } }
|
游戏运行过程中保证唯一且不会销毁的Mono单例
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
| using UnityEngine;
public class SingletonAutoMono<T> : MonoBehaviour where T : MonoBehaviour { private static T m_instance = null;
public static T Instance { get { if (m_instance == null) { GameObject obj = new GameObject(); obj.name = typeof(T).ToString(); DontDestroyOnLoad(obj); m_instance = obj.AddComponent<T>(); } return m_instance; } }
}
|
参考
继承自Monobehavior基类的子类Start函数的调用
在使用继承自Monovehavior的父类时,其子类如果实现Start等Unity方法时,只会优先实现子类的Start方法,其父类中的代码段不会被执行。
而在实现Animal基类时,期待可以在基类的Start方法中初始化碰撞体。
为了解决这一问题,我们可以在Animal基类中实现类似于Unity的Start等方法,在基类的Start等方法中调用单独实现的对应虚方法,如果子类需要调用则重写基类相关方法,否则可以不写。
设置为虚函数可以保证子类并非强制重写,而如果设置为抽象函数,那么无论子类是否需要进行实现,都需要对此进行重写,因此这里采用虚函数的方式。
其基类对应的实现如下
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
| using System.Collections; using System.Collections.Generic; using Unity.VisualScripting; using UnityEngine;
public abstract class Animal : MonoBehaviour { void Start() { OnStart(); }
void Awake() { OnAwake(); }
void Update() { OnUpdate(); }
protected virtual void OnStart() {
}
protected virtual void OnUpdate() {
}
protected virtual void OnAwake() {
} }
|
对应子类的实现方式如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| using System; using System.Collections; using System.Collections.Generic; using UnityEngine;
public class Child : Animal { protected override void OnStart() { }
protected override void OnUpdate() { } }
|
参考链接:关于继承MonoBehaviour的一些记录
观察者模式与事件中心
在玩家触碰到怪与死亡的时候,需要UI界面减小体力,怪物接收到指令后执行相应的事件。
为此,我们有三种方式
- 拉取
- 方式:在Player中设置一个状态变量,比如isAlive,在死亡的时候改变该值,其他需要获取的脚本在每帧检测,在对应时机执行。
- 缺点:不及时,拉取实际只能为下一帧,该方式比较消耗性能
- 推送
- 方式:在Player中保存所有需要监听的组件,当死亡时向Player维护的数组中所有的组件发送死亡消息(调用对应的方法)
- 缺点:拓展性差,紧耦合,具体表现为,无论实现什么新的方法都需要Player去进行关心,如果在联合开发的时候,每次开发新的功能都需要其他人来告诉Player部分的开发者添加什么样子的功能。
- 事件:
- 方式:使用委托构建事件系统,在需要调用的实际调用该委托类的Invoke方法,其他需要监听的地方调用并且添加相应的监听方式(AddEventListener)
- 优点:结合前两种方式的优点
其对应代码如下
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
| using System.Collections.Generic; using UnityEngine.Events;
public interface IEventInfo{ } public class EventInfo : IEventInfo { public UnityAction Action; } public class EventInfo<T> : IEventInfo { public UnityAction<T> Action; }
public class EventSystem : Singleton<EventSystem> {
public Dictionary<string, IEventInfo> actionDictionary = new();
public void AddEventListener(string name, UnityAction action) { if (actionDictionary.ContainsKey(name)) { (actionDictionary[name] as EventInfo).Action += action; } else { actionDictionary.Add(name, new EventInfo() { Action = action}); } }
public void AddEventListener<T>(string name, UnityAction<T> action) { if (actionDictionary.ContainsKey(name)) { (actionDictionary[name] as EventInfo<T>).Action += action; } else { actionDictionary.Add(name, new EventInfo<T>() { Action = action }); } }
public void RemoveEventListener(string name, UnityAction action) { if (actionDictionary.ContainsKey(name)) { (actionDictionary[name] as EventInfo).Action -= action; } }
public void RemoveEventListener<T>(string name, UnityAction<T> action) { if (actionDictionary.ContainsKey(name)) { (actionDictionary[name] as EventInfo<T>).Action -= action; } }
public void TriggerEventListener(string name) { if (actionDictionary.ContainsKey(name)) { (actionDictionary[name] as EventInfo).Action?.Invoke(); } }
public void TriggerEventListene<T>(string name, T paramater) { if (actionDictionary.ContainsKey(name)) { (actionDictionary[name] as EventInfo<T>).Action?.Invoke(paramater); } }
public void Clear() { actionDictionary.Clear(); } }
|
开发日志
2022-02-05: 正式建立项目
2022-02-20: 完成开发UI框架
2022-03-05: 第一次与元夜子进行面对面沟通意见
2022-03-06: 完成故事读取(含联网获取)框架
2022-03-12: 第二次与元夜子进行面对面交换意见
2022-03-20: 编写Animal类及其子类,制作Animals预构件
2022-03-25: 完善Player和Enemies的预构件
2022-04-03: 将加载界面和文字滚动显示界面嵌入UIFramework
2022-04-19: 添加背景移动(及基类等)和角色移动控制。
2022-06-18: 重构背景层素材,并完善背景的分层移动和昼夜切换