成就系统

成就系统——观察者模式

百无聊赖,周末来写一个博客吧。苦于前几次去面试都被问到同一个问题,今天来好好想一下,该怎么做,怎么做才会更好一些。

游戏中的成就系统,是一个根据一些特定事件去完成一些目标,然后获得奖励的玩法。游戏中的各种事件遍布游戏各处,成就类型又是多样化的。

先说一下我自己的做法,在游戏中成就是和玩家相关的,所以直接在玩家的系统组件中添加了一个成就系统。我们要做的就是在成就系统中有一个事件触发的接口,所有的事件触发都会调用到这个地方,然后通过许许多多的事件定义来区分各个成就的处理(当然这里的前提是玩家只有在线的时候才能获得成就)。

void onNotify(Event event,int param, int num);

所以我理解的成就系统就是对一些成就类型的特定参数的计数(比如对进入场景类型,特定参数是A场景)。

struct Achievement
{
	int id_;
	int param_;
	int num_;
	Event event_;
};

但是,游戏里还包含这样的需求,比如说这个目标是进入A场景,那个目标是进入B场景。一开始的时候我们试图使用一个参数 param_来给他们找一个共同点。但是后来仍然失败了,然后就不得不扩展到param1_param2_

感觉这是一种比较通常的做法了,但是有一天我看了项目的技能使用的做法,我就想改进我的成就系统了。技能释放是用的是一个消息队列实现的。具体做法是,释放技能者(游戏中的一些实体)将释放的技能添加到消息队列线程,消息队列线程管理消息列表,以确定消息什么时候以及,被哪个对象接收。消息队列的好处是解耦了消息释放者与接受者关系,而且还能保证消息的有序性。

学习设计模式就是让你和其他同类从业者交流的时候能有一个统一的名字来指明同一种系统结构的实现方式,这个消息队列模式在《游戏编程模式》里也有提到,里面讲的是事件队列。但我现在还没搞清楚事件队列和消息队列有什么不一样,看上去很像同一个东西。

就这样在面对面试官的一个致命问题时,我展现出了灵活的一面——你觉得这个系统还有什么地方做的不够好吗?如果有,有想过怎么改进吗?这个问题我本来是想用消息队列或者事件队列来改进优化的,但现在看来这完全是一种错误的想法。当然我也有理由:同步调用会阻塞当前程序执行,要等到所有调用结束后才会继续执行,而且当前成就也许会触发其他的游戏事件,这样就加大了调用的深度;所以我的理由就是降低调用深度。(但事实上调用深度很大的的只是少数情况)

比较一下事件队列观察者最大的不同在于事件队列是使用异步处理的方式解耦了事件触发者和事件关注者,而观察者通常是同步调用的。我们想一下这个情况,在游戏角色获得一个物品时,要触发一个成就事件的改变,但是玩家在获得这个物品后马上又使用了它;这就导致,系统在接收到物品增加的消息时再去查看这个物品,发现它已经不存在了。

以上我成就系统是基于玩家组件方式实现,当然还可以基于系统组件来实现,这时候我想*onNotify()*就要换一种写法,把Actor指针也传进去。

void onNotify(const Actor actor, Event event,int param, int num);

the end…

												2018年10月21日
### 设计和实现 Unity 成就系统的概述 在 Unity 游戏开发中,成就系统是一种激励玩家并增强用户体验的重要机制。通过设计和实现成就系统,可以增加游戏的可玩性和吸引力。以下是关于如何构建一个基本成就系统的详细说明。 #### 1. 数据结构的设计 为了存储成就的相关数据,通常会创建一个脚本来定义成就的数据模型。这可以通过 C# 类来完成,其中包含诸如名称、描述、解锁状态等属性。 ```csharp [System.Serializable] public class Achievement { public string name; public string description; public bool isUnlocked; public void Unlock() { isUnlocked = true; } } ``` 此代码片段展示了 `Achievement` 的类定义以及其核心功能[^5]。 #### 2. 存储与加载成就进度 由于成就的状态需要在玩家退出后再进入时保持一致,因此应考虑持久化保存这些数据。Unity 提供了几种方法用于本地数据存储,比如 PlayerPrefs 或 JSON 文件。 对于简单的键值对存储需求,PlayerPrefs 是一种便捷的选择: ```csharp public static void SaveAchievements(Achievement[] achievements) { foreach (var achievement in achievements) { if (achievement.isUnlocked) { PlayerPrefs.SetInt(achievement.name, 1); } else { PlayerPrefs.SetInt(achievement.name, 0); } } } public static void LoadAchievements(Achievement[] achievements) { foreach (var achievement in achievements) { int value = PlayerPrefs.GetInt(achievement.name, 0); achievement.isUnlocked = (value == 1); } } ``` 上述函数实现了成就数组的存取操作[^6]。 #### 3. 用户界面展示 当某个成就可以被触发或者已经达成条件时,应该及时通知玩家。为此可以在 UI 上显示相应的提示框或图标变化效果。 利用 Unity 的 Canvas 和 EventSystem 可以轻松搭建这样的交互界面组件,并绑定逻辑事件处理程序到按钮或其他控件上。 #### 4. 实现具体业务逻辑 最后一步就是编写具体的检测算法去判断何时满足特定成就的要求。例如,在游戏中击败一定数量敌人之后授予奖励;连续跳跃次数达到指定数目给予额外分数等等。 假设我们有一个名为 "DefeatEnemies" 的成就,则可能如下设置它的激活规则: ```csharp private void OnEnemyKilled(int count){ var defeatEnemiesAchivement = GetAchievementByName("DefeatEnemies"); if(defeatEnemiesAchivement != null && !defeatEnemiesAchivement.isUnlocked && count >= REQUIRED_KILLS){ defeatEnemiesAchivement.Unlock(); ShowNotification($"You've unlocked the '{defeatEnemiesAchivement.name}'!"); } } ``` 这里演示了一个典型的响应型成就解锁流程[^7]。 --- ### 总结 综上所述,从基础架构建立至高级特性扩展,整个过程涵盖了多个方面的技能应用与发展思路。希望以上内容能够帮助您更好地理解如何在自己的项目里加入类似的特色玩法!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值