You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

P5.js创建NPC引发程序极端卡顿的问题排查与修复求助

问题描述

运行以下p5.js代码时,无论使用何种设备,程序仅正常运行1帧后就出现严重卡顿崩溃,且无任何报错信息。已定位问题与NPC的创建相关,寻求修复方案:

var player;
var speed = 5;

var HP = 10;
var MaxHP = 10;

var frames = 0;

var VillageX = 0;
var VillageY = 0;

var NPCs = [];

class NPC {
    constructor() {
        this.x = getRandomInt(VillageX, VillageX + 500);
        this.y = getRandomInt(VillageY, VillageY + 500);
        
        this.sprite = new Sprite(this.x, this.y, 15);

        this.sprite.img = "Images/npc.png";
        this.sprite.collider = "dynamic";
        this.sprite.scale = 3;
        this.minimapX = 0;
        this.minimapY = 0;

        NPCs.push(this);
    }
}

function getRandomInt(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min) + min);
}

function preload() {
    lexendFont = loadFont("Fonts/lexend.ttf")
}

function setup() {
    frameRate(60);
    createCanvas(windowWidth, windowHeight);

    frames += 1;
    console.log(frames);

    VillageX = getRandomInt(-4900, 4400);
    VillageY = getRandomInt(-4900, 4400);

    player = new Sprite(0, 0, 15, "circle");
    player.addImage("player", loadImage("Images/player.png"));
    player.scale = 3;
    player.collider = "dynamic";

    topWall = new Sprite(0, -5010, 10000, 10);
    topWall.visible = false;
    topWall.collider = "kinematic";
    
    leftWall = new Sprite(-5010, 0, 10, 10000);
    leftWall.visible = false;
    leftWall.collider = "kinematic";

    bottomWall = new Sprite(0, 5010, 10000, 10);
    bottomWall.visible = false;
    bottomWall.collider = "kinematic";

    rightWall = new Sprite(5010, 0, 10, 10000);
    rightWall.visible = false;
    rightWall.collider = "kinematic";

    for (var i = 0; i < 3; i++) {
        new NPC();
    }

    textSize(32);
}

function draw() {
    
    clear();
    background("green");

    stroke(0);
    strokeWeight(8);
    noFill();
    rect(windowWidth - 215, windowHeight - 50, 200, 35, 3);

    noStroke();
    fill("#00cc44");
    rect(windowWidth - 215, windowHeight - 50, map(HP, 0, MaxHP, 0, 200), 35, 3);

    fill("black");
    rect(15, windowHeight - 215, 200, 200, 12);

    var minimapX = map(player.x, -5200, 5200, 15, 215);
    var minimapY = map(player.y, -5200, 5200, windowHeight - 215, windowHeight - 15);
    
    fill("white");
    circle(minimapX, minimapY, 8);

    NPCs.forEach((NP, index) => {
        NP.minimapX = map(NP.x, -5200, 5200, 15, 215);
        NP.minimapY = map(NP.y, -5200, 5200, windowHeight - 215, windowHeight - 15)
        
        fill("blue");
        circle(NP.minimapX, NP.minimapY, 8);
    });

    camera.x = player.x;
    camera.y = player.y;

    player.rotation = player.angleTo(mouse) + 90;

    if (kb.pressing("left")) player.vel.x = -speed;
    else if (kb.pressing("right")) player.vel.x = speed;
    else player.vel.x = 0;

    if (kb.pressing("up")) player.vel.y = -speed;
    else if (kb.pressing("down")) player.vel.y = speed;
    else player.vel.y = 0;

    fill("white");
    textFont(lexendFont)
    textSize(18);
    text("HEALTH", windowWidth - 205, windowHeight - 27)
}
修复方案

核心问题分析

卡顿崩溃的根源是NPC的Sprite图片未预加载,且直接通过字符串路径赋值img属性。p5.play中直接给Sprite的img赋值路径会导致每帧重复尝试加载图片,引发资源占用飙升,最终导致程序崩溃。此外,代码中存在一些逻辑耦合问题,可同步优化。

具体修复步骤

  1. 预加载所有资源
    在preload函数中统一预加载字体、玩家和NPC图片,确保资源仅加载一次:

    function preload() {
        lexendFont = loadFont("Fonts/lexend.ttf");
        playerImg = loadImage("Images/player.png");
        npcImg = loadImage("Images/npc.png");
    }
    
  2. 修改NPC类的图片设置与数组添加逻辑
    用预加载的图片对象替代字符串路径,并将NPCs.push(this)移出构造函数,避免类逻辑耦合:

    class NPC {
        constructor() {
            this.x = getRandomInt(VillageX, VillageX + 500);
            this.y = getRandomInt(VillageY, VillageY + 500);
            
            this.sprite = new Sprite(this.x, this.y, 15);
            this.sprite.addImage("npc", npcImg);
            this.sprite.collider = "dynamic";
            this.sprite.scale = 3;
            this.minimapX = 0;
            this.minimapY = 0;
        }
    }
    
  3. 调整NPC创建时的数组添加逻辑
    在setup的循环中手动将NPC实例加入数组:

    for (var i = 0; i < 3; i++) {
        NPCs.push(new NPC());
    }
    
  4. 优化draw函数的渲染顺序
    将相机位置设置移到background之后、UI绘制之前,确保相机逻辑正确:

    function draw() {
        clear();
        background("green");
    
        // 先设置相机
        camera.x = player.x;
        camera.y = player.y;
    
        // 后续UI、小地图、玩家控制逻辑不变
        // ...
    }
    
  5. 修复玩家图片的加载方式
    同样使用预加载的图片对象,避免重复加载:

    player = new Sprite(0, 0, 15, "circle");
    player.addImage("player", playerImg);
    

完整修复后的代码

var player;
var speed = 5;

var HP = 10;
var MaxHP = 10;

var frames = 0;

var VillageX = 0;
var VillageY = 0;

var NPCs = [];
var lexendFont, playerImg, npcImg;

class NPC {
    constructor() {
        this.x = getRandomInt(VillageX, VillageX + 500);
        this.y = getRandomInt(VillageY, VillageY + 500);
        
        this.sprite = new Sprite(this.x, this.y, 15);
        this.sprite.addImage("npc", npcImg);
        this.sprite.collider = "dynamic";
        this.sprite.scale = 3;
        this.minimapX = 0;
        this.minimapY = 0;
    }
}

function getRandomInt(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min) + min);
}

function preload() {
    lexendFont = loadFont("Fonts/lexend.ttf");
    playerImg = loadImage("Images/player.png");
    npcImg = loadImage("Images/npc.png");
}

function setup() {
    frameRate(60);
    createCanvas(windowWidth, windowHeight);

    frames += 1;
    console.log(frames);

    VillageX = getRandomInt(-4900, 4400);
    VillageY = getRandomInt(-4900, 4400);

    player = new Sprite(0, 0, 15, "circle");
    player.addImage("player", playerImg);
    player.scale = 3;
    player.collider = "dynamic";

    topWall = new Sprite(0, -5010, 10000, 10);
    topWall.visible = false;
    topWall.collider = "kinematic";
    
    leftWall = new Sprite(-5010, 0, 10, 10000);
    leftWall.visible = false;
    leftWall.collider = "kinematic";

    bottomWall = new Sprite(0, 5010, 10000, 10);
    bottomWall.visible = false;
    bottomWall.collider = "kinematic";

    rightWall = new Sprite(5010, 0, 10, 10000);
    rightWall.visible = false;
    rightWall.collider = "kinematic";

    for (var i = 0; i < 3; i++) {
        NPCs.push(new NPC());
    }

    textSize(32);
}

function draw() {
    clear();
    background("green");

    camera.x = player.x;
    camera.y = player.y;

    stroke(0);
    strokeWeight(8);
    noFill();
    rect(windowWidth - 215, windowHeight - 50, 200, 35, 3);

    noStroke();
    fill("#00cc44");
    rect(windowWidth - 215, windowHeight - 50, map(HP, 0, MaxHP, 0, 200), 35, 3);

    fill("black");
    rect(15, windowHeight - 215, 200, 200, 12);

    var minimapX = map(player.x, -5200, 5200, 15, 215);
    var minimapY = map(player.y, -5200, 5200, windowHeight - 215, windowHeight - 15);
    
    fill("white");
    circle(minimapX, minimapY, 8);

    NPCs.forEach((NP, index) => {
        NP.minimapX = map(NP.x, -5200, 5200, 15, 215);
        NP.minimapY = map(NP.y, -5200, 5200, windowHeight - 215, windowHeight - 15)
        
        fill("blue");
        circle(NP.minimapX, NP.minimapY, 8);
    });

    player.rotation = player.angleTo(mouse) + 90;

    if (kb.pressing("left")) player.vel.x = -speed;
    else if (kb.pressing("right")) player.vel.x = speed;
    else player.vel.x = 0;

    if (kb.pressing("up")) player.vel.y = -speed;
    else if (kb.pressing("down")) player.vel.y = speed;
    else player.vel.y = 0;

    fill("white");
    textFont(lexendFont);
    textSize(18);
    text("HEALTH", windowWidth - 205, windowHeight - 27);
}

内容的提问来源于stack exchange,提问作者PseudoGhost

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 17:22:11