首页 文章

编码成就系统的最佳方式

提问于
浏览
85

我正在考虑设计一个在我的网站上使用的成就系统的最佳方法 . 数据库结构可以在Best way to tell 3 or more consecutive records missing找到,这个线程实际上是从开发人员那里获得想法的扩展 .

我在这个网站上有很多关于徽章/成就系统的讨论就是这个问题 - 这都是谈话而不是代码 . 实际的代码实现示例在哪里?

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

请随时提出您的想法 .


my system design idea

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

$event->trigger('POST_CREATED', array('id' => 8));

然后事件类找出这个事件的徽章是"listening",然后是 requires 那个文件,并创建该类的实例,如下所示:

require '/badges/' . $file;
$badge = new $class;

然后它调用默认事件传递调用 trigger 时收到的数据;

$badge->default_event($data);

the badges

这就是真正的魔法发生的地方 . 每个徽章都有自己的查询/逻辑,以确定是否应颁发徽章 . 每个徽章都在例如这种格式:

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表 . 徽章类还负责检索用户的所有徽章并将其返回到数组等中(因此徽章可以例如显示在用户配置文件上)

what about when the system is very first implemented on an already live site?

还有一个“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

我为此创建一个专门的查询的原因是虽然我们可以"simulate"之前的事件,即通过每个用户发布并触发事件类,如 $event->trigger() ,它会非常慢,特别是对于许多徽章 . 因此,我们改为创建优化查询 .

what user gets the award? all about awarding other users based on event

Badge class 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功能仍然可以工作 .

3 回答

  • 2

    我已经实现了一个奖励系统,你可以称之为面向文档的数据库(这对于玩家而言是一种泥潭) . 我的实现中的一些亮点,翻译为PHP和MySQL:

    • 徽章的每个细节都存储在用户数据中 . 如果您使用MySQL,我会确保此数据在数据库中的每个用户的一条记录中以提高性能 .

    • 每次有问题的人做某事时,代码会触发带有给定标志的徽章代码,例如flag('POST_MESSAGE') .

    • 一个事件也可以触发一个计数器,例如一个帖子数量 . increase_count( 'POST_MESSAGE') . 在这里,您可以检查(通过挂钩,或只是在此方法中进行测试),如果POST_MESSAGE计数大于300,那么您应该获得奖励徽章,例如:flag(“300_POST”) .

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

    • 在flag方法中,您还应该有用户以前的标志 . 所以你可以说当用户有FIRST_COMMENT,FIRST_POST,FIRST_READ你授予徽章(“新用户”),当你获得100_COMMENT,100_POST,300_READ时你可以授予徽章(“EXPERIENCED_USER”)

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

    $ achievments = 0;
    $ bits = sprintf(“%032b”,$ achievement);

    / *设置位10 * /
    $ bits [10] = 1;

    $ achievement = bindec($ bits);

    print“Bits:$ bits \ n”;
    打印“成就:$ achievement \ n”;

    / 重新加载 /

    $ bits = sprintf(“%032b”,$ achievments);

    / *设置位5 * /
    $ bits [5] = 1;

    $ achievement = bindec($ bits);

    print“Bits:$ bits \ n”;
    打印“成就:$ achievement \ n”;

    • 为用户存储文档的一种好方法是使用json并将用户数据存储在单个文本列中 . 使用json_encode和json_decode存储/检索数据 .

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

  • 0

    成就可能是繁重的,甚至更多,如果你必须在以后添加它们,除非你有一个格式良好的 Event 类 .

    这就是我实现成就的技巧 .

    我喜欢先将它们分成'categories',并在那些有成就的层次之内 . 即游戏中的 kills 类别可以在1处获得第一次杀戮,10次杀戮,10万次杀戮等奖励 .

    然后到任何好的应用程序的主干,处理您的事件的类 . 再想象一场杀人游戏;当玩家杀死某些东西时,会发生一些事情 . 注意杀戮等等,最好在集中的位置处理,比如和 Events 类可以将信息发送到其他相关的地方 .

    它完全落在那里,在正确的方法中,实例化你的 Achievements 类并检查它是否应该是玩家 .

    在构建 Achievements 类时,它是微不足道的,只是检查数据库以查看玩家是否具有下一个成就所需的杀伤次数 .

    我喜欢将用户's achievements in a BitField using Redis but the same technique can be used in MySQL. That is, you can store the player'的成就存储为 int ,然后将 and 存储为具有您已定义为该成就的位的int,以查看它们是否已获得它 . 这样它在数据库中只使用一个 int 列 .

    这样做的缺点是你必须把它们组织得很好,你可能需要在你的代码中做一些评论,这样你才能记住2 ^ 14后来对应的内容 . 如果您的成就在他们自己的表中枚举,那么您可以只做2 ^ pk,其中 pk 是成就表的主键 . 这使得检查就像

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

    通过这种方式,您可以在以后添加成就,它将与之相媲美,只是不要更改已经获得的成就的主键 .

  • 9

    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

相关问题