1. 核心玩法设计
2026/4/14大约 11 分钟
1. 咸鱼之王——核心玩法
本章你将掌握
可以学到的 Godot 技能
| 技能 | 说明 |
|---|---|
| 自动战斗系统 | 用 Timer 节点驱动英雄自动攻击,无需玩家操作 |
| 离线收益计算 | 记录退出时间戳,重新进入时补发离线期间的金币 |
| 数值平衡设计 | 用公式控制升级费用和属性增长,保持游戏平衡 |
| 存档系统 | 用 FileAccess 将游戏数据序列化为 JSON 并持久化 |
| 信号系统 | 用 signal 解耦英雄升级、金币变化等事件通知 |
| UI 数据绑定 | 监听数值变化,实时刷新金币、等级、进度条等 UI |
| 对象池 | 复用敌人节点,避免频繁创建销毁造成性能抖动 |
| 资源文件(Resource) | 用 .tres 文件定义英雄、关卡的静态配置数据 |
关键 Node 节点
IdleFishKing(Node2D)
├── GameManager(Node) ← 全局管理器,持有金币/关卡状态
│ ├── IncomeTimer(Timer) ← 每秒触发一次,结算金币收益
│ └── SaveTimer(Timer) ← 定时自动存档
├── BattleScene(Node2D) ← 战斗场景
│ ├── HeroSlots(HBoxContainer) ← 英雄上阵位置
│ │ └── HeroSlot × 5(Node2D) ← 每个英雄的容器
│ │ ├── Sprite2D ← 英雄立绘
│ │ └── AttackTimer(Timer)← 该英雄的攻击计时器
│ └── EnemySpawner(Node2D) ← 敌人生成器(对象池)
├── HeroPanel(CanvasLayer) ← 英雄收集/升级面板
│ └── HeroList(ScrollContainer) ← 英雄列表
└── HUD(CanvasLayer) ← 游戏主界面
├── GoldLabel(Label) ← 金币显示
├── StageLabel(Label) ← 当前关卡
└── OfflineRewardPopup(Panel) ← 离线收益弹窗游戏核心节点
点击节点可跳转到对应文档查看详细说明。
游戏系统结构图
┌─────────────────────────────────────────────────────────────┐
│ 咸鱼之王 系统架构 │
├──────────────┬──────────────┬──────────────┬────────────────┤
│ 收益系统 │ 战斗系统 │ 英雄系统 │ 存档系统 │
│ │ │ │ │
│ IncomeTimer │ AttackTimer │ HeroData │ FileAccess │
│ 每秒结算 │ 自动攻击 │ 属性/技能 │ JSON序列化 │
│ │ │ │ │
│ 在线收益 │ 伤害计算 │ 升级系统 │ 自动存档 │
│ 离线补发 │ 敌人死亡 │ 收集抽卡 │ 离线时间戳 │
│ 倍率加成 │ 关卡推进 │ 装备强化 │ 数据恢复 │
└──────────────┴──────────────┴──────────────┴────────────────┘
↓ ↓ ↓
┌─────────────────────────────────────────────────────────────┐
│ UI 层 │
│ 金币显示 │ 关卡进度 │ 英雄面板 │ 离线奖励弹窗 │
└─────────────────────────────────────────────────────────────┘1.1 什么是放置挂机RPG?
想象你养了一只鱼。你不需要每天给它喂食、换水——它会自己长大、自己产生金币。你只需要偶尔打开手机看看,收一下金币,给鱼升个级,然后继续去做别的事。等下次再打开时,鱼又帮你赚了不少钱。
这就是放置挂机游戏(Idle Game)的核心体验——你不在的时候,游戏也在帮你赚钱。
"咸鱼之王"是一款融合了放置挂机和RPG元素的游戏。你收集各种英雄,组建队伍,让英雄自动打怪推图。即使你不在线,英雄们也在帮你战斗赚金币。
为什么选择放置游戏作为项目?
| 原因 | 说明 |
|---|---|
| 机制简单 | 核心循环清晰,代码逻辑不复杂 |
| 数据驱动 | 游戏体验主要由数值决定,适合学习数值设计 |
| 留存设计 | 放置游戏天然有"回来看看"的吸引力,可以学习留存策略 |
| 商业化成熟 | 放置游戏是最容易变现的游戏类型之一 |
1.2 核心循环
放置游戏的核心循环可以用一句话概括:挂机赚钱→升级英雄→推更多关卡→赚更多钱。
┌─────────────────────────────────────────────────────┐
│ │
│ 挂机打怪 → 自动获得金币 → 升级英雄 → 推更多关卡 │
│ ↑ │ │
│ └──────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘用大白话拆开来说:
- 挂机打怪:英雄们自动和当前关卡的怪物战斗,不需要你操作
- 自动收益:每秒产生金币,放在那不动也在赚钱
- 升级英雄:用金币提升英雄的攻击力和血量
- 推更多关卡:英雄变强了,就能打过更强的怪物,解锁新关卡
- 赚更多钱:关卡越高,每秒产出的金币越多
这就是一个正向飞轮——越玩越强,越强赚越多,赚越多越强。
1.3 放置游戏的关键机制
| 机制 | 说明 | 生活比喻 |
|---|---|---|
| 离线收益 | 关掉游戏后,回来时补发这段时间的金币 | 银行的利息 |
| 扫荡功能 | 打过的关卡可以一键扫荡获取奖励 | 快进看电视剧 |
| 自动战斗 | 英雄自动寻敌、自动攻击、自动释放技能 | 自动驾驶 |
| 英雄收集 | 不同的英雄有不同的技能和属性 | 收集 pokemon |
| 装备强化 | 给英雄穿装备、强化装备提升属性 | 给汽车换更好的零件 |
离线收益计算
离线收益是放置游戏的灵魂。计算方式通常是:
离线收益 = 每秒金币产出 x 离线秒数 x 离线效率离线效率通常是50%~100%——你不能和在线一样高效,但也不会完全没有收益。就像银行存款的利息,虽然不如你亲自去投资赚得多,但总比放着不动强。
每秒金币产出公式
每秒金币 = 基础金币 x (1 + 推图关卡数 x 0.1) x 英雄加成倍率| 因素 | 影响 | 举例 |
|---|---|---|
| 基础金币 | 游戏的固定基准值 | 10金/秒 |
| 推图关卡数 | 推得越远,产出越高 | 第50关 → 10 x (1+5) = 60金/秒 |
| 英雄加成 | 英雄等级越高,产出加成越大 | 英雄加成2倍 → 120金/秒 |
1.4 游戏数据结构
放置游戏的核心是数值。我们需要定义清楚英雄、关卡、装备等数据结构。
英雄数据
C
using Godot;
/// <summary>
/// 英雄稀有度
/// </summary>
public enum HeroRarity
{
Common, // 普通(白色)
Rare, // 稀有(绿色)
Epic, // 史诗(蓝色)
Legend // 传说(橙色)
}
/// <summary>
/// 英雄数据
/// </summary>
public struct HeroData
{
public string Id; // 英雄唯一ID
public string Name; // 英雄名称
public HeroRarity Rarity; // 稀有度
public int Level; // 当前等级
public int Attack; // 攻击力
public int MaxHp; // 最大生命值
public float AttackSpeed; // 攻击速度(次/秒)
public int SkillPower; // 技能威力
public string SkillName; // 技能名称
public string SkillDesc; // 技能描述
/// <summary>
/// 升级英雄——消耗金币提升属性
/// </summary>
public HeroData LevelUp()
{
var result = this;
result.Level += 1;
result.Attack += (int)(result.Attack * 0.15f); // 攻击+15%
result.MaxHp += (int)(result.MaxHp * 0.12f); // 血量+12%
result.SkillPower += (int)(result.SkillPower * 0.1f); // 技能+10%
return result;
}
/// <summary>
/// 计算升级所需金币
/// </summary>
public int GetUpgradeCost()
{
// 升级费用随等级递增
return 10 + Level * Level;
}
/// <summary>
/// 计算DPS(每秒伤害)
/// </summary>
public float GetDps()
{
return Attack * AttackSpeed;
}
}GDScript
## 英雄稀有度
enum HeroRarity {
COMMON, ## 普通(白色)
RARE, ## 稀有(绿色)
EPIC, ## 史诗(蓝色)
LEGEND ## 传说(橙色)
}
## 英雄数据
class_name HeroData
var id: String ## 英雄唯一ID
var name: String ## 英雄名称
var rarity: HeroRarity ## 稀有度
var level: int ## 当前等级
var attack: int ## 攻击力
var max_hp: int ## 最大生命值
var attack_speed: float ## 攻击速度(次/秒)
var skill_power: int ## 技能威力
var skill_name: String ## 技能名称
var skill_desc: String ## 技能描述
## 升级英雄——消耗金币提升属性
func level_up() -> HeroData:
var result = HeroData.new()
result.id = id
result.name = name
result.rarity = rarity
result.level = level + 1
result.attack = attack + int(attack * 0.15)
result.max_hp = max_hp + int(max_hp * 0.12)
result.attack_speed = attack_speed
result.skill_power = skill_power + int(skill_power * 0.1)
result.skill_name = skill_name
result.skill_desc = skill_desc
return result
## 计算升级所需金币
func get_upgrade_cost() -> int:
return 10 + level * level
## 计算DPS(每秒伤害)
func get_dps() -> float:
return attack * attack_speed关卡数据
C
/// <summary>
/// 关卡数据
/// </summary>
public struct StageData
{
public int StageId; // 关卡ID(从1开始)
public int Chapter; // 所属章节
public int StageInChapter; // 在章节中的序号(1~10)
public string Name; // 关卡名称
public int EnemyHp; // 怪物血量
public int EnemyAttack; // 怪物攻击力
public int GoldReward; // 通关金币奖励
public bool IsBoss; // 是否是Boss关
/// <summary>
/// 生成关卡数据(根据关卡ID自动计算难度)
/// </summary>
public static StageData Generate(int stageId)
{
int chapter = (stageId - 1) / 10 + 1;
int stageInChapter = (stageId - 1) % 10 + 1;
bool isBoss = stageInChapter == 10;
// 难度随关卡递增
float difficultyMult = 1f + (stageId - 1) * 0.2f;
return new StageData
{
StageId = stageId,
Chapter = chapter,
StageInChapter = stageInChapter,
Name = isBoss
? $"第{chapter}章 Boss"
: $"第{chapter}章 第{stageInChapter}关",
EnemyHp = isBoss
? (int)(200 * difficultyMult * 5)
: (int)(200 * difficultyMult),
EnemyAttack = (int)(15 * difficultyMult),
GoldReward = stageId * 20 + (isBoss ? 200 : 0),
IsBoss = isBoss
};
}
}GDScript
## 关卡数据
class_name StageData
var stage_id: int ## 关卡ID(从1开始)
var chapter: int ## 所属章节
var stage_in_chapter: int ## 在章节中的序号(1~10)
var name: String ## 关卡名称
var enemy_hp: int ## 怪物血量
var enemy_attack: int ## 怪物攻击力
var gold_reward: int ## 通关金币奖励
var is_boss: bool ## 是否是Boss关
## 生成关卡数据(根据关卡ID自动计算难度)
static func generate(stage_id: int) -> StageData:
var data = StageData.new()
data.stage_id = stage_id
data.chapter = (stage_id - 1) / 10 + 1
data.stage_in_chapter = (stage_id - 1) % 10 + 1
data.is_boss = data.stage_in_chapter == 10
var diff_mult: float = 1.0 + (stage_id - 1) * 0.2
data.name = ("第%d章 Boss" % data.chapter) if data.is_boss \
else ("第%d章 第%d关" % [data.chapter, data.stage_in_chapter])
data.enemy_hp = int(200 * diff_mult * 5) if data.is_boss \
else int(200 * diff_mult)
data.enemy_attack = int(15 * diff_mult)
data.gold_reward = stage_id * 20 + (200 if data.is_boss else 0)
return data1.5 游戏状态管理
放置游戏的状态比较简单,主要就是"在线"和"离线"两种。
┌──────────┐ 打开游戏 ┌──────────┐
│ 离线状态 │ ────────────→ │ 在线状态 │
└──────────┘ └────┬─────┘
↑ │
│ 关闭游戏 │
│ ←────────────────── │
│ │
│ 进入战斗 │
│ ┌────────┐ │
└──── │ 战斗中 │ ←────────┘
└────────┘1.6 收益倍率系统
为了让游戏更有策略性,我们设计一些可以提升收益倍率的因素:
| 倍率来源 | 效果 | 获取方式 |
|---|---|---|
| 在线时长 | 每在线10分钟,收益+5%(上限50%) | 持续在线 |
| 英雄等级 | 英雄每升10级,金币产出+10% | 升级英雄 |
| 装备加成 | 装备上有金币加成属性 | 装备强化 |
| 签到奖励 | 每日签到获得临时倍率buff | 每日登录 |
C
/// <summary>
/// 收益计算器——计算当前的金币产出
/// </summary>
public static class IncomeCalculator
{
/// <summary>
/// 基础每秒金币(根据推图进度)
/// </summary>
public static int GetBaseIncomePerSecond(int highestStage)
{
return 5 + highestStage * 2;
}
/// <summary>
/// 计算综合每秒金币产出
/// </summary>
public static float CalculateIncomePerSecond(
int highestStage,
float heroBonus,
float onlineTimeBonus,
float equipBonus)
{
int baseIncome = GetBaseIncomePerSecond(highestStage);
float totalMultiplier = 1f + heroBonus + onlineTimeBonus + equipBonus;
return baseIncome * totalMultiplier;
}
/// <summary>
/// 计算离线收益
/// </summary>
public static int CalculateOfflineIncome(
int highestStage,
float heroBonus,
double offlineSeconds,
float offlineEfficiency = 0.5f)
{
float incomePerSec = CalculateIncomePerSecond(
highestStage, heroBonus, 0f, 0f);
// 离线效率(最多按8小时计算)
double maxOfflineSeconds = 8 * 3600;
double effectiveSeconds = Mathf.Min(offlineSeconds, maxOfflineSeconds);
return (int)(incomePerSec * effectiveSeconds * offlineEfficiency);
}
}GDScript
## 收益计算器——计算当前的金币产出
class_name IncomeCalculator
## 基础每秒金币(根据推图进度)
static func get_base_income_per_second(highest_stage: int) -> int:
return 5 + highest_stage * 2
## 计算综合每秒金币产出
static func calculate_income_per_second(
highest_stage: int,
hero_bonus: float,
online_time_bonus: float,
equip_bonus: float
) -> float:
var base_income: int = get_base_income_per_second(highest_stage)
var total_multiplier: float = 1.0 + hero_bonus + online_time_bonus + equip_bonus
return float(base_income) * total_multiplier
## 计算离线收益
static func calculate_offline_income(
highest_stage: int,
hero_bonus: float,
offline_seconds: float,
offline_efficiency: float = 0.5
) -> int:
var income_per_sec: float = calculate_income_per_second(
highest_stage, hero_bonus, 0.0, 0.0)
# 离线效率(最多按8小时计算)
var max_offline_seconds: float = 8.0 * 3600.0
var effective_seconds: float = minf(offline_seconds, max_offline_seconds)
return int(income_per_sec * effective_seconds * offline_efficiency)1.7 本章小结
在这一章中,我们分析了咸鱼之王这款放置挂机RPG的核心设计:
| 知识点 | 说明 |
|---|---|
| 核心循环 | 挂机打怪→自动收益→升级英雄→推更多关卡 |
| 离线收益 | 关闭游戏后回来补发金币(50%效率,最多8小时) |
| 英雄数据 | 稀有度、属性、技能、升级费用 |
| 关卡数据 | 按章节组织,难度递增,每章10关+1Boss |
| 收益倍率 | 在线时长、英雄等级、装备加成等影响金币产出 |
| 数值设计 | 所有数值都可以用公式自动生成,方便调整平衡 |
下一章我们将正式搭建项目结构,创建自动存档和全局管理器。
