3. 挂机收益
2026/4/14大约 4 分钟
3. 咸鱼之王——挂机收益
3.1 什么是挂机收益?
想象你开了一家奶茶店。你雇了一个店员,他每小时帮你卖出10杯奶茶,每杯赚10块钱。你去睡觉了8个小时,醒来一看,店员帮你赚了800块钱——这就是挂机收益。
在放置游戏中,挂机收益就是"即使你不操作,游戏也在帮你赚钱"的机制。这是放置游戏最核心的吸引力。
3.2 收益管理器
IncomeManager 负责计算和显示当前的每秒金币产出、离线收益、在线时长奖励等。
C
using Godot;
/// <summary>
/// 收益管理器——管理金币产出和离线收益
/// </summary>
public partial class IncomeManager : Node
{
// UI引用
private Label _incomePerSecondLabel;
private Label _goldLabel;
private Label _onlineBonusLabel;
private ProgressBar _onlineBonusBar;
// 当前每秒金币
private float _currentIncomePerSecond = 0;
public float CurrentIncomePerSecond => _currentIncomePerSecond;
public override void _Ready()
{
_incomePerSecondLabel = GetNode<Label>("%IncomePerSecondLabel");
_goldLabel = GetNode<Label>("%GoldLabel");
_onlineBonusLabel = GetNode<Label>("%OnlineBonusLabel");
_onlineBonusBar = GetNode<ProgressBar>("%OnlineBonusBar");
// 监听金币变化
GameManager.Instance.GoldChanged += OnGoldChanged;
UpdateIncomeDisplay();
}
public override void _Process(double delta)
{
UpdateIncomeDisplay();
}
/// <summary>
/// 更新收益显示
/// </summary>
private void UpdateIncomeDisplay()
{
var gm = GameManager.Instance;
// 计算每秒金币
_currentIncomePerSecond = IncomeCalculator.CalculateIncomePerSecond(
gm.HighestStage,
gm.HeroBonusMultiplier(),
gm.GetOnlineTimeBonus(),
0f);
// 更新UI
_incomePerSecondLabel.Text = $"{_currentIncomePerSecond:F1} 金/秒";
_goldLabel.Text = FormatGold(gm.Gold);
// 在线时长奖励进度
float bonus = gm.GetOnlineTimeBonus();
float bonusPercent = bonus * 100;
_onlineBonusLabel.Text = $"在线加成: +{bonusPercent:F0}%";
_onlineBonusBar.Value = bonus / IdleConstants.MaxOnlineBonus * 100;
}
/// <summary>
/// 格式化金币显示(大数字简化)
/// </summary>
private string FormatGold(int gold)
{
if (gold >= 1_000_000)
return $"{gold / 1_000_000f:F1}M";
if (gold >= 1_000)
return $"{gold / 1_000f:F1}K";
return gold.ToString();
}
private void OnGoldChanged(int newGold)
{
_goldLabel.Text = FormatGold(newGold);
}
/// <summary>
/// 显示离线收益弹窗
/// </summary>
public void ShowOfflineIncomePopup(int gold, double offlineSeconds)
{
string timeStr = FormatOfflineTime(offlineSeconds);
// 弹出离线收益面板
var popup = GetNode<Control>("%OfflineIncomePopup");
popup.Visible = true;
var timeLabel = popup.GetNode<Label>("TimeLabel");
var goldLabel = popup.GetNode<Label>("GoldLabel");
var collectButton = popup.GetNode<Button>("CollectButton");
timeLabel.Text = $"离线时间: {timeStr}";
goldLabel.Text = $"获得: {FormatGold(gold)} 金币";
// 点击"领取"按钮后关闭弹窗
collectButton.Pressed += () =>
{
popup.Visible = false;
};
}
/// <summary>
/// 格式化离线时间
/// </summary>
private string FormatOfflineTime(double seconds)
{
int hours = (int)(seconds / 3600);
int minutes = (int)((seconds % 3600) / 60);
if (hours > 0)
return $"{hours}小时{minutes}分钟";
return $"{minutes}分钟";
}
}GDScript
extends Node
## 收益管理器——管理金币产出和离线收益
@onready var _income_label: Label = %IncomePerSecondLabel
@onready var _gold_label: Label = %GoldLabel
@onready var _online_bonus_label: Label = %OnlineBonusLabel
@onready var _online_bonus_bar: ProgressBar = %OnlineBonusBar
var _current_income_per_second: float = 0
func _ready() -> void:
GameManager.gold_changed.connect(_on_gold_changed)
_update_income_display()
func _process(delta: float) -> void:
_update_income_display()
## 更新收益显示
func _update_income_display() -> void:
var gm = GameManager
# 计算每秒金币
_current_income_per_second = IncomeCalculator.calculate_income_per_second(
gm.highest_stage,
gm._hero_bonus_multiplier(),
gm._get_online_time_bonus(),
0.0
)
# 更新UI
_income_label.text = "%.1f 金/秒" % _current_income_per_second
_gold_label.text = _format_gold(gm.gold)
# 在线时长奖励
var bonus: float = gm._get_online_time_bonus()
var bonus_percent: float = bonus * 100
_online_bonus_label.text = "在线加成: +%.0f%%" % bonus_percent
_online_bonus_bar.value = bonus / IdleConstants.MAX_ONLINE_BONUS * 100
## 格式化金币显示
func _format_gold(gold_amount: int) -> String:
if gold_amount >= 1_000_000:
return "%.1fM" % (gold_amount / 1000000.0)
if gold_amount >= 1_000:
return "%.1fK" % (gold_amount / 1000.0)
return str(gold_amount)
func _on_gold_changed(new_gold: int) -> void:
_gold_label.text = _format_gold(new_gold)
## 显示离线收益弹窗
func show_offline_income_popup(gold: int, offline_seconds: float) -> void:
var time_str = _format_offline_time(offline_seconds)
var popup = get_node("%OfflineIncomePopup")
popup.visible = true
popup.get_node("TimeLabel").text = "离线时间: %s" % time_str
popup.get_node("GoldLabel").text = "获得: %s 金币" % _format_gold(gold)
## 格式化离线时间
func _format_offline_time(seconds: float) -> String:
var hours: int = int(seconds / 3600)
var minutes: int = int((seconds % 3600) / 60)
if hours > 0:
return "%d小时%d分钟" % [hours, minutes]
return "%d分钟" % minutes3.3 金币飘字效果
当玩家获得金币时,屏幕上飘出"+100"这样的文字,给玩家正面的反馈。
C
using Godot;
/// <summary>
/// 金币飘字——在指定位置显示"+xxx 金币"
/// </summary>
public partial class GoldFloatText : Node2D
{
private Label _label;
public override void _Ready()
{
_label = GetNode<Label>("Label");
// 2秒后自动消失
var timer = GetTree().CreateTimer(2.0);
timer.Timeout += QueueFree;
}
/// <summary>
/// 在指定位置显示飘字
/// </summary>
public void Show(Vector2 position, int amount)
{
GlobalPosition = position;
_label.Text = $"+{amount}";
// 向上飘动 + 淡出
var tween = CreateTween();
tween.TweenProperty(this, "position:y", -60, 1.5f);
tween.Parallel().TweenProperty(_label, "modulate:a", 0f, 1.5f);
}
}GDScript
extends Node2D
## 金币飘字——在指定位置显示"+xxx 金币"
@onready var _label: Label = $Label
func _ready() -> void:
# 2秒后自动消失
await get_tree().create_timer(2.0).timeout
queue_free()
## 在指定位置显示飘字
func show(position: Vector2, amount: int) -> void:
global_position = position
_label.text = "+%d" % amount
# 向上飘动 + 淡出
var tween = create_tween()
tween.tween_property(self, "position:y", -60.0, 1.5)
tween.parallel().tween_property(_label, "modulate:a", 0.0, 1.5)3.4 收益面板UI
┌──────────────────────────────────┐
│ 金 币: 12,350 │
│ │
│ 每秒收入: 25.3 金/秒 │
│ │
│ ┌──────────────────────────┐ │
│ │ 在线加成: +25% │ │
│ │ ████████████░░░░░░░░░░░ │ │
│ │ 再等10分钟加成提升至+30% │ │
│ └──────────────────────────┘ │
│ │
│ ┌──────────────────────────┐ │
│ │ 离线收益 │ │
│ │ 离线时间: 3小时25分钟 │ │
│ │ 获得金币: 4,580 │ │
│ │ [ 领 取 ] │ │
│ └──────────────────────────┘ │
└──────────────────────────────────┘3.5 大数字格式化
当金币数量很大时(比如1,234,567),直接显示太长了。我们用简化格式:
| 实际数值 | 显示格式 | 说明 |
|---|---|---|
| 500 | 500 | 直接显示 |
| 1,200 | 1.2K | K = 千(1,000) |
| 45,600 | 45.6K | 保留一位小数 |
| 1,500,000 | 1.5M | M = 百万(1,000,000) |
| 999,999,999 | 1.0B | B = 十亿(1,000,000,000) |
3.6 本章小结
| 知识点 | 说明 |
|---|---|
| 收益管理器 | 实时计算每秒金币,显示在UI上 |
| 在线时长奖励 | 每在线10分钟增加5%收益,上限50% |
| 离线收益弹窗 | 打开游戏时弹出离线收益面板 |
| 金币飘字 | 获得金币时显示飘字动画 |
| 大数字格式化 | K/M/B简写,保持界面整洁 |
下一章我们将实现英雄收集系统——放置游戏的另一核心。
