You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP实现游戏服务器端定时添加木材功能的技术求助

嘿,我来帮你解决这个问题!你现在的写法有几个关键问题,导致页面一直加载,而且根本没法正常工作——咱们一步步来理顺:

为什么你的当前代码会出问题?

你写的递归+sleep()的方式完全不符合PHP的运行逻辑:

  • sleep()是同步阻塞的:一旦调用,整个PHP进程会挂起10秒,期间不会响应任何请求,页面自然会一直处于加载状态,直到超时或者栈溢出。
  • 无限递归会耗尽内存:每次调用Criar_Madeira()都会在内存里留下调用栈,次数多了直接触发致命错误,进程崩溃。
  • PHP请求是短生命周期的:用户的HTTP请求结束后,PHP进程就会销毁,根本没法持续运行无限循环的定时任务。
正确的实现思路

服务器端处理玩家资源的周期性增长,核心是不要在用户请求里跑持续任务,而是用以下两种思路:

  1. 基于用户交互触发计算(最常用,适合Web游戏):记录最后一次资源更新的时间,每次用户打开页面/发起请求时,计算这段时间应该增长的资源量,一次性补上。
  2. 用独立的定时任务/常驻进程:如果需要玩家离线时也增长资源,就用系统定时任务或者异步进程来批量更新。
具体解决方案

方案一:基于用户交互的延迟计算(推荐)

这个方案不需要后台一直跑进程,安全又高效,而且所有计算都在服务器端,客户端没法篡改。

实现步骤:

  1. 在玩家数据库表中添加一个字段,比如ultima_atualizacao_madeira,用来记录最后一次更新木材的时间戳。
  2. 每次用户打开游戏页面、发起资源相关请求时,执行以下代码:
function atualizarMadeira($pdo, $playerId) {
    // 获取当前时间戳
    $agora = time();
    
    // 从数据库获取玩家的当前木材和最后更新时间
    $stmt = $pdo->prepare("SELECT madeira, ultima_atualizacao_madeira FROM jogadores WHERE id = ?");
    $stmt->execute([$playerId]);
    $player = $stmt->fetch(PDO::FETCH_ASSOC);
    
    if (!$player) {
        return; // 玩家不存在,直接返回
    }
    
    // 计算从上次更新到现在过去了多少秒
    $tempoDecorrido = $agora - $player['ultima_atualizacao_madeira'];
    
    // 每10秒加500木材,计算有多少个完整的10秒周期
    $periodosValidos = floor($tempoDecorrido / 10);
    
    if ($periodosValidos > 0) {
        // 计算应该添加的木材总量
        $madeiraAdicional = $periodosValidos * 500;
        
        // 更新数据库:添加木材,同时把最后更新时间设为当前时间
        $updateStmt = $pdo->prepare("UPDATE jogadores SET madeira = madeira + ?, ultima_atualizacao_madeira = ? WHERE id = ?");
        $updateStmt->execute([$madeiraAdicional, $agora, $playerId]);
    }
}

// 调用示例(假设已经有PDO连接实例$pdo,玩家ID是$a[1])
atualizarMadeira($pdo, $a[1]);

这样不管玩家隔多久再来,都会自动补上这段时间应该获得的木材,完全不需要后台持续运行任务。

方案二:系统定时任务(Crontab)

如果需要玩家离线时也持续增长木材,可以写一个独立的PHP脚本,用系统的Crontab定时执行。

步骤:

  1. 写一个批量更新所有玩家木材的脚本(比如update_madeira.php):
<?php
// 连接数据库
try {
    $pdo = new PDO('mysql:host=localhost;dbname=seu_banco_de_dados', 'seu_usuario', 'sua_senha');
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    
    // 给所有玩家添加500木材
    $sql = "UPDATE jogadores SET madeira = madeira + 500";
    $pdo->exec($sql);
    
    echo "木材更新完成\n";
} catch (PDOException $e) {
    die("数据库错误: " . $e->getMessage());
}
?>
  1. 配置Crontab,每10秒执行一次这个脚本:
    因为Crontab最小的分钟级间隔是1分钟,要实现10秒一次,需要添加6条任务(每分钟内每10秒执行一次):
* * * * * php /caminho/completo/para/update_madeira.php
* * * * * sleep 10; php /caminho/completo/para/update_madeira.php
* * * * * sleep 20; php /caminho/completo/para/update_madeira.php
* * * * * sleep 30; php /caminho/completo/para/update_madeira.php
* * * * * sleep 40; php /caminho/completo/para/update_madeira.php
* * * * * sleep 50; php /caminho/completo/para/update_madeira.php

方案三:用Swoole实现常驻进程定时器

如果你的服务器支持Swoole(PHP的异步扩展),可以写一个常驻进程,用定时器每隔10秒更新一次木材。这个方案适合需要实时更新的场景,但需要额外的进程管理。

示例代码:

<?php
// 创建Swoole服务器(这里只是用来启动定时器,不需要监听端口也可以)
$server = new Swoole\Server('127.0.0.1', 9501);

// 进程启动时初始化定时器
$server->on('WorkerStart', function ($server) {
    // 每隔10秒(10000毫秒)执行一次更新任务
    Swoole\Timer::tick(10000, function () {
        try {
            $pdo = new PDO('mysql:host=localhost;dbname=seu_banco_de_dados', 'seu_usuario', 'sua_senha');
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            
            $sql = "UPDATE jogadores SET madeira = madeira + 500";
            $pdo->exec($sql);
            
            echo date('Y-m-d H:i:s') . " - 木材更新完成\n";
        } catch (PDOException $e) {
            echo date('Y-m-d H:i:s') . " - 错误: " . $e->getMessage() . "\n";
        }
    });
});

// 启动服务器
$server->start();

启动这个脚本后,它会一直运行,每隔10秒自动更新所有玩家的木材。你可以用nohup或者守护进程工具来保证它持续运行。

总结
  • 绝对不要在用户请求里用sleep()和递归,这完全不符合Web环境的运行逻辑。
  • 优先用方案一,实现简单、安全,不需要额外的服务器配置,适合绝大多数Web游戏。
  • 如果需要离线增长资源,再考虑方案二或三。

内容的提问来源于stack exchange,提问作者VilmaH

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:54:38