Input.action_release
2026/4/14大约 7 分钟
最后同步日期:2026-04-15 | Godot 官方原文 — Input.action_release
Input.action_release
定义
想象你按住了一个按钮不放——你的角色一直在做某个动作(比如蓄力、冲刺)。现在你想让角色停下来,就需要松开按钮。action_release 就是用来在代码里"松手"的。
它是 action_press 的搭档。action_press 负责"按下去",action_release 负责"松开"。两个函数配合使用,就能在代码中完整地模拟一次按键的"按下-松开"过程。
一句话总结
action_release = "让代码替玩家松开按键"——结束由 action_press 开始的模拟按下状态。
和 action_press 的关系
action_release 通常和 action_press 成对使用。如果你只调用了 action_press 而忘记调用 action_release,该动作会永远处于按下状态,直到游戏结束。这就像你按住门铃不松手——门铃会一直响。
函数签名
C#
public static void ActionRelease(string action)GDScript
Input.action_release(action: String) -> void参数说明
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
| action | string | 是 | 要模拟松开的动作名称,在项目设置(Project Settings -> Input Map)中定义。比如 "jump"、"shoot" |
返回值
此函数没有返回值(void)。
代码示例
基础用法
最简单的用法——模拟一次完整的"按下然后松开":
C#
using Godot;
public partial class TestActionRelease : Node
{
public override void _Ready()
{
// 第一步:模拟按下
Input.ActionPress("jump");
GD.Print("按下后: " + Input.IsActionPressed("jump"));
// 运行结果: 按下后: True
// 第二步:模拟松开
Input.ActionRelease("jump");
GD.Print("松开后: " + Input.IsActionPressed("jump"));
// 运行结果: 松开后: False
}
}GDScript
extends Node
func _ready() -> void:
# 第一步:模拟按下
Input.action_press("jump")
print("按下后: " + str(Input.is_action_pressed("jump")))
# 运行结果: 按下后: True
# 第二步:模拟松开
Input.action_release("jump")
print("松开后: " + str(Input.is_action_pressed("jump")))
# 运行结果: 松开后: False实际场景
在一个格斗游戏中,实现"蓄力攻击":按住攻击键蓄力,松开后释放。用 action_press 和 action_release 来让 AI 角色也能执行蓄力攻击:
C#
using Godot;
public partial class ChargeAttack : Node
{
[Export] public float ExMaxChargeTime = 2.0f;
[Export] public int ExMinDamage = 10;
[Export] public int ExMaxDamage = 50;
private bool _isCharging = false;
private float _chargeStartTime;
public override void _UnhandledInput(InputEvent @event)
{
// 玩家按下攻击键:开始蓄力
if (@event.IsActionPressed("attack"))
{
_isCharging = true;
_chargeStartTime = (float)Time.GetTicksMsec() / 1000f;
GD.Print("开始蓄力...");
// 运行结果: 开始蓄力...
}
// 玩家松开攻击键:释放攻击
if (@event.IsActionReleased("attack") && _isCharging)
{
ReleaseCharge();
}
}
public override void _Process(double delta)
{
if (!_isCharging) return;
// 显示蓄力进度
float chargeTime = (float)Time.GetTicksMsec() / 1000f - _chargeStartTime;
float progress = Mathf.Clamp(chargeTime / ExMaxChargeTime, 0f, 1f);
GD.Print($"蓄力中: {progress * 100:F0}%");
// 运行结果: 蓄力中: 25%
// 蓄力中: 50%
// 蓄力中: 75%
}
private void ReleaseCharge()
{
_isCharging = false;
float chargeTime = (float)Time.GetTicksMsec() / 1000f - _chargeStartTime;
float progress = Mathf.Clamp(chargeTime / ExMaxChargeTime, 0f, 1f);
int damage = (int)(ExMinDamage + (ExMaxDamage - ExMinDamage) * progress);
GD.Print($"释放攻击!蓄力={progress * 100:F0}%,伤害={damage}");
// 运行结果: 释放攻击!蓄力=75%,伤害=40
// 释放攻击!蓄力=100%,伤害=50
}
// AI 角色也可以使用蓄力攻击
public async void AIChargeAttack(float chargeDuration)
{
GD.Print("AI 开始蓄力攻击");
// 运行结果: AI 开始蓄力攻击
Input.ActionPress("attack");
await ToSignal(GetTree().CreateTimer(chargeDuration), SceneTreeTimer.SignalName.Timeout);
Input.ActionRelease("attack");
}
}GDScript
extends Node
@export var ex_max_charge_time: float = 2.0
@export var ex_min_damage: int = 10
@export var ex_max_damage: int = 50
var _is_charging: bool = false
var _charge_start_time: float = 0.0
func _unhandled_input(event: InputEvent) -> void:
# 玩家按下攻击键:开始蓄力
if event.is_action_pressed("attack"):
_is_charging = true
_charge_start_time = Time.get_ticks_msec() / 1000.0
print("开始蓄力...")
# 运行结果: 开始蓄力...
# 玩家松开攻击键:释放攻击
if event.is_action_released("attack") and _is_charging:
_release_charge()
func _process(delta: float) -> void:
if not _is_charging:
return
# 显示蓄力进度
var charge_time := Time.get_ticks_msec() / 1000.0 - _charge_start_time
var progress := clampf(charge_time / ex_max_charge_time, 0.0, 1.0)
print("蓄力中: %.0f%%" % (progress * 100.0))
# 运行结果: 蓄力中: 25%
# 蓄力中: 50%
# 蓄力中: 75%
func _release_charge() -> void:
_is_charging = false
var charge_time := Time.get_ticks_msec() / 1000.0 - _charge_start_time
var progress := clampf(charge_time / ex_max_charge_time, 0.0, 1.0)
var damage := int(ex_min_damage + (ex_max_damage - ex_min_damage) * progress)
print("释放攻击!蓄力=%.0f%%,伤害=%d" % [progress * 100.0, damage])
# 运行结果: 释放攻击!蓄力=75%,伤害=40
# 释放攻击!蓄力=100%,伤害=50
# AI 角色也可以使用蓄力攻击
func ai_charge_attack(charge_duration: float) -> void:
print("AI 开始蓄力攻击")
# 运行结果: AI 开始蓄力攻击
Input.action_press("attack")
await get_tree().create_timer(charge_duration).timeout
Input.action_release("attack")进阶用法
实现一个连招系统——用 action_press 和 action_release 自动执行一系列连续的按键操作,形成连招效果。这在格斗游戏或动作游戏中非常常见:
C#
using Godot;
public partial class ComboSystem : Node
{
// 连招定义:每个步骤包含动作名和持续时间(秒)
private readonly record ComboStep(string Action, double HoldTime);
private Godot.Collections.Array<ComboStep> _currentCombo = new();
private int _currentStep = 0;
private double _stepTimer = 0;
private bool _isExecuting = false;
// 预设连招:三连击
private readonly ComboStep[] _tripleHitCombo = new ComboStep[]
{
new ComboStep("attack_light", 0.15),
new ComboStep("attack_light", 0.15),
new ComboStep("attack_heavy", 0.3),
};
// 预设连招:冲刺 + 上挑 + 下砸
private readonly ComboStep[] _launchCombo = new ComboStep[]
{
new ComboStep("dash", 0.2),
new ComboStep("attack_up", 0.15),
new ComboStep("attack_down", 0.3),
};
public override void _UnhandledInput(InputEvent @event)
{
// 按 1 键执行三连击
if (@event.IsActionPressed("combo_1"))
{
ExecuteCombo(_tripleHitCombo);
}
// 按 2 键执行上挑连招
if (@event.IsActionPressed("combo_2"))
{
ExecuteCombo(_launchCombo);
}
}
private void ExecuteCombo(ComboStep[] combo)
{
if (_isExecuting) return; // 正在执行中,不重复触发
_currentCombo.Clear();
foreach (var step in combo)
{
_currentCombo.Add(step);
}
_currentStep = 0;
_isExecuting = true;
GD.Print("=== 开始执行连招,共 " + _currentCombo.Count + " 步 ===");
// 运行结果: === 开始执行连招,共 3 步 ===
}
public override void _Process(double delta)
{
if (!_isExecuting || _currentStep >= _currentCombo.Count) return;
var step = _currentCombo[_currentStep];
_stepTimer += delta;
if (_stepTimer == 0)
{
// 第一步:按下当前动作
Input.ActionPress(step.Action);
GD.Print($"第 {_currentStep + 1} 步: 按下 {step.Action}");
// 运行结果: 第 1 步: 按下 attack_light
}
if (_stepTimer >= step.HoldTime)
{
// 时间到:松开当前动作,进入下一步
Input.ActionRelease(step.Action);
GD.Print($"第 {_currentStep + 1} 步: 松开 {step.Action}");
// 运行结果: 第 1 步: 松开 attack_light
_currentStep++;
_stepTimer = 0;
if (_currentStep >= _currentCombo.Count)
{
_isExecuting = false;
GD.Print("=== 连招执行完毕 ===");
// 运行结果: === 连招执行完毕 ===
}
}
}
}GDScript
extends Node
# 连招定义:每个步骤包含动作名和持续时间(秒)
var _current_combo: Array = []
var _current_step: int = 0
var _step_timer: float = 0.0
var _is_executing: bool = false
# 预设连招:三连击
var _triple_hit_combo: Array = [
{"action": "attack_light", "hold_time": 0.15},
{"action": "attack_light", "hold_time": 0.15},
{"action": "attack_heavy", "hold_time": 0.3},
]
# 预设连招:冲刺 + 上挑 + 下砸
var _launch_combo: Array = [
{"action": "dash", "hold_time": 0.2},
{"action": "attack_up", "hold_time": 0.15},
{"action": "attack_down", "hold_time": 0.3},
]
func _unhandled_input(event: InputEvent) -> void:
# 按 1 键执行三连击
if event.is_action_pressed("combo_1"):
_execute_combo(_triple_hit_combo)
# 按 2 键执行上挑连招
if event.is_action_pressed("combo_2"):
_execute_combo(_launch_combo)
func _execute_combo(combo: Array) -> void:
if _is_executing:
return # 正在执行中,不重复触发
_current_combo = combo.duplicate()
_current_step = 0
_is_executing = true
print("=== 开始执行连招,共 %d 步 ===" % _current_combo.size())
# 运行结果: === 开始执行连招,共 3 步 ===
func _process(delta: float) -> void:
if not _is_executing or _current_step >= _current_combo.size():
return
var step = _current_combo[_current_step]
if _step_timer == 0.0:
# 第一步:按下当前动作
Input.action_press(step["action"])
print("第 %d 步: 按下 %s" % [_current_step + 1, step["action"]])
# 运行结果: 第 1 步: 按下 attack_light
_step_timer += delta
if _step_timer >= step["hold_time"]:
# 时间到:松开当前动作,进入下一步
Input.action_release(step["action"])
print("第 %d 步: 松开 %s" % [_current_step + 1, step["action"]])
# 运行结果: 第 1 步: 松开 attack_light
_current_step += 1
_step_timer = 0.0
if _current_step >= _current_combo.size():
_is_executing = false
print("=== 连招执行完毕 ===")
# 运行结果: === 连招执行完毕 ===注意事项
- 必须和
action_press配对使用:action_release的作用是解除由action_press设置的"按下"状态。如果你在调用action_release之前没有调用过action_press,调用也不会报错,但不会有任何效果。 - 同样不会产生 InputEvent 事件:和
action_press一样,action_release也不会触发InputEvent。_Input、_UnhandledInput等回调函数收不到这个模拟的松开事件。它只影响Input.IsActionPressed、Input.IsActionJustReleased等查询方法的返回值。 - 忘记释放会导致"卡键":如果你调用了
action_press但忘记调用action_release,该动作会一直保持按下状态,is_action_just_released可能会在下一帧触发一次,但is_action_pressed会一直返回true。这会导致你的角色"卡住"(比如一直处于跳跃或攻击状态)。建议在不需要模拟输入时,确保所有之前action_press过的动作都被正确action_release。 - 可以用
flush_buffer清除所有状态:如果你不确定当前有哪些动作处于模拟按下状态,可以调用Input.FlushBuffer()(C#)/Input.flush_buffer()(GDScript)来一次性清除所有模拟的输入状态,相当于"全部松开"。 - 玩家真实输入会覆盖模拟状态:如果玩家真实按下了某个你正在用
action_press模拟的动作,真实输入会与模拟输入叠加。松开真实按键或调用action_release都会改变动作的状态。建议在模拟输入期间暂时禁用或忽略玩家的真实输入,避免冲突。 - C# 差异:C# 中方法名用 PascalCase(
Input.ActionRelease),GDScript 中用 snake_case(Input.action_release)。
