wrap
2026/4/14大约 5 分钟
最后同步日期:2026-04-15 | Godot 官方原文 — wrap
wrap
定义
wrap() 用来把一个数值"环绕"到指定范围内——简单说,就像钟表的指针:超过 12 点后会从 1 点重新开始,永远不会显示 13 点或 14 点。
想象一个环形跑道:跑道全长 400 米,你从起点出发跑了 450 米,实际上你回到了跑道的 50 米处。wrap() 就是干这件事的:超出范围的值会被"折回来",从范围的开头继续计数。wrap() 是 wrapf()(浮点数版)的通用写法。
在游戏开发中,wrap() 常见于:环形地图(坦克大战的地图环绕)、循环动画进度、角度归一化、周期性事件计时、武器轮换切换等。任何需要"到头了从头再来"的场景都适用。
函数签名
C#
// 浮点数版本
public static float Wrap(float value, float min, float max)
// 整数版本
public static int Wrap(int value, int min, int max)GDScript
func wrap(value: float, min: float, max: float) -> float参数说明
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
value | float 或 int | 是 | 要环绕的原始值。可以超出 [min, max] 范围 |
min | float 或 int | 是 | 环绕范围的最小值 |
max | float 或 int | 是 | 环绕范围的最大值 |
返回值
float 或 int —— 环绕后的值,始终在 [min, max) 范围内(注意:包含 min,不包含 max)。
具体规则:
- 如果
value在[min, max)内,返回value本身 - 如果
value >= max,往回折:从min开始继续数 - 如果
value < min,往前折:从max方向往回数
例子:wrap(12, 0, 10) 返回 2,wrap(-3, 0, 10) 返回 7
代码示例
基础用法:数值环绕演示
C#
using Godot;
public partial class WrapExample : Node
{
public override void _Ready()
{
// 在范围内,原样返回
float a = Mathf.Wrap(5f, 0f, 10f);
GD.Print($"wrap(5, 0, 10) = {a}");
// 运行结果: wrap(5, 0, 10) = 5
// 超过上限,从最小值重新开始
float b = Mathf.Wrap(12f, 0f, 10f);
GD.Print($"wrap(12, 0, 10) = {b}");
// 运行结果: wrap(12, 0, 10) = 2
// 低于下限,从最大值方向折回
float c = Mathf.Wrap(-3f, 0f, 10f);
GD.Print($"wrap(-3, 0, 10) = {c}");
// 运行结果: wrap(-3, 0, 10) = 7
// 整数版本
int d = Mathf.Wrap(15, 0, 10);
GD.Print($"wrap(15, 0, 10) = {d}");
// 运行结果: wrap(15, 0, 10) = 5
}
}GDScript
func _ready():
# 在范围内,原样返回
var a = wrapf(5.0, 0.0, 10.0)
print("wrap(5, 0, 10) = %f" % a)
# 运行结果: wrap(5, 0, 10) = 5.000000
# 超过上限,从最小值重新开始
var b = wrapf(12.0, 0.0, 10.0)
print("wrap(12, 0, 10) = %f" % b)
# 运行结果: wrap(12, 0, 10) = 2.000000
# 低于下限,从最大值方向折回
var c = wrapf(-3.0, 0.0, 10.0)
print("wrap(-3, 0, 10) = %f" % c)
# 运行结果: wrap(-3, 0, 10) = 7.000000
# 大范围偏移
var d = wrapf(25.0, 0.0, 10.0)
print("wrap(25, 0, 10) = %f" % d)
# 运行结果: wrap(25, 0, 10) = 5.000000实际场景:武器轮换切换
C#
using Godot;
public partial class WeaponSwitcher : Node
{
// 内部变量:当前武器索引
private int _currentWeapon = 0;
// 内部变量:武器总数
private int _weaponCount = 5;
// 武器名称(仅用于演示)
private readonly string[] _weaponNames = { "手枪", "霰弹枪", "步枪", "狙击枪", "火箭筒" };
public override void _UnhandledInput(InputEvent ev)
{
// 滚轮向上切换下一把武器
if (ev.IsActionPressed("weapon_next"))
{
_currentWeapon = Mathf.Wrap(_currentWeapon + 1, 0, _weaponCount);
GD.Print($"切换到: {_weaponNames[_currentWeapon]}");
}
// 滚轮向下切换上一把武器
if (ev.IsActionPressed("weapon_prev"))
{
_currentWeapon = Mathf.Wrap(_currentWeapon - 1, 0, _weaponCount);
GD.Print($"切换到: {_weaponNames[_currentWeapon]}");
}
// 运行结果: 从火箭筒按下一个 → 切换到: 手枪
}
}GDScript
extends Node
# 内部变量:当前武器索引
var _current_weapon: int = 0
# 内部变量:武器总数
var _weapon_count: int = 5
# 武器名称(仅用于演示)
var _weapon_names = ["手枪", "霰弹枪", "步枪", "狙击枪", "火箭筒"]
func _unhandled_input(ev: InputEvent) -> void:
# 滚轮向上切换下一把武器
if ev.is_action_pressed("weapon_next"):
_current_weapon = wrapi(_current_weapon + 1, 0, _weapon_count)
print("切换到: %s" % _weapon_names[_current_weapon])
# 滚轮向下切换上一把武器
if ev.is_action_pressed("weapon_prev"):
_current_weapon = wrapi(_current_weapon - 1, 0, _weapon_count)
print("切换到: %s" % _weapon_names[_current_weapon])
# 运行结果: 从火箭筒按下一个 → 切换到: 手枪进阶用法:环形世界地图(坦克大战风格)
C#
using Godot;
public partial class WrapWorld : Node2D
{
// 导出属性:地图宽度
[Export] public float ExWorldWidth = 800f;
// 导出属性:地图高度
[Export] public float ExWorldHeight = 600f;
// 导出属性:移动速度
[Export] public float ExMoveSpeed = 200f;
public override void _Process(double delta)
{
Vector2 velocity = Vector2.Zero;
if (Input.IsKeyPressed(Key.Right)) velocity.X += 1;
if (Input.IsKeyPressed(Key.Left)) velocity.X -= 1;
if (Input.IsKeyPressed(Key.Down)) velocity.Y += 1;
if (Input.IsKeyPressed(Key.Up)) velocity.Y -= 1;
velocity = velocity.Normalized() * ExMoveSpeed;
Position += velocity * (float)delta;
// 超出地图边界时,从另一侧出现(环形世界)
float x = Mathf.Wrap(Position.X, 0f, ExWorldWidth);
float y = Mathf.Wrap(Position.Y, 0f, ExWorldHeight);
Position = new Vector2(x, y);
GD.Print($"位置: ({x:F0}, {y:F0})");
// 运行结果: 位置: (50, 300)(从右边出去后从左边出现)
}
}GDScript
extends Node2D
# 导出属性:地图宽度
@export var ex_world_width: float = 800.0
# 导出属性:地图高度
@export var ex_world_height: float = 600.0
# 导出属性:移动速度
@export var ex_move_speed: float = 200.0
func _process(delta):
var velocity = Vector2.ZERO
if Input.is_key_pressed(KEY_RIGHT): velocity.x += 1
if Input.is_key_pressed(KEY_LEFT): velocity.x -= 1
if Input.is_key_pressed(KEY_DOWN): velocity.y += 1
if Input.is_key_pressed(KEY_UP): velocity.y -= 1
velocity = velocity.normalized() * ex_move_speed
position += velocity * delta
# 超出地图边界时,从另一侧出现(环形世界)
var x = wrapf(position.x, 0.0, ex_world_width)
var y = wrapf(position.y, 0.0, ex_world_height)
position = Vector2(x, y)
print("位置: (%.0f, %.0f)" % [x, y])
# 运行结果: 位置: (50, 300)(从右边出去后从左边出现)注意事项
- 返回值范围是
[min, max):包含min,但不包含max。这和fmod()的行为不同——fmod()可能返回等于max的值。 - GDScript 中的整数和浮点版本:GDScript 提供了
wrapf()(浮点版)和wrapi()(整数版)两个函数。wrap()是通用写法,会根据参数类型自动选择。 min必须小于max:如果min >= max,行为是未定义的。- 与
posmod()的区别:posmod()只处理"除法取余"的场景,而wrap()可以处理任意范围,并且正负方向都能环绕。 - 角度归一化不要用
wrap():如果需要把角度归一化到[0, 360)或[-180, 180)范围,请使用fmod()配合条件判断,或使用 Godot 内置的角度处理函数。wrap()可以用但不那么直观。
