重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
godot-genre-fighting by thedivergentai/gd-agentic-skills
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-genre-fighting专注于帧级精确战斗和竞技平衡的 2D/3D 格斗游戏专家蓝图。
_physics_process)中,并在瞬移时调用 reset_physics_interpolation()。PhysicsDirectSpaceState.intersect_shape() 来即时查询碰撞框,避免 Area2D 信号延迟。Area2D.get_overlapping_areas();严格使用 intersect_shape() 进行即时、帧级精确的判定。广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
scale.x = -1)来翻转角色;严格调整专用的视觉节点,同时通过编程方式管理碰撞框偏移。ADVANCE_MANUAL 模式的 AnimationMixer 来实现帧同步播放。yield 或 await;严格在状态机内使用整数帧计数来完美管理硬直/启动窗口。Resource 文件 (.tres) 并配合委托逻辑来处理伤害缩放、取消和连击状态跟踪。Input.use_accumulated_input 启用;严格禁用它,以保留子帧时序,实现精确的连段衔接。StringName 比较的模式。中立博弈 → 确认命中 → 执行连段 → 优势状态 → 重复
godot-project-foundations, godot-characterbody-2d, godot-input-handling, animation, godot-combat-system, godot-state-machine-advanced, multiplayer-lobby
格斗游戏基于帧数据运行——离散的时间单位(通常为 60fps)。
class_name Attack
extends Resource
@export var name: String
@export var startup_frames: int # 碰撞框激活前的帧数
@export var active_frames: int # 碰撞框处于激活状态的帧数
@export var recovery_frames: int # 碰撞框失效后的帧数
@export var on_hit_advantage: int # 攻击命中时的帧优势
@export var on_block_advantage: int # 攻击被防御时的帧优势
@export var damage: int
@export var hitstun: int # 对手被击晕的帧数
@export var blockstun: int # 对手处于防御硬直的帧数
func get_total_frames() -> int:
return startup_frames + active_frames + recovery_frames
func is_safe_on_block() -> bool:
return on_block_advantage >= 0
extends Node
var frame_count: int = 0
const FRAME_DURATION := 1.0 / 60.0
var accumulator: float = 0.0
func _process(delta: float) -> void:
accumulator += delta
while accumulator >= FRAME_DURATION:
process_game_frame()
frame_count += 1
accumulator -= FRAME_DURATION
func process_game_frame() -> void:
# 所有游戏逻辑在此处以固定的 60fps 运行
for fighter in fighters:
fighter.process_frame()
存储输入并在有效时执行:
class_name InputBuffer
extends Node
const BUFFER_FRAMES := 8 # 行业标准:5-10 帧
var buffer: Array[InputEvent] = []
func add_input(input: InputEvent) -> void:
buffer.append(input)
if buffer.size() > BUFFER_FRAMES:
buffer.pop_front()
func consume_input(action: StringName) -> bool:
for i in range(buffer.size() - 1, -1, -1):
if buffer[i].is_action(action):
buffer.remove_at(i)
return true
return false
class_name MotionDetector
extends Node
const QCF := ["down", "down_forward", "forward"] # 前下前(Quarter Circle Forward)
const DP := ["forward", "down", "down_forward"] # 前下前(Dragon Punch)
const MOTION_WINDOW := 15 # 完成招式的帧数窗口
var direction_history: Array[String] = []
func add_direction(dir: String) -> void:
if direction_history.is_empty() or direction_history[-1] != dir:
direction_history.append(dir)
# 保留最近 N 个方向
if direction_history.size() > 20:
direction_history.pop_front()
func check_motion(motion: Array[String]) -> bool:
if direction_history.size() < motion.size():
return false
# 检查招式是否出现在最近的历史记录中
var recent := direction_history.slice(-MOTION_WINDOW)
return _contains_sequence(recent, motion)
func _contains_sequence(haystack: Array, needle: Array) -> bool:
var idx := 0
for dir in haystack:
if dir == needle[idx]:
idx += 1
if idx >= needle.size():
return true
return false
class_name HitboxComponent
extends Area2D
enum BoxType { HITBOX, HURTBOX, THROW, PROJECTILE }
@export var box_type: BoxType
@export var attack_data: Attack
@export var owner_fighter: Fighter
signal hit_confirmed(target: Fighter, attack: Attack)
func _ready() -> void:
monitoring = (box_type == BoxType.HITBOX or box_type == BoxType.THROW)
monitorable = (box_type == BoxType.HURTBOX)
connect("area_entered", _on_area_entered)
func _on_area_entered(area: Area2D) -> void:
if area is HitboxComponent:
var other := area as HitboxComponent
if other.box_type == BoxType.HURTBOX and other.owner_fighter != owner_fighter:
hit_confirmed.emit(other.owner_fighter, attack_data)
class_name ComboTracker
extends Node
var combo_count: int = 0
var combo_damage: int = 0
var in_combo: bool = false
var damage_scaling: float = 1.0
const SCALING_PER_HIT := 0.9 # 每次命中减少 10%
func start_combo() -> void:
in_combo = true
combo_count = 0
combo_damage = 0
damage_scaling = 1.0
func add_hit(base_damage: int) -> int:
combo_count += 1
var scaled_damage := int(base_damage * damage_scaling)
combo_damage += scaled_damage
damage_scaling *= SCALING_PER_HIT
return scaled_damage
func drop_combo() -> void:
in_combo = false
combo_count = 0
damage_scaling = 1.0
enum CancelType { NONE, NORMAL, SPECIAL, SUPER }
func can_cancel_into(from_attack: Attack, to_attack: Attack) -> bool:
# 普通技 → 必杀技 → 超必杀技 的层级关系
match to_attack.cancel_type:
CancelType.NORMAL:
return from_attack.cancel_type == CancelType.NONE
CancelType.SPECIAL:
return from_attack.cancel_type in [CancelType.NONE, CancelType.NORMAL]
CancelType.SUPER:
return true # 超必杀技可以取消任何招式
return false
enum FighterState {
IDLE, WALKING, CROUCHING, JUMPING,
ATTACKING, BLOCKING, HITSTUN, BLOCKSTUN,
KNOCKDOWN, WAKEUP, THROW, THROWN
}
class_name FighterStateMachine
extends Node
var current_state: FighterState = FighterState.IDLE
var state_frame: int = 0
func transition_to(new_state: FighterState) -> void:
exit_state(current_state)
current_state = new_state
state_frame = 0
enter_state(new_state)
func is_actionable() -> bool:
return current_state in [
FighterState.IDLE,
FighterState.WALKING,
FighterState.CROUCHING
]
class_name GameState
extends Resource
# 为回滚序列化完整的游戏状态
func save_state() -> Dictionary:
return {
"frame": frame_count,
"fighters": fighters.map(func(f): return f.serialize()),
"projectiles": projectiles.map(func(p): return p.serialize())
}
func load_state(state: Dictionary) -> void:
frame_count = state["frame"]
for i in fighters.size():
fighters[i].deserialize(state["fighters"][i])
# 重构抛射物...
| 元素 | 指南 |
|---|---|
| 生命值 | 10,000-15,000,对应约 20 秒一局 |
| 连段伤害 | 每次接触最大造成 30-40% 生命值伤害 |
| 最快招式 | 3-5 帧启动(轻攻击) |
| 最慢招式 | 20-40 帧(超必杀技、上段攻击) |
| 投技范围 | 短但可靠 |
| 能量槽增长 | 约承受 2 套连段后攒满一条 |
| 陷阱 | 解决方案 |
|---|---|
| 无限连 | 实现受击硬直衰减和重力缩放 |
| 无法防御的套路 | 确保所有攻击都有应对方法 |
| 延迟输入丢失 | 稳健的输入缓冲(8+ 帧) |
| 网络对战不同步 | 确定性物理、回滚网络代码 |
_physics_process - 实现你自己的基于帧的循环每周安装量
52
代码仓库
GitHub 星标数
59
首次出现
2026年2月10日
安全审计
安装于
opencode52
gemini-cli51
codex51
kimi-cli50
amp50
github-copilot50
Expert blueprint for 2D/3D fighters emphasizing frame-perfect combat and competitive balance.
_physics_process with a frame-counter) and call reset_physics_interpolation() on teleport.PhysicsDirectSpaceState.intersect_shape() to query hitboxes instantly without Area2D signal lag.Area2D.get_overlapping_areas(); strictly use intersect_shape() for immediate, frame-perfect resolution.scale.x = -1) for character flip; strictly adjust the dedicated Visuals node while managing hitbox offsets programmatically.AnimationMixer with ADVANCE_MANUAL for frame-synced playback.yield or await for frame-critical logic; strictly use Integer Frame Counting within state machines to manage recovery/startup windows perfectly.Resource files (.tres) with delegated logic for damage scaling, cancels, and combo-state tracking.Input.use_accumulated_input enabled; strictly disable it to preserve sub-frame timing for precise combo links.StringName comparisons in AI states.Neutral Game → Confirm Hit → Execute Combo → Advantage State → Repeat
godot-project-foundations, godot-characterbody-2d, godot-input-handling, animation, godot-combat-system, godot-state-machine-advanced, multiplayer-lobby
Fighting games operate on frame data - discrete time units (typically 60fps).
class_name Attack
extends Resource
@export var name: String
@export var startup_frames: int # Frames before hitbox becomes active
@export var active_frames: int # Frames hitbox is active
@export var recovery_frames: int # Frames after hitbox deactivates
@export var on_hit_advantage: int # Frame advantage when attack hits
@export var on_block_advantage: int # Frame advantage when blocked
@export var damage: int
@export var hitstun: int # Frames opponent is stunned
@export var blockstun: int # Frames opponent is in blockstun
func get_total_frames() -> int:
return startup_frames + active_frames + recovery_frames
func is_safe_on_block() -> bool:
return on_block_advantage >= 0
extends Node
var frame_count: int = 0
const FRAME_DURATION := 1.0 / 60.0
var accumulator: float = 0.0
func _process(delta: float) -> void:
accumulator += delta
while accumulator >= FRAME_DURATION:
process_game_frame()
frame_count += 1
accumulator -= FRAME_DURATION
func process_game_frame() -> void:
# All game logic runs here at fixed 60fps
for fighter in fighters:
fighter.process_frame()
Store inputs and execute when valid:
class_name InputBuffer
extends Node
const BUFFER_FRAMES := 8 # Industry standard: 5-10 frames
var buffer: Array[InputEvent] = []
func add_input(input: InputEvent) -> void:
buffer.append(input)
if buffer.size() > BUFFER_FRAMES:
buffer.pop_front()
func consume_input(action: StringName) -> bool:
for i in range(buffer.size() - 1, -1, -1):
if buffer[i].is_action(action):
buffer.remove_at(i)
return true
return false
class_name MotionDetector
extends Node
const QCF := ["down", "down_forward", "forward"] # Quarter Circle Forward
const DP := ["forward", "down", "down_forward"] # Dragon Punch
const MOTION_WINDOW := 15 # Frames to complete motion
var direction_history: Array[String] = []
func add_direction(dir: String) -> void:
if direction_history.is_empty() or direction_history[-1] != dir:
direction_history.append(dir)
# Keep last N directions
if direction_history.size() > 20:
direction_history.pop_front()
func check_motion(motion: Array[String]) -> bool:
if direction_history.size() < motion.size():
return false
# Check if motion appears in recent history
var recent := direction_history.slice(-MOTION_WINDOW)
return _contains_sequence(recent, motion)
func _contains_sequence(haystack: Array, needle: Array) -> bool:
var idx := 0
for dir in haystack:
if dir == needle[idx]:
idx += 1
if idx >= needle.size():
return true
return false
class_name HitboxComponent
extends Area2D
enum BoxType { HITBOX, HURTBOX, THROW, PROJECTILE }
@export var box_type: BoxType
@export var attack_data: Attack
@export var owner_fighter: Fighter
signal hit_confirmed(target: Fighter, attack: Attack)
func _ready() -> void:
monitoring = (box_type == BoxType.HITBOX or box_type == BoxType.THROW)
monitorable = (box_type == BoxType.HURTBOX)
connect("area_entered", _on_area_entered)
func _on_area_entered(area: Area2D) -> void:
if area is HitboxComponent:
var other := area as HitboxComponent
if other.box_type == BoxType.HURTBOX and other.owner_fighter != owner_fighter:
hit_confirmed.emit(other.owner_fighter, attack_data)
class_name ComboTracker
extends Node
var combo_count: int = 0
var combo_damage: int = 0
var in_combo: bool = false
var damage_scaling: float = 1.0
const SCALING_PER_HIT := 0.9 # 10% reduction per hit
func start_combo() -> void:
in_combo = true
combo_count = 0
combo_damage = 0
damage_scaling = 1.0
func add_hit(base_damage: int) -> int:
combo_count += 1
var scaled_damage := int(base_damage * damage_scaling)
combo_damage += scaled_damage
damage_scaling *= SCALING_PER_HIT
return scaled_damage
func drop_combo() -> void:
in_combo = false
combo_count = 0
damage_scaling = 1.0
enum CancelType { NONE, NORMAL, SPECIAL, SUPER }
func can_cancel_into(from_attack: Attack, to_attack: Attack) -> bool:
# Normal → Special → Super hierarchy
match to_attack.cancel_type:
CancelType.NORMAL:
return from_attack.cancel_type == CancelType.NONE
CancelType.SPECIAL:
return from_attack.cancel_type in [CancelType.NONE, CancelType.NORMAL]
CancelType.SUPER:
return true # Supers can cancel anything
return false
enum FighterState {
IDLE, WALKING, CROUCHING, JUMPING,
ATTACKING, BLOCKING, HITSTUN, BLOCKSTUN,
KNOCKDOWN, WAKEUP, THROW, THROWN
}
class_name FighterStateMachine
extends Node
var current_state: FighterState = FighterState.IDLE
var state_frame: int = 0
func transition_to(new_state: FighterState) -> void:
exit_state(current_state)
current_state = new_state
state_frame = 0
enter_state(new_state)
func is_actionable() -> bool:
return current_state in [
FighterState.IDLE,
FighterState.WALKING,
FighterState.CROUCHING
]
class_name GameState
extends Resource
# Serialize complete game state for rollback
func save_state() -> Dictionary:
return {
"frame": frame_count,
"fighters": fighters.map(func(f): return f.serialize()),
"projectiles": projectiles.map(func(p): return p.serialize())
}
func load_state(state: Dictionary) -> void:
frame_count = state["frame"]
for i in fighters.size():
fighters[i].deserialize(state["fighters"][i])
# Reconstruct projectiles...
| Element | Guideline |
|---|---|
| Health | 10,000-15,000 for ~20 second rounds |
| Combo damage | Max 30-40% of health per touch |
| Fastest moves | 3-5 frames startup (jabs) |
| Slowest moves | 20-40 frames (supers, overheads) |
| Throw range | Short but reliable |
| Meter gain | Full bar in ~2 combos received |
| Pitfall | Solution |
|---|---|
| Infinite combos | Implement hitstun decay and gravity scaling |
| Unblockable setups | Ensure all attacks have counterplay |
| Lag input drops | Robust input buffering (8+ frames) |
| Desync in netplay | Deterministic physics, rollback netcode |
_physics_process sparingly - implement your own frame-based loopWeekly Installs
52
Repository
GitHub Stars
59
First Seen
Feb 10, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode52
gemini-cli51
codex51
kimi-cli50
amp50
github-copilot50
TanStack Query v5 完全指南:React 数据管理、乐观更新、离线支持
2,500 周安装