编写成就系统的最佳方法


85

我正在考虑设计一种在我的网站上使用的成就系统的最佳方法。数据库结构可以以最佳方式找到,以告知缺少的3个或更多连续记录,并且该线程实际上是从开发人员处获得想法的扩展。

我在这个网站上经常谈论徽章/成就系统时遇到的问题就是-都是谈论而没有代码。实际的代码实现示例在哪里?

我在这里提出一种设计,希望人们可以做出贡献,并希望为编码可扩展成就系统创建一个好的设计。我并不是说这是最好的,远非如此,但这是一个可能的起点。

请随时贡献您的想法。


我的系统设计思路

似乎普遍的共识是创建一个“基于事件的系统”-每当发生已知事件(如创建,删除帖子等)时,它都会像这样调用事件类。

$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类扩展了主要的badge类,它可以重新使用逻辑功能 try_award

我为此创建专门查询的原因是,尽管我们可以“模拟”以前的事件,即遍历每个用户帖子并触发事件类,因为$event->trigger()这会非常慢,尤其是对于许多徽章。因此,我们改为创建优化的查询。

什么用户获得奖励?关于根据事件奖励其他用户

Badgeaward功能作用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功能仍将起作用。



2
它是相关的,但不是重复的。请阅读第二段。“在这个网站上,关于徽章/成就系统的讨论很多,我遇到的问题就是-全部都是讨论,没有代码。实际的代码实现示例在哪里?”
加里·格林

1
好吧,编写工作代码仅在一定程度上是可行的。我想说,一旦任何实现都过于复杂,人们只给您理论知识是很正常的。
戈登

Answers:


9

我曾经在您称为面向文档的数据库中实施了奖励系统(这对玩家来说是泥泞的)。从我的实现中转换为PHP和MySQL的一些要点:

  • 有关徽章的每个详细信息都存储在用户数据中。如果使用MySQL,我将确保该数据在数据库中每个用户的一条记录中以提高性能。

  • 每当有问题的人做某事时,代码都会使用给定的标志(例如flag('POST_MESSAGE'))触发徽章代码。

  • 一个事件也可能触发计数器,例如帖子数的计数。增加计数('POST_MESSAGE')。在这里,您可以检查(通过钩子,或仅通过此方法进行测试),如果POST_MESSAGE计数> 300,那么您应该奖励徽章,例如:flag(“ 300_POST”)。

  • 在标志方法中,我将代码用于奖励徽章。例如,如果发送了标志300_POST,则应调用徽章reward_badge(“ 300_POST”)。

  • 在标志方法中,还应使用户具有以前的标志。因此您可以说,当用户拥有FIRST_COMMENT,FIRST_POST,FIRST_READ时,您授予徽章(“ NEW USER”),而当您获得100_COMMENT,100_POST,300_READ时,您可以授予徽章(“ EXPERIENCED_USER”)

  • 所有这些标志和徽章都需要以某种方式存储。使用某种方式将标志视为位。如果要真正有效地存储它,可以将它们视为位,并使用下面的代码:(或者,如果不想这种复杂性,可以只使用裸字符串“ 000000001111000”。

$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";
  • 为用户存储文档的一种好方法是使用json并将用户数据存储在单个文本列中。使用json_encode和json_decode来存储/检索数据。

  • 要跟踪由其他用户操纵的某些用户数据的活动,请在项目上添加数据结构,并在其中使用计数器。例如读取计数。使用与上述授予徽章相同的技术,但是更新当然应该在拥有用户的帖子中。(例如,文章阅读了1000次徽章)。


1
徽章系统的经典趋势是为表中的新统计信息添加一个新字段。对我来说,这似乎是一个简单的解决方法,但不是一个好主意,因为您存储的镜像数据可以从表中已有的数据计算得出(也许是简单的COUNT(),在MyISAM表上非常快)准确)。如果性能是您的目标,则需要进行更新并选择以获取当前的值,例如post_count值,以检查是否应授予徽章。您可能只需要一个查询COUNT(*)。我同意了更复杂的数据将有充分的理由来添加一个字段虽然
加里·格林

5
@Gary Green这不仅是一种简便的方法,而且还是可扩展的方法,并且与文档数据库兼容。至于正确性,您是对的,尽管对于徽章系统,我宁愿它快速且最有可能正确,而不是100%正确和缓慢。一次计数可能很快,但是当您的系统扩展且拥有大量用户时,就无法确定该策略是否成立。
努博

1
我喜欢仅具有徽章定义表和将用户链接到徽章及其当前进度的链接表的想法。这样做时,noSQL会锁定您当时的任何模式,并且在突然发现徽章中的错别字或添加了1000个新徽章时将无法维护。您总是可以使用批处理过程将它们缓存到更多文档存储中,以便快速检索,但我会保持联系。
FlavorScape 2015年

2

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


1
这是基于PHP的吗?问题基于PHP
Lenin Raj Rajasekaran

1
它具有PHP绑定,但是服务器端代码是用Python编写的。
Navraj Chohan 2011年

0

成就可能很繁重,甚至更大,因此如果您以后必须添加它们,除非您的Event课程结构良好。

这成为我实现成就的技巧。

我喜欢先将它们分为“类别”,在其中具有一定的成就感。也就是说kills,游戏中的某个类别可能在第一次击杀,10次击杀,10万次击杀等方面获得1分的奖励。

然后到任何好的应用程序的脊梁,处理事件的类。再次想象一场带有杀戮的游戏;当玩家杀死某物时,就会发生事情。记录下杀死对象,等等,最好在集中的位置进行处理,例如Events可以将信息发送到其他相关位置的类。

它完美地落在那儿,以正确的方法实例化您的Achievements课程,并检查玩家是否应得。

在建立Achievements类的过程中,它是微不足道的,只是可以检查数据库以查看玩家是否具有下一个成就所需的杀死数量的东西。

我喜欢使用Redis将用户的成就存储在BitField中,但是可以在MySQL中使用相同的技术。也就是说,您可以将玩家的成就存储为int,然后将and其与您定义为该成就的位一起存储,以查看他们是否已经获得了成就。这样,它仅使用int数据库中的单个列。

缺点是您必须将它们组织得井井有条,并且您可能需要在代码中添加一些注释,以便稍后记住2 ^ 14对应的内容。如果您的成就是在自己的表中枚举的,那么您只需做2 ^ pk,这pk是成就表的主键。这使得检查像

if(((2**$pk) & ($usersAchInt)) > 0){
  // fire off the giveAchievement() event 
} 

这样,您以后可以添加成就,并且可以很好地吻合,只不过不要更改已经授予的成就的主键。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.