代码之家  ›  专栏  ›  技术社区  ›  Gary Green

编码成就系统的最佳方法

  •  85
  • Gary Green  · 技术社区  · 15 年前

    我在想最好的方法来设计一个成就系统,在我的网站上使用。数据库结构可以在 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 它基本上检查用户是否已经获得了徽章,如果没有,将更新徽章数据库表。徽章类还负责检索用户的所有徽章并将其以数组形式返回,等等(因此徽章可以显示在用户配置文件上)

    当系统第一次在一个已经存在的站点上实现时呢?

    还有一个“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() 会很慢,特别是对于很多徽章。所以我们创建了一个优化的查询。

    哪个用户获奖?关于奖励 其他 基于事件的用户

    这个 徽章 作用于 user_id --他们将永远获得这个奖。默认情况下,徽章授予导致事件发生的人,即会话用户ID(对于 default_event 函数,尽管cron作业显然会循环遍历所有用户并授予单独的用户)

    所以让我们举个例子,在一个编码挑战网站上,用户提交他们的编码条目。管理员然后判断条目,完成后,将结果发布到challenge页面供所有人查看。发生这种情况时,将调用posted_results事件。

    如果您想为所有发布的条目授予用户徽章,比方说,如果他们排名在前5名之内,那么您应该使用cron作业(尽管请记住,这将为所有用户更新,而不仅仅是为发布结果的挑战更新)

    如果您希望使用cron job更新更具体的区域,那么让我们看看是否有方法将筛选参数添加到cron job对象中,并让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 回复  |  直到 9 年前
        1
  •  9
  •   Case    12 年前

    我曾经在你称之为面向文档的数据库中实现了奖励系统(这对玩家来说是一个烂泥)。我的实现中的一些亮点,翻译成了php和mysql:

    • 徽章的每个细节都存储在用户数据中。如果您使用mysql,为了提高性能,我会确保每个用户在数据库中都有一条记录。

    • 每次有问题的人做某事时,代码都会触发带有给定标志的徽章代码,例如flag(“post_message”)。

    • 一个事件还可以触发计数器,例如一个帖子数的计数。增加“邮件”计数。在这里,您可以有一个检查(通过一个钩子,或者在这个方法中进行一个测试),如果post_消息计数为300,那么您应该有一个奖章,例如:flag(“300_post”)。

    • 在旗帜法中,我会把密码放在奖章上。例如,如果发送标志300_post,则应调用徽章奖励徽章(“300_post”)。

    • 在flag方法中,还应该存在用户以前的标志。所以你可以说当用户有FrimtSytRead、FrestStPoST、FrestStRead给你颁发徽章(“新用户”)时,当你得到100i评论、100iPoST、300×Read时,你可以授予徽章(“ExeXeDeDuxAuthor”)。

    • 所有这些旗帜和徽章都需要以某种方式储存起来。用某种方式,你认为标志是位。如果你想有效地存储它,你可以把它们看作是位,并使用下面的代码:(或者,如果你不想这样复杂,你可以使用一个裸字符串“0000000011111000”。

    $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遍徽章)。

        2
  •  2
  •   Martin Bean    12 年前

    userinuser是一个开源游戏化平台,它实现了一个bading/points服务。你可以在这里查看它的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

        3
  •  0
  •   NappingRabbit    8 年前

    除非你有一个良好的结构,否则你必须在以后再加上这些成果。 Event 班级。

    这就涉及到我实现成就的技巧。

    我喜欢先把它们分为“类别”,并且在这些类别中有层次的成就。即A kills 一个游戏中的类别可能有一个奖励1的第一杀,10十杀,10万杀等。

    然后到任何一个好的应用程序的脊椎,处理你的事件的类。再一次想象一场有杀戮的游戏;当一个玩家杀戮某物时,事情就会发生。杀戮被记录在案,最好在一个集中的地方处理,比如 Events 可以将信息发送到其他地方的类。

    在那里它完全就位了,在适当的方法中,实例化 Achievements 下课,检查一下,这名球员该上场了。

    在建造 成就 类它很简单,只是用来检查数据库,看看玩家是否拥有下一个成就所需的杀伤力。

    我喜欢使用RIDIS将用户的成就存储在位字段中,但同样的技术可以在MySQL中使用。也就是说,你可以把玩家的成就储存起来。 int 然后 and 你所定义的那一点智力成就,看看他们是否已经获得了它。这样它只用一个 int 数据库中的列。

    这样做的缺点是你必须把它们组织得很好,而且你可能需要在代码中添加一些注释,这样你以后就会记住2^14对应的是什么。如果你的成绩被列举在自己的表中,那么你可以做2 ^ pk pk 是成绩表的主键。这使得支票看起来像

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

    这样,你就可以在以后再加上成绩,这将是很好的,只是永远不会改变已经授予的成绩的主键。

    推荐文章