Laya.MathUtil 数学工具类使用说明

简介

Laya.MathUtil 是 LayaAir 引擎提供的静态数学工具类,包含常用的数值计算方法,涵盖线性插值、数值限制、距离计算、角度计算、数组排序等功能。


目录


线性插值

lerp

在两个值之间进行线性插值。

static lerp(left: number, right: number, amount: number): number

参数:

  • left: 起始值
  • right: 终止值
  • amount: 插值比率(0-1)

返回值: 插值结果

示例:

// 基本用法
let result1 = Laya.MathUtil.lerp(0, 100, 0.5);   // 50
let result2 = Laya.MathUtil.lerp(0, 100, 0.25);  // 25
let result3 = Laya.MathUtil.lerp(0, 100, 0.75);  // 75
let result4 = Laya.MathUtil.lerp(50, 150, 0.5);  // 100

// 实际应用:平滑移动动画
class SmoothMovement {
    private startX: number = 0;
    private endX: number = 500;
    private progress: number = 0;
    private sprite: Laya.Sprite;

    update(deltaTime: number): void {
        this.progress += deltaTime * 0.001; // 假设 delta 单位是毫秒
        if (this.progress > 1) this.progress = 1;

        // 使用 lerp 计算当前位置
        this.sprite.x = Laya.MathUtil.lerp(
            this.startX,
            this.endX,
            this.progress
        );
    }
}

// 实际应用:颜色渐变
function lerpColor(color1: number, color2: number, t: number): number {
    let r1 = (color1 >> 16) & 0xFF;
    let g1 = (color1 >> 8) & 0xFF;
    let b1 = color1 & 0xFF;

    let r2 = (color2 >> 16) & 0xFF;
    let g2 = (color2 >> 8) & 0xFF;
    let b2 = color2 & 0xFF;

    let r = Math.floor(Laya.MathUtil.lerp(r1, r2, t));
    let g = Math.floor(Laya.MathUtil.lerp(g1, g2, t));
    let b = Math.floor(Laya.MathUtil.lerp(b1, b2, t));

    return (r << 16) | (g << 8) | b;
}

// 从红色渐变到蓝色
let color = lerpColor(0xFF0000, 0x0000FF, 0.5); // 0x800080 (紫色)

数值循环

repeat

将值 t 重复限定在 [0, length) 范围内。

static repeat(t: number, length: number): number

参数:

  • t: 要重复的值
  • length: 范围长度

返回值: 重复后的值,范围在 [0, length) 之间

示例:

// 基本用法
let result1 = Laya.MathUtil.repeat(5, 10);    // 5
let result2 = Laya.MathUtil.repeat(15, 10);   // 5 (15 % 10 = 5)
let result3 = Laya.MathUtil.repeat(-3, 10);   // 7
let result4 = Laya.MathUtil.repeat(0, 10);    // 0

// 实际应用:循环动画
class LoopingAnimation {
    private time: number = 0;
    private loopDuration: number = 2000; // 2秒循环

    update(deltaTime: number): number {
        this.time += deltaTime;
        // 获取循环内的进度 [0, 1)
        let progress = Laya.MathUtil.repeat(this.time, this.loopDuration) / this.loopDuration;
        return progress;
    }
}

// 实际应用:无限滚动的背景
class ScrollingBackground {
    private scrollX: number = 0;
    private textureWidth: number = 800;

    scroll(speed: number): void {
        this.scrollX += speed;
        // 保持在纹理宽度范围内
        this.scrollX = Laya.MathUtil.repeat(this.scrollX, this.textureWidth);
    }
}

距离计算

distance

计算二维空间中两点之间的距离。

static distance(x1: number, y1: number, x2: number, y2: number): number

参数:

  • x1: 第一个点的 X 坐标
  • y1: 第一个点的 Y 坐标
  • x2: 第二个点的 X 坐标
  • y2: 第二个点的 Y 坐标

返回值: 两点之间的距离

示例:

// 基本用法
let dist1 = Laya.MathUtil.distance(0, 0, 100, 0);    // 100
let dist2 = Laya.MathUtil.distance(0, 0, 0, 100);    // 100
let dist3 = Laya.MathUtil.distance(0, 0, 100, 100);  // ≈ 141.42
let dist4 = Laya.MathUtil.distance(50, 50, 100, 100); // ≈ 70.71

// 实际应用:检测敌人是否进入攻击范围
class EnemyDetection {
    private playerX: number = 400;
    private playerY: number = 300;
    private attackRange: number = 150;

    isInRange(enemyX: number, enemyY: number): boolean {
        let dist = Laya.MathUtil.distance(
            this.playerX,
            this.playerY,
            enemyX,
            enemyY
        );
        return dist <= this.attackRange;
    }
}

// 实际应用:寻找最近的目标
class TargetSelector {
    findNearest(x: number, y: number, targets: Array<{x: number, y: number}>): {x: number, y: number} | null {
        let nearest: {x: number, y: number} | null = null;
        let minDistance = Infinity;

        for (let target of targets) {
            let dist = Laya.MathUtil.distance(x, y, target.x, target.y);
            if (dist < minDistance) {
                minDistance = dist;
                nearest = target;
            }
        }

        return nearest;
    }
}

数值限制

clamp

将值限制在指定范围内。

static clamp(value: number, min: number, max: number): number

参数:

  • value: 要限制的值
  • min: 最小值
  • max: 最大值

返回值: 限制后的值

示例:

// 基本用法
let result1 = Laya.MathUtil.clamp(50, 0, 100);    // 50
let result2 = Laya.MathUtil.clamp(-10, 0, 100);   // 0
let result3 = Laya.MathUtil.clamp(150, 0, 100);   // 100
let result4 = Laya.MathUtil.clamp(0, -50, 50);    // 0

// 实际应用:限制血量范围
class Character {
    private maxHealth: number = 100;
    private currentHealth: number = 100;

    takeDamage(damage: number): void {
        this.currentHealth = Laya.MathUtil.clamp(
            this.currentHealth - damage,
            0,
            this.maxHealth
        );
    }

    heal(amount: number): void {
        this.currentHealth = Laya.MathUtil.clamp(
            this.currentHealth + amount,
            0,
            this.maxHealth
        );
    }
}

// 实际应用:限制速度范围
class MovableObject {
    private speed: number = 0;
    private minSpeed: number = -200;
    private maxSpeed: number = 200;

    setSpeed(newSpeed: number): void {
        this.speed = Laya.MathUtil.clamp(newSpeed, this.minSpeed, this.maxSpeed);
    }
}

clamp01

将值限制在 [0, 1] 范围内。

static clamp01(value: number): number

参数:

  • value: 要限制的值

返回值: 限制后的值

示例:

// 基本用法
let result1 = Laya.MathUtil.clamp01(0.5);    // 0.5
let result2 = Laya.MathUtil.clamp01(-0.5);   // 0
let result3 = Laya.MathUtil.clamp01(1.5);    // 1
let result4 = Laya.MathUtil.clamp01(0);      // 0

// 实际应用:标准化进度值
class ProgressBar {
    private currentValue: number = 0;
    private maxValue: number = 100;

    getProgress(): number {
        // 确保进度始终在 0-1 之间
        return Laya.MathUtil.clamp01(this.currentValue / this.maxValue);
    }

    setValue(value: number): void {
        this.currentValue = value;
    }
}

// 实际应用:透明度限制
class FadeEffect {
    private alpha: number = 1;

    setAlpha(value: number): void {
        // 确保透明度在 0-1 之间
        this.alpha = Laya.MathUtil.clamp01(value);
    }
}

四元数插值

slerpQuaternionArray

在两个四元数之间进行球面线性插值(slerp)。

static slerpQuaternionArray(
    a: Float32Array,
    Offset1: number,
    b: Float32Array,
    Offset2: number,
    t: number,
    out: Float32Array,
    Offset3: number
): Float32Array

参数:

  • a: 起始四元数数组
  • Offset1: 起始四元数数组的偏移量
  • b: 终止四元数数组
  • Offset2: 终止四元数数组的偏移量
  • t: 插值比率(0-1)
  • out: 用于存储结果的输出四元数数组
  • Offset3: 输出四元数数组的偏移量

返回值: 输出四元数数组

示例:

// 实际应用:3D 对象旋转平滑过渡
class SmoothRotation3D {
    private startRotation = new Float32Array([0, 0, 0, 1]);
    private endRotation = new Float32Array([0, 0.707, 0, 0.707]);
    private currentRotation = new Float32Array([0, 0, 0, 1]);
    private progress: number = 0;

    update(deltaTime: number): void {
        this.progress += deltaTime * 0.001;
        if (this.progress > 1) this.progress = 1;

        Laya.MathUtil.slerpQuaternionArray(
            this.startRotation, 0,
            this.endRotation, 0,
            this.progress,
            this.currentRotation, 0
        );
    }
}

角度计算

getRotation

获取由两个指定点组成的线段的角度值。

static getRotation(x0: number, y0: number, x1: number, y1: number): number

参数:

  • x0: 第一个点的 X 坐标
  • y0: 第一个点的 Y 坐标
  • x1: 第二个点的 X 坐标
  • y1: 第二个点的 Y 坐标

返回值: 角度值,单位为度

示例:

// 基本用法
let angle1 = Laya.MathUtil.getRotation(0, 0, 100, 0);   // 0° (向右)
let angle2 = Laya.MathUtil.getRotation(0, 0, 0, 100);   // 90° (向下)
let angle3 = Laya.MathUtil.getRotation(0, 0, -100, 0);  // 180° (向左)
let angle4 = Laya.MathUtil.getRotation(0, 0, 0, -100);  // -90° (向上)
let angle5 = Laya.MathUtil.getRotation(0, 0, 100, 100); // 45° (右下)

// 实际应用:让炮台朝向目标
class Turret {
    private turretSprite: Laya.Sprite;
    private turretX: number = 400;
    private turretY: number = 300;

    aimAt(targetX: number, targetY: number): void {
        // 计算朝向目标的角度
        let angle = Laya.MathUtil.getRotation(
            this.turretX,
            this.turretY,
            targetX,
            targetY
        );
        this.turretSprite.rotation = angle;
    }
}

// 实际应用:子弹发射方向
class Bullet {
    static create(
        startX: number,
        startY: number,
        targetX: number,
        targetY: number,
        speed: number
): { x: number; y: number } {
        let angle = Laya.MathUtil.getRotation(startX, startY, targetX, targetY);
        let radians = angle * Math.PI / 180;

        return {
            x: Math.cos(radians) * speed,
            y: Math.sin(radians) * speed
        };
    }
}

// 实际应用:让角色朝向移动方向
class Character {
    private sprite: Laya.Sprite;
    private currentX: number = 0;
    private currentY: number = 0;

    moveTo(targetX: number, targetY: number): void {
        // 计算移动方向的角度
        let angle = Laya.MathUtil.getRotation(
            this.currentX,
            this.currentY,
            targetX,
            targetY
        );
        this.sprite.rotation = angle;

        // 更新位置...
    }
}

数组排序

sortBigFirst

数值从大到小排序的比较函数。

static sortBigFirst(a: number, b: number): number

参数:

  • a: 待比较数字
  • b: 待比较数字

返回值: 如果 a 等于 b 返回 0;如果 b > a 返回 1;如果 b < a 返回 -1

示例:

// 基本用法
let scores = [85, 92, 78, 95, 88];
scores.sort(Laya.MathUtil.sortBigFirst);
console.log(scores); // [95, 92, 88, 85, 78]

// 实际应用:排行榜按分数排序
class Leaderboard {
    private entries: Array<{name: string, score: number}> = [];

    addEntry(name: string, score: number): void {
        this.entries.push({ name, score });
        this.sortByScore();
    }

    sortByScore(): void {
        this.entries.sort((a, b) => Laya.MathUtil.sortBigFirst(a.score, b.score));
    }

    getTopPlayers(count: number): Array<{name: string, score: number}> {
        return this.entries.slice(0, count);
    }
}

sortSmallFirst

数值从小到大排序的比较函数。

static sortSmallFirst(a: number, b: number): number

参数:

  • a: 待比较数字
  • b: 待比较数字

返回值: 如果 a 等于 b 返回 0;如果 b > a 返回 -1;如果 b < a 返回 1

示例:

// 基本用法
let numbers = [5, 2, 8, 1, 9];
numbers.sort(Laya.MathUtil.sortSmallFirst);
console.log(numbers); // [1, 2, 5, 8, 9]

// 实际应用:按价格排序商品
class Shop {
    private items: Array<{name: string, price: number}> = [];

    sortByPrice(): void {
        this.items.sort((a, b) => Laya.MathUtil.sortSmallFirst(a.price, b.price));
    }
}

sortNumBigFirst

将元素转为数字后从大到小排序的比较函数。

static sortNumBigFirst(a: any, b: any): number

参数:

  • a: 待比较元素
  • b: 待比较元素

返回值: b、a 转化成数字的差值 (b-a)

示例:

// 基本用法:处理字符串数字
let strNumbers = ["95", "88", "92", "78"];
strNumbers.sort(Laya.MathUtil.sortNumBigFirst);
console.log(strNumbers); // ["95", "92", "88", "78"]

// 实际应用:按数字属性排序
class QuestManager {
    private quests: Array<{id: string, priority: string}> = [
        { id: "quest1", priority: "3" },
        { id: "quest2", priority: "1" },
        { id: "quest3", priority: "2" }
    ];

    sortByPriority(): void {
        this.quests.sort((a, b) =>
            Laya.MathUtil.sortNumBigFirst(a.priority, b.priority)
        );
    }
}

sortNumSmallFirst

将元素转为数字后从小到大排序的比较函数。

static sortNumSmallFirst(a: any, b: any): number

参数:

  • a: 待比较元素
  • b: 待比较元素

返回值: a、b 转化成数字的差值 (a-b)

示例:

// 基本用法
let values = ["100", "25", "50", "10"];
values.sort(Laya.MathUtil.sortNumSmallFirst);
console.log(values); // ["10", "25", "50", "100"]

sortByKey

根据对象指定的属性进行排序的比较函数。

static sortByKey(key: string, bigFirst?: boolean, forceNum?: boolean): (a: any, b: any) => number

参数:

  • key: 排序要依据的元素属性名
  • bigFirst: 是否从大到小排序,默认 false(从小到大)
  • forceNum: 是否将元素转为数字比较,默认 false

返回值: 排序函数

示例:

// 基本用法
interface Player {
    name: string;
    level: number;
    score: string;
}

let players: Player[] = [
    { name: "Alice", level: 10, score: "1500" },
    { name: "Bob", level: 5, score: "2000" },
    { name: "Charlie", level: 15, score: "1000" }
];

// 按等级从高到低排序
players.sort(Laya.MathUtil.sortByKey("level", true));
// 结果: Charlie(15), Alice(10), Bob(5)

// 按分数从低到高排序(转为数字)
players.sort(Laya.MathUtil.sortByKey("score", false, true));
// 结果: Charlie(1000), Alice(1500), Bob(2000)

// 实际应用:动态排序
class SortableList<T> {
    private items: T[] = [];

    sortBy(property: string, descending: boolean = false): void {
        this.items.sort(Laya.MathUtil.sortByKey(property, descending));
    }
}

// 实际应用:商品排序
class ProductManager {
    private products: Array<{
        name: string;
        price: number;
        sales: string;
        rating: number;
    }> = [];

    // 按价格从低到高
    sortByPrice(): void {
        this.products.sort(Laya.MathUtil.sortByKey("price", false));
    }

    // 按销量从高到低(转为数字)
    sortBySales(): void {
        this.products.sort(Laya.MathUtil.sortByKey("sales", true, true));
    }

    // 按评分从高到低
    sortByRating(): void {
        this.products.sort(Laya.MathUtil.sortByKey("rating", true));
    }
}

数值精度处理

roundTo

将数字按小数位四舍五入,并将接近 0 的值归零,避免浮点误差。

static roundTo(value: number, decimals?: number, epsilon?: number): number

参数:

  • value: 输入数字
  • decimals: 保留小数位数,默认 3
  • epsilon: 接近 0 判定阈值,默认 1e-6

返回值: 处理后的数字

示例:

// 基本用法
let result1 = Laya.MathUtil.roundTo(3.1415926);        // 3.142
let result2 = Laya.MathUtil.roundTo(3.1415926, 2);     // 3.14
let result3 = Laya.MathUtil.roundTo(3.1415926, 4);     // 3.1416

// 处理浮点误差
let result4 = Laya.MathUtil.roundTo(0.0000001);        // 0 (小于 epsilon)
let result5 = Laya.MathUtil.roundTo(0.000001);         // 0 (等于 epsilon)
let result6 = Laya.MathUtil.roundTo(0.00001);          // 0.00001 (大于 epsilon)

// 实际应用:显示精度控制
class ScoreDisplay {
    private rawScore: number = 0;

    getFormattedScore(decimals: number = 1): string {
        return Laya.MathUtil.roundTo(this.rawScore, decimals).toFixed(decimals);
    }

    addScore(points: number): void {
        this.rawScore += points;
        // 累积后清理浮点误差
        this.rawScore = Laya.MathUtil.roundTo(this.rawScore, 3);
    }
}

// 实际应用:物理模拟中的精度控制
class PhysicsSimulation {
    private position: { x: number; y: number } = { x: 0, y: 0 };
    private velocity: { x: number; y: number } = { x: 0, y: 0 };

    update(dt: number): void {
        // 更新位置
        this.position.x += this.velocity.x * dt;
        this.position.y += this.velocity.y * dt;

        // 清理浮点累积误差
        this.position.x = Laya.MathUtil.roundTo(this.position.x, 4);
        this.position.y = Laya.MathUtil.roundTo(this.position.y, 4);
    }
}

// 实际应用:货币计算
class Wallet {
    private balance: number = 0;

    add(amount: number): void {
        this.balance += amount;
        this.balance = Laya.MathUtil.roundTo(this.balance, 2);
    }

    getBalance(): number {
        return Laya.MathUtil.roundTo(this.balance, 2);
    }
}

完整示例

以下是一个综合使用 Laya.MathUtil 工具类的完整示例:

export async function main() {
    await Laya.init(800, 600);

    // 创建游戏场景
    let scene = new Laya.Scene();
    Laya.stage.addChild(scene);

    // 1. 创建玩家精灵
    let player = new Laya.Sprite();
    player.graphics.drawCircle(0, 0, 20, "#00FF00");
    player.pos(400, 300);
    Laya.stage.addChild(player);

    // 2. 创建目标点
    let target = new Laya.Sprite();
    target.graphics.drawCircle(0, 0, 10, "#FF0000");
    target.pos(600, 400);
    Laya.stage.addChild(target);

    // 3. 使用 getRotation 让玩家朝向目标
    let angle = Laya.MathUtil.getRotation(
        player.x,
        player.y,
        target.x,
        target.y
    );
    console.log("朝向角度:", angle);

    // 4. 使用 distance 计算距离
    let distance = Laya.MathUtil.distance(
        player.x,
        player.y,
        target.x,
        target.y
    );
    console.log("目标距离:", distance);

    // 5. 使用 lerp 实现平滑移动
    let progress = 0;
    let speed = 0.02;

    Laya.timer.frameLoop(1, this, () => {
        if (progress < 1) {
            progress = Laya.MathUtil.clamp01(progress + speed);
            player.x = Laya.MathUtil.lerp(400, 600, progress);
            player.y = Laya.MathUtil.lerp(300, 400, progress);
        }
    });

    // 6. 使用 sortByKey 对玩家分数排序
    interface PlayerData {
        name: string;
        score: number;
    }

    let leaderboard: PlayerData[] = [
        { name: "玩家A", score: 1500 },
        { name: "玩家B", score: 2000 },
        { name: "玩家C", score: 1200 }
    ];

    // 按分数从高到低排序
    leaderboard.sort(Laya.MathUtil.sortByKey("score", true));
    console.log("排行榜:", leaderboard);

    // 7. 使用 roundTo 处理浮点数
    let piRounded = Laya.MathUtil.roundTo(Math.PI, 2);
    console.log("PI (保留2位小数):", piRounded);

    // 8. 使用 clamp 限制血量范围
    class HealthSystem {
        private health: number = 80;
        private maxHealth: number = 100;

        takeDamage(damage: number): void {
            this.health = Laya.MathUtil.clamp(
                this.health - damage,
                0,
                this.maxHealth
            );
            console.log("当前血量:", this.health);
        }

        heal(amount: number): void {
            this.health = Laya.MathUtil.clamp(
                this.health + amount,
                0,
                this.maxHealth
            );
            console.log("当前血量:", this.health);
        }
    }

    let healthSystem = new HealthSystem();
    healthSystem.takeDamage(30); // 50
    healthSystem.heal(70);       // 100 (不会超过最大值)

    // 9. 使用 repeat 实现循环动画
    let animTime = 0;
    Laya.timer.frameLoop(1, this, () => {
        animTime += 16; // 假设每帧约 16ms
        // 让进度在 0-2 秒内循环
        let loopProgress = Laya.MathUtil.repeat(animTime, 2000) / 2000;
        console.log("循环进度:", loopProgress);
    });

    // 10. 使用 sortBigFirst 排序数组
    let scores = [85, 92, 78, 95, 88];
    scores.sort(Laya.MathUtil.sortBigFirst);
    console.log("分数排序:", scores); // [95, 92, 88, 85, 78]
}

注意事项

  1. 静态方法: MathUtil 类的所有方法都是静态的,使用时必须加 Laya. 前缀。

  2. 角度 vs 弧度:

    • getRotation() 返回的是度数(degrees),可直接用于 Sprite.rotation
    • 使用 Math.sin()Math.cos() 等函数时需先转换为弧度
    • 可使用 Laya.Utils.toRadian() 进行转换
  3. 浮点精度: 由于 JavaScript 浮点数计算可能产生精度误差,建议使用 roundTo() 方法进行精度控制。

  4. 数组排序: sortBigFirst/sortSmallFirst 只能排序数字数组,如需排序对象数组请使用 sortByKey()

  5. clamp 参数顺序: clamp(value, min, max) 的参数顺序是 value、min、max,注意不要写成 min、max、value。

  6. 版本信息: 本文档基于 LayaAir 3.3.6 版本编写。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐