🤔 Para Refletir :
"Quem ri por último, tá com lag."
- Moge

Plugin Character Selector — Sistema de Seleção de Personagem por Cards

Moyc4now Masculino

Novato
Membro
Membro
Juntou-se
27 de Abril de 2026
Postagens
4
Bravecoins
9
Olá, pessoal.

Estou disponibilizando um plugin simples para RPG Maker MZ que cria uma tela de seleção de personagem por cards.

A proposta é permitir que o jogador escolha entre personagens já configurados. Cada personagem pode ter sua própria imagem, nome, ator e classe.

Recursos​

  • Seleção de personagem por cards.
  • Suporte a imagem de fundo.
  • Imagem individual para cada personagem.
  • Personagem selecionado em destaque.
  • Personagens não selecionados em preto e branco.
  • Aplica automaticamente o ator escolhido.
  • Aplica automaticamente a classe configurada.
  • Pode teleportar o jogador após a escolha.
  • Usa switch para impedir que o evento abra novamente.
  • Funciona diretamente sobre o mapa.
Animação.gif


Como usar​

Coloque o plugin na pasta:

js/plugins/

Ative no Gerenciador de Plugins.

Depois, em um evento, use o comando de script:

SistemaSelecao.iniciar();

Configuração recomendada do evento:


MoyCharacterSelector.start();

this.terminate();

Depois crie uma segunda página no mesmo evento usando o switch configurado no plugin. Assim a seleção não abre novamente.

Imagens​

As imagens devem ficar em:

img/pictures/

No código, use o nome da imagem sem .png.

Exemplo:

activeImage: "Actor1_1",
inactiveImage: "Actor1_1",

Para o fundo:

image: "BG",

Configuração dos personagens​

Cada personagem é configurado no bloco characters.

Exemplo:

{
id: 1,
name: "Espadachim",
actorName: "Reid",
activeImage: "Actor1_1",
inactiveImage: "Actor1_1",
actorId: 1,
classId: 1
}

Campos principais:

  • actorId: ID do ator no banco de dados.
  • classId: ID da classe aplicada ao personagem.
  • actorName: nome exibido após a escolha.
  • activeImage: imagem do card selecionado.
  • inactiveImage: imagem do card não selecionado.

Código​

Javascript:
/*:
 * @target MZ
 * @plugindesc v1.8.1 - Character selection scene with background, card layout, class assignment, and grayscale inactive states.
 * @author Moycanow
 *
 * @help
 * ============================================================================
 * Moycanow Character Selector
 * ============================================================================
 *
 * This plugin provides a lightweight character selection interface for
 * RPG Maker MZ. It allows the player to choose between predefined characters,
 * each one linked to a specific Actor ID and Class ID.
 *
 * The selection screen is rendered directly over the map scene and supports:
 *
 * - Custom background image
 * - Automatic background scaling
 * - Character cards
 * - Card image fitting and masking
 * - Selected card scaling
 * - Inactive card opacity
 * - True grayscale filter for inactive characters
 * - Actor name override
 * - Class assignment on confirmation
 * - Optional party cleanup before adding the selected actor
 * - Optional player transparency during selection
 * - Optional map transfer after selection
 * - Switch-based selection lock to prevent rerunning the event
 *
 * ============================================================================
 * Usage
 * ============================================================================
 *
 * Use a Script command in an event:
 *
 *     SistemaSelecao.iniciar();
 *
 * or:
 *
 *     MoyCharacterSelector.start();
 *
 * Recommended event setup:
 *
 * Page 1:
 * - Trigger: Autorun or Parallel
 * - Script:
 *
 *     SistemaSelecao.iniciar();
 *     this.erase();
 *
 * Page 2:
 * - Condition: Switch defined in CONFIG.doneSwitchId is ON
 * - No commands required
 *
 * ============================================================================
 * Image Setup
 * ============================================================================
 *
 * Character card images must be placed in:
 *
 *     img/pictures/
 *
 * Example:
 *
 *     img/pictures/Actor1_1.png
 *
 * In the plugin configuration, reference the file without extension:
 *
 *     activeImage: "Actor1_1"
 *     inactiveImage: "Actor1_1"
 *
 * Background image also uses img/pictures/ and must be referenced without
 * extension:
 *
 *     background.image: "BG"
 *
 * ============================================================================
 * Configuration Notes
 * ============================================================================
 *
 * - actorId controls which Actor from the database is added to the party.
 * - classId controls which Class is assigned to that Actor.
 * - actorName overrides the Actor's displayed name after selection.
 * - activeImage is used while the card is selected.
 * - inactiveImage is used while the card is not selected.
 * - If inactiveImage is empty, activeImage can still be reused by assigning
 *   the same filename to both fields.
 *
 * ============================================================================
 * Terms of Use
 * ============================================================================
 *
 * Free to use in commercial and non-commercial RPG Maker MZ projects.
 * Credit "Moycanow" when used or redistributed.
 *
 * Redistribution is allowed as long as this header remains intact.
 * Modification is allowed for personal or project-specific needs.
 *
 * ============================================================================
 */

var MoyCharacterSelector = MoyCharacterSelector || {};
var SistemaSelecao = SistemaSelecao || {};

(function() {
    "use strict";

    const CONFIG = {
        selectionVariableId: 1,
        doneSwitchId: 10,

        clearPartyBeforeAdd: true,
        keepLevelOnClassChange: false,
        transparentPlayerDuringSelection: true,

        background: {
            enabled: true,

            image: "BG",

            opacity: 255,
            fallbackColor: "rgba(0, 0, 0, 0.80)",

            autoFitScreen: true,

            offsetX: 120,
            offsetY: 0,

            extraScale: 1.20
        },

        transfer: {
            enabled: true,
            mapId: 1,
            x: 1,
            y: 12,
            direction: 2,
            fadeType: 0
        },

        layout: {
            startY: 150,

            boxWidth: 165,
            boxHeight: 300,

            spacingX: 14,

            imagePadding: 0,

            imageZoom: 1.05,

            fitMode: "cover",

            inactiveOpacity: 170,

            selectedBrightness: 0.50,
            selectedContrast: 0.75,

            inactiveBrightness: 0.78,
            inactiveContrast: 1.08,
            inactiveBlackAndWhite: true,

            selectedCardScale: 1.04,
            inactiveCardScale: 1.00,

            nameFontSize: 20,
            infoFontSize: 14,

            textPanelHeight: 72
        },

        characters: [
            {
                id: 1,
                name: "Espadachim",
                actorName: "Reid",

                activeImage: "Actor1_1",
                inactiveImage: "Actor1_1",

                actorId: 1,
                classId: 1
            },
            {
                id: 2,
                name: "Artista Marcial",
                actorName: "Gale",

                activeImage: "Actor1_3",
                inactiveImage: "Actor1_3",

                actorId: 3,
                classId: 5
            },
            {
                id: 3,
                name: "Mago",
                actorName: "Albert",

                activeImage: "Actor1_5",
                inactiveImage: "Actor1_5",

                actorId: 5,
                classId: 2
            },
            {
                id: 4,
                name: "Sacerdote",
                actorName: "Eliot",

                activeImage: "Actor1_7",
                inactiveImage: "Actor1_7",

                actorId: 7,
                classId: 3
            }
        ]
    };

    let _active = false;
    let _lastSelection = 0;
    let _rootLayer = null;
    let _backgroundSprite = null;
    let _characterContainers = [];

    function hasImageName(name) {
        return name && String(name).trim().length > 0;
    }

    function maxSelection() {
        return CONFIG.characters.length;
    }

    function currentSelection() {
        let value = Number($gameVariables.value(CONFIG.selectionVariableId));

        if (value < 1 || value > maxSelection()) {
            value = 1;
            $gameVariables.setValue(CONFIG.selectionVariableId, value);
        }

        return value;
    }

    function characterBySelection(selection) {
        return CONFIG.characters.find(function(character) {
            return Number(character.id) === Number(selection);
        });
    }

    function characterIndex(character) {
        return CONFIG.characters.indexOf(character);
    }

    function totalCardsWidth() {
        const count = CONFIG.characters.length;
        return count * CONFIG.layout.boxWidth + (count - 1) * CONFIG.layout.spacingX;
    }

    function computedStartX() {
        const total = totalCardsWidth();
        return Math.max(10, Math.floor((Graphics.boxWidth - total) / 2));
    }

    function characterX(character) {
        if (character.x !== undefined) {
            return Number(character.x);
        }

        return computedStartX() + characterIndex(character) * (CONFIG.layout.boxWidth + CONFIG.layout.spacingX);
    }

    function characterY(character) {
        if (character.y !== undefined) {
            return Number(character.y);
        }

        return CONFIG.layout.startY;
    }

    function className(classId) {
        if ($dataClasses && $dataClasses[classId]) {
            return $dataClasses[classId].name;
        }

        return "Class ID: " + classId;
    }

    function removeSprite(sprite) {
        if (sprite && sprite.parent) {
            sprite.parent.removeChild(sprite);
        }
    }

    function clearSelectionLayer() {
        for (const container of _characterContainers) {
            removeSprite(container);
        }

        _characterContainers = [];

        removeSprite(_backgroundSprite);
        _backgroundSprite = null;

        removeSprite(_rootLayer);
        _rootLayer = null;
    }

    function createRootLayer(scene) {
        clearSelectionLayer();

        _rootLayer = new Sprite();
        scene.addChild(_rootLayer);
    }

    function fitBackgroundSprite(sprite) {
        if (!sprite || !sprite.bitmap || sprite.bitmap.width <= 0 || sprite.bitmap.height <= 0) {
            return;
        }

        const screenW = Graphics.boxWidth;
        const screenH = Graphics.boxHeight;

        const baseScaleX = screenW / sprite.bitmap.width;
        const baseScaleY = screenH / sprite.bitmap.height;
        const baseScale = Math.max(baseScaleX, baseScaleY);

        const extraScale = Number(CONFIG.background.extraScale || 1);
        const scale = baseScale * extraScale;

        sprite.scale.x = scale;
        sprite.scale.y = scale;

        let x = Math.floor((screenW - sprite.bitmap.width * scale) / 2) + Number(CONFIG.background.offsetX || 0);
        let y = Math.floor((screenH - sprite.bitmap.height * scale) / 2) + Number(CONFIG.background.offsetY || 0);

        const scaledW = sprite.bitmap.width * scale;
        const scaledH = sprite.bitmap.height * scale;

        if (scaledW > screenW) {
            const minX = screenW - scaledW;
            const maxX = 0;
            x = Math.max(minX, Math.min(maxX, x));
        } else {
            x = Math.floor((screenW - scaledW) / 2);
        }

        if (scaledH > screenH) {
            const minY = screenH - scaledH;
            const maxY = 0;
            y = Math.max(minY, Math.min(maxY, y));
        } else {
            y = Math.floor((screenH - scaledH) / 2);
        }

        sprite.x = Math.floor(x);
        sprite.y = Math.floor(y);
    }

    function createBackground() {
        if (!_rootLayer || !CONFIG.background.enabled) {
            return;
        }

        removeSprite(_backgroundSprite);
        _backgroundSprite = null;

        if (hasImageName(CONFIG.background.image)) {
            const sprite = new Sprite();
            sprite.bitmap = ImageManager.loadPicture(CONFIG.background.image);
            sprite.opacity = Number(CONFIG.background.opacity || 255);
            sprite.x = 0;
            sprite.y = 0;

            sprite.bitmap.addLoadListener(function() {
                if (CONFIG.background.autoFitScreen) {
                    fitBackgroundSprite(sprite);
                }
            });

            _backgroundSprite = sprite;
            _rootLayer.addChild(_backgroundSprite);
        } else {
            const bitmap = new Bitmap(Graphics.boxWidth, Graphics.boxHeight);

            bitmap.fillRect(
                0,
                0,
                Graphics.boxWidth,
                Graphics.boxHeight,
                CONFIG.background.fallbackColor || "rgba(0,0,0,0.80)"
            );

            _backgroundSprite = new Sprite(bitmap);
            _backgroundSprite.x = 0;
            _backgroundSprite.y = 0;

            _rootLayer.addChild(_backgroundSprite);
        }
    }

    function makeCardFrameBitmap(character, selected) {
        const w = CONFIG.layout.boxWidth;
        const h = CONFIG.layout.boxHeight;
        const textPanelHeight = CONFIG.layout.textPanelHeight;

        const bitmap = new Bitmap(w, h);

        const borderColor = selected ? "#ffe600" : "#b0b0b0";
        const panelColor = selected ? "rgba(0,0,0,0.62)" : "rgba(0,0,0,0.52)";
        const titleColor = selected ? "#ffe600" : "#ffffff";

        bitmap.fillRect(0, 0, w, h, selected ? "rgba(0,0,0,0.00)" : "rgba(0,0,0,0.05)");
        bitmap.fillRect(0, h - textPanelHeight, w, textPanelHeight, panelColor);

        bitmap.fillRect(0, 0, w, 4, borderColor);
        bitmap.fillRect(0, h - 4, w, 4, borderColor);
        bitmap.fillRect(0, 0, 4, h, borderColor);
        bitmap.fillRect(w - 4, 0, 4, h, borderColor);

        const nameY = h - textPanelHeight + 8;
        const classY = h - textPanelHeight + 36;
        const selY = h - textPanelHeight + 55;

        bitmap.fontSize = CONFIG.layout.nameFontSize;
        bitmap.textColor = titleColor;
        bitmap.outlineColor = "rgba(0,0,0,0.95)";
        bitmap.outlineWidth = 5;
        bitmap.drawText(character.actorName || ("Character " + character.id), 0, nameY, w, 26, "center");

        bitmap.fontSize = CONFIG.layout.infoFontSize;
        bitmap.textColor = "#ffffff";
        bitmap.outlineColor = "rgba(0,0,0,0.95)";
        bitmap.outlineWidth = 4;
        bitmap.drawText(className(character.classId), 0, classY, w, 20, "center");

        if (selected) {
            bitmap.fontSize = CONFIG.layout.infoFontSize;
            bitmap.textColor = "#ffe600";
            bitmap.drawText("Selected", 0, selY, w, 18, "center");
        }

        return bitmap;
    }

    function fitImageToCard(sprite, bitmap) {
        const boxW = CONFIG.layout.boxWidth;
        const boxH = CONFIG.layout.boxHeight;
        const pad = Number(CONFIG.layout.imagePadding || 0);

        const availableW = Math.max(boxW - pad * 2, 1);
        const availableH = Math.max(boxH - pad * 2, 1);

        const scaleX = availableW / bitmap.width;
        const scaleY = availableH / bitmap.height;

        let scale;

        if (CONFIG.layout.fitMode === "contain") {
            scale = Math.min(scaleX, scaleY);
        } else {
            scale = Math.max(scaleX, scaleY);
        }

        scale *= Number(CONFIG.layout.imageZoom || 1);

        sprite.anchor.x = 0.5;
        sprite.anchor.y = 0.5;

        sprite.scale.x = scale;
        sprite.scale.y = scale;

        sprite.x = Math.floor(boxW / 2);
        sprite.y = Math.floor(boxH / 2);
    }

    function createCardMask() {
        const mask = new PIXI.Graphics();

        const pad = Number(CONFIG.layout.imagePadding || 0);
        const w = CONFIG.layout.boxWidth - pad * 2;
        const h = CONFIG.layout.boxHeight - pad * 2;

        mask.beginFill(0xffffff);
        mask.drawRect(pad, pad, w, h);
        mask.endFill();

        return mask;
    }

    function makeTrueGrayFilter() {
        const filter = new PIXI.filters.ColorMatrixFilter();

        filter.matrix = [
            0.299, 0.587, 0.114, 0, 0,
            0.299, 0.587, 0.114, 0, 0,
            0.299, 0.587, 0.114, 0, 0,
            0,     0,     0,     1, 0
        ];

        return filter;
    }

    function makeBrightnessContrastFilter(brightness, contrast) {
        const filter = new PIXI.filters.ColorMatrixFilter();

        filter.brightness(Number(brightness || 1.0), false);
        filter.contrast(Number(contrast || 1.0), true);

        return filter;
    }

    function applyImageVisual(sprite, selected) {
        if (typeof PIXI !== "undefined" && PIXI.filters && PIXI.filters.ColorMatrixFilter) {
            if (selected) {
                const filter = makeBrightnessContrastFilter(
                    CONFIG.layout.selectedBrightness,
                    CONFIG.layout.selectedContrast
                );

                sprite.filters = [filter];
            } else {
                const filters = [];

                if (CONFIG.layout.inactiveBlackAndWhite) {
                    filters.push(makeTrueGrayFilter());
                }

                filters.push(makeBrightnessContrastFilter(
                    CONFIG.layout.inactiveBrightness,
                    CONFIG.layout.inactiveContrast
                ));

                sprite.filters = filters;
            }
        } else {
            sprite.filters = null;
        }
    }

    function applyCardScale(container, selected) {
        const selectedScale = Number(CONFIG.layout.selectedCardScale || 1.0);
        const inactiveScale = Number(CONFIG.layout.inactiveCardScale || 1.0);
        const scale = selected ? selectedScale : inactiveScale;

        container.scale.x = scale;
        container.scale.y = scale;

        const w = CONFIG.layout.boxWidth;
        const h = CONFIG.layout.boxHeight;

        container.pivot.x = w / 2;
        container.pivot.y = h / 2;

        const character = container._moyCharacter;
        container.x = characterX(character) + w / 2;
        container.y = characterY(character) + h / 2;
    }

    function createCharacterContainer(character) {
        const container = new Sprite();

        container.x = characterX(character);
        container.y = characterY(character);
        container._moyCharacter = character;

        container._moyImageSprite = new Sprite();
        container.addChild(container._moyImageSprite);

        container._moyMask = createCardMask();
        container.addChild(container._moyMask);
        container._moyImageSprite.mask = container._moyMask;

        container._moyFrameSprite = new Sprite();
        container.addChild(container._moyFrameSprite);

        _rootLayer.addChild(container);

        return container;
    }

    function refreshCharacterContainer(container, selected) {
        const character = container._moyCharacter;

        const imageName = selected
            ? (character.activeImage || "")
            : (character.inactiveImage || character.activeImage || "");

        applyCardScale(container, selected);

        container._moyFrameSprite.bitmap = makeCardFrameBitmap(character, selected);

        if (hasImageName(imageName)) {
            const bitmap = ImageManager.loadPicture(imageName);

            container._moyImageSprite.bitmap = bitmap;
            container._moyImageSprite.visible = true;
            container._moyImageSprite.opacity = selected ? 255 : Number(CONFIG.layout.inactiveOpacity || 170);

            applyImageVisual(container._moyImageSprite, selected);

            bitmap.addLoadListener(function() {
                fitImageToCard(container._moyImageSprite, bitmap);
            });
        } else {
            container._moyImageSprite.bitmap = null;
            container._moyImageSprite.visible = false;
            container._moyImageSprite.filters = null;
        }
    }

    function createSelectionUI(scene) {
        createRootLayer(scene);
        createBackground();

        _characterContainers = CONFIG.characters.map(function(character) {
            return createCharacterContainer(character);
        });

        refreshSelectionUI(true);
    }

    function refreshSelectionUI(force) {
        if (!_rootLayer) {
            return;
        }

        const selection = currentSelection();

        if (!force && selection === _lastSelection) {
            return;
        }

        _lastSelection = selection;

        for (const container of _characterContainers) {
            const character = container._moyCharacter;
            const selected = Number(character.id) === Number(selection);
            refreshCharacterContainer(container, selected);
        }
    }

    function removeAllPartyMembers() {
        const members = $gameParty.members().slice();

        for (const member of members) {
            $gameParty.removeActor(member.actorId());
        }
    }

    function applyCharacter(character) {
        if (!character) {
            console.error("[MoyCharacterSelector] Character configuration not found.");
            SoundManager.playBuzzer();
            return false;
        }

        const actorId = Number(character.actorId || 0);
        const classId = Number(character.classId || 0);

        if (!$dataActors[actorId]) {
            console.error("[MoyCharacterSelector] Invalid Actor ID:", actorId);
            SoundManager.playBuzzer();
            return false;
        }

        if (!$dataClasses[classId]) {
            console.error("[MoyCharacterSelector] Invalid Class ID:", classId);
            SoundManager.playBuzzer();
            return false;
        }

        if (CONFIG.clearPartyBeforeAdd) {
            removeAllPartyMembers();
        }

        const actor = $gameActors.actor(actorId);

        if (!actor) {
            console.error("[MoyCharacterSelector] Failed to load actor:", actorId);
            SoundManager.playBuzzer();
            return false;
        }

        if (character.actorName) {
            actor.setName(character.actorName);
        }

        actor.changeClass(classId, CONFIG.keepLevelOnClassChange);
        actor.refresh();

        $gameParty.addActor(actorId);
        $gamePlayer.refresh();

        return true;
    }

    function moveLeft() {
        let selection = currentSelection() - 1;

        if (selection < 1) {
            selection = maxSelection();
        }

        $gameVariables.setValue(CONFIG.selectionVariableId, selection);
        SoundManager.playCursor();

        _lastSelection = 0;
        refreshSelectionUI(true);
    }

    function moveRight() {
        let selection = currentSelection() + 1;

        if (selection > maxSelection()) {
            selection = 1;
        }

        $gameVariables.setValue(CONFIG.selectionVariableId, selection);
        SoundManager.playCursor();

        _lastSelection = 0;
        refreshSelectionUI(true);
    }

    function finishSelection() {
        clearSelectionLayer();

        if (CONFIG.transparentPlayerDuringSelection) {
            $gamePlayer.setTransparent(false);
        }

        if (CONFIG.doneSwitchId > 0) {
            $gameSwitches.setValue(CONFIG.doneSwitchId, true);
        }

        _active = false;
    }

    function confirmSelection() {
        const character = characterBySelection(currentSelection());
        const success = applyCharacter(character);

        if (!success) {
            return;
        }

        SoundManager.playOk();
        finishSelection();

        if (CONFIG.transfer.enabled) {
            $gamePlayer.reserveTransfer(
                Number(CONFIG.transfer.mapId || 1),
                Number(CONFIG.transfer.x || 0),
                Number(CONFIG.transfer.y || 0),
                Number(CONFIG.transfer.direction || 2),
                Number(CONFIG.transfer.fadeType || 0)
            );
        }
    }

    MoyCharacterSelector.start = function() {
        if (CONFIG.doneSwitchId > 0 && $gameSwitches.value(CONFIG.doneSwitchId)) {
            return;
        }

        _active = true;
        _lastSelection = 0;

        if (currentSelection() < 1) {
            $gameVariables.setValue(CONFIG.selectionVariableId, 1);
        }

        if (CONFIG.transparentPlayerDuringSelection) {
            $gamePlayer.setTransparent(true);
        }

        if (SceneManager._scene && SceneManager._scene instanceof Scene_Map) {
            createSelectionUI(SceneManager._scene);
        }
    };

    MoyCharacterSelector.stop = function() {
        finishSelection();
    };

    MoyCharacterSelector.refresh = function() {
        _lastSelection = 0;
        refreshSelectionUI(true);
    };

    MoyCharacterSelector.config = function() {
        return CONFIG;
    };

    SistemaSelecao.iniciar = function() {
        MoyCharacterSelector.start();
    };

    SistemaSelecao.parar = function() {
        MoyCharacterSelector.stop();
    };

    SistemaSelecao.config = function() {
        return CONFIG;
    };

    const _Scene_Map_update = Scene_Map.prototype.update;

    Scene_Map.prototype.update = function() {
        _Scene_Map_update.call(this);

        if (!_active) {
            return;
        }

        if (!_rootLayer) {
            createSelectionUI(this);
        }

        if (Input.isTriggered("left")) {
            moveLeft();
            return;
        }

        if (Input.isTriggered("right")) {
            moveRight();
            return;
        }

        if (Input.isTriggered("ok")) {
            confirmSelection();
            return;
        }

        refreshSelectionUI(false);
    };

})();

Créditos​

Plugin por Moycanow.

Pode usar em projetos comerciais e não comerciais.
Pode editar conforme a necessidade do projeto.
Se redistribuir, mantenha os créditos.
 

Anexos

  • Captura de tela 2026-05-30 012529.png
    Captura de tela 2026-05-30 012529.png
    412,7 KB · Visualizações: 6
Voltar
Topo Inferior