我正在考虑设计一个在我的网站上使用的成就系统的最佳方法。数据库结构可以在Best way to tell 3 or more consecutive records missing找到,这个线程实际上是从开发人员那里获得想法的扩展。
我在这个网站上有很多关于徽章/成就系统的讨论就是这个问题 - 这都是谈话而不是代码。实际的代码实现示例在哪里?
我在这里提出一个设计,我希望人们可以做出贡献,并希望为编码可扩展的成就系统创建一个好的设计。我不是说这是最好的,远非它,但它是一个可能的起点。
请随时提出您的想法。
我的系统设计理念
似乎普遍的共识是创建一个“基于事件的系统” - 每当发生已知事件就像创建,删除等事件一样,它会像这样调用事件类。
$event->trigger('POST_CREATED', array('id' => 8));
事件类然后找出哪些徽章正在“监听”此事件,然后它requires
该文件,并创建该类的实例,如下所示:
require '/badges/' . $file;
$badge = new $class;
然后它调用默认事件传递调用trigger
时收到的数据;
$badge->default_event($data);
徽章
这就是真正的魔法发生的地方。每个徽章都有自己的查询/逻辑,以确定是否应颁发徽章。每个徽章都列在例如这种格式:
class Badge_Name extends Badge
{
const _BADGE_500 = 'POST_500';
const _BADGE_300 = 'POST_300';
const _BADGE_100 = 'POST_100';
function get_user_post_count()
{
$escaped_user_id = mysql_real_escape_string($this->user_id);
$r = mysql_query("SELECT COUNT(*) FROM posts
WHERE userid='$escaped_user_id'");
if ($row = mysql_fetch_row($r))
{
return $row[0];
}
return 0;
}
function default_event($data)
{
$post_count = $this->get_user_post_count();
$this->try_award($post_count);
}
function try_award($post_count)
{
if ($post_count > 500)
{
$this->award(self::_BADGE_500);
}
else if ($post_count > 300)
{
$this->award(self::_BADGE_300);
}
else if ($post_count > 100)
{
$this->award(self::_BADGE_100);
}
}
}
award
函数来自扩展类Badge
,它基本上检查用户是否已经被授予该徽章,如果没有,将更新徽章db表。徽章类还负责检索用户的所有徽章并将其返回到数组等中(因此徽章可以例如显示在用户配置文件上)
系统何时首次在现场网站上实施?
还有一个“cron”作业查询可以添加到每个徽章。这样做的原因是因为当徽章系统首次实施和启动时,应该已经获得的徽章尚未被授予,因为这是基于事件的系统。因此,根据需要为每个徽章运行CRON作业,以奖励任何需要的东西。例如,上面的CRON作业看起来像:
class Badge_Name_Cron extends Badge_Name
{
function cron_job()
{
$r = mysql_query('SELECT COUNT(*) as post_count, user_id FROM posts');
while ($obj = mysql_fetch_object($r))
{
$this->user_id = $obj->user_id; //make sure we're operating on the right user
$this->try_award($obj->post_count);
}
}
}
由于上面的cron类扩展了主徽章类,它可以重用逻辑函数try_award
我为此创建一个专门的查询的原因是虽然我们可以“模拟”以前的事件,即通过每个用户帖子并触发事件类,如$event->trigger()
,它会非常慢,特别是对于许多徽章。因此,我们改为创建优化查询。
用户获得奖项的是什么?所有关于根据事件授予其他用户的权利
Badge
类award
函数作用于user_id
- 他们将永远获得奖励。默认情况下,徽章将授予发生事件的人员,即会话用户ID(对于default_event
函数,这是正确的,尽管CRON作业显然会遍历所有用户并奖励单独的用户)
让我们举一个例子,在编码挑战网站上用户提交他们的编码条目。管理员然后判断条目,完成后,将结果发布到挑战页面供所有人查看。发生这种情况时,会调用POSTED_RESULTS事件。
如果您想为所有发布的条目为用户颁发徽章,那么,如果他们排在前5名之内,您应该使用cron作业(尽管记住这会为所有用户更新,而不仅仅是针对该挑战结果发布)
如果您想要使用cron作业定位更具体的区域,让我们看看是否有办法将过滤参数添加到cron作业对象中,并获取cron_job函数来使用它们。例如:
class Badge_Top5 extends Badge
{
const _BADGE_NAME = 'top5';
function try_award($position)
{
if ($position <= 5)
{
$this->award(self::_BADGE_NAME);
}
}
}
class Badge_Top5_Cron extends Badge_Top5
{
function cron_job($challenge_id = 0)
{
$where = '';
if ($challenge_id)
{
$escaped_challenge_id = mysql_real_escape_string($challenge_id);
$where = "WHERE challenge_id = '$escaped_challenge_id'";
}
$r = mysql_query("SELECT position, user_id
FROM challenge_entries
$where");
while ($obj = mysql_fetch_object($r))
{
$this->user_id = $obj->user_id; //award the correct user!
$this->try_award($obj->position);
}
}
即使未提供参数,cron函数仍然可以工作。
我已经实现了一个奖励系统,你可以称之为面向文档的数据库(这对玩家来说是个泥潭)。我的实现中的一些亮点,翻译为PHP和MySQL:
$achievments = 0; $bits = sprintf("%032b", $achievements); /* Set bit 10 */ $bits[10] = 1; $achievements = bindec($bits); print "Bits: $bits\n"; print "Achievements: $achievements\n"; /* Reload */ $bits = sprintf("%032b", $achievments); /* Set bit 5 */ $bits[5] = 1; $achievements = bindec($bits); print "Bits: $bits\n"; print "Achievements: $achievements\n";
UserInfuser是一个开源游戏化平台,它实现了徽章/积分服务。你可以在这里查看它的API:http://code.google.com/p/userinfuser/wiki/API_Documentation
我实现了它并试图保持最小的函数数量。这是php客户端的API:
class UserInfuser($account, $api_key)
{
public function get_user_data($user_id);
public function update_user($user_id);
public function award_badge($badge_id, $user_id);
public function remove_badge($badge_id, $user_id);
public function award_points($user_id, $points_awarded);
public function award_badge_points($badge_id, $user_id, $points_awarded, $points_required);
public function get_widget($user_id, $widget_type);
}
最终结果是通过使用小部件以有意义的方式显示数据。这些小部件包括:奖杯案例,排行榜,里程碑,实时通知,排名和积分。
API的实现可以在这里找到:http://code.google.com/p/userinfuser/source/browse/trunk/serverside/api/api.py
成就可能是繁重的,甚至更多,如果你必须在以后添加它们,除非你有一个格式良好的Event
类。
这就是我实现成就的技巧。
我喜欢先将它们分成“类别”,并在那些有成就的层次之内。即游戏中的kills
类别可以在1处获得第一次杀戮,10次杀戮,10万次杀戮等奖励。
然后到任何好应用程序的主干,处理事件的类。再想象一场杀人游戏;当玩家杀死某些东西时,会发生一些事情。注意杀戮等等,最好在集中的位置处理,比如和Events
类可以将信息发送到其他相关的地方。
它完全落在那里,在正确的方法中,实例化你的Achievements
类并检查它是否应该是玩家。
在构建Achievements
类时,它是微不足道的,只是检查数据库以查看玩家是否有下次成就所需的杀伤次数。
我喜欢使用Redis在BitField中存储用户的成就,但是在MySQL中可以使用相同的技术。也就是说,您可以将玩家的成就存储为int
,然后将and
存储在int中,并将其定义为该成就,以查看他们是否已获得该成就。这样它在数据库中只使用一个int
列。
这样做的缺点是你必须把它们组织得很好,你可能需要在你的代码中做一些评论,这样你才能记住2 ^ 14后来对应的内容。如果您的成就在他们自己的表中枚举,那么您可以只做2 ^ pk,其中pk
是成就表的主键。这使得检查就像
if(((2**$pk) & ($usersAchInt)) > 0){
// fire off the giveAchievement() event
}
通过这种方式,您可以在以后添加成就,它将与之相媲美,只是永远不会更改已经获得的成就的主键。