LoginSignup
15
8

More than 5 years have passed since last update.

Slay the SpireのMODの作り方

Last updated at Posted at 2019-02-11

Slay the Spire の MODを楽して作りたい

わかる

というわけで Slay the SpireのMODの作り方講座です。

BaseModのGit内のwiki
https://github.com/daviscook477/BaseMod/wiki/Getting-Started-(For-Modders)

Mod The SpireのGit内のwikiにあるModリスト
https://github.com/kiooeht/ModTheSpire/wiki/List-of-Known-Mods

Slay the Spire Mod開発入門
https://qiita.com/kojim/items/0c7164c78a5a909b4478
https://qiita.com/kojim/items/1a97f200fc8e545cee13
https://qiita.com/kojim/items/f61d9f3553e2d045aa2e

以上、見てやればできます。先人に感謝。

……ってだけだと作る人が増えてくれないので、BaseModのwikiの「Getting Started With Modding Slay The Spire」を解説します。
序盤は開発環境の構築ですけど、Java開発環境ある人は斜め読みしてください。
というかそもそもこの記事は、超初心者向けです。

開発環境

まずフォルダ作れって言われてますね。どこでもいいんですけど、フォルダ作ってください。MODこれからたくさん作るぜっての名前のフォルダ(my_modsとか)で。で、その中に「lib」ってフォルダ作れと言われてますので作ります。そこにBaseMod.jarとModTheSpire.jarを入れます。

BaseMod.jar
https://github.com/daviscook477/BaseMod/releases
ModTheSpire.jar
https://github.com/kiooeht/ModTheSpire/releases

BaseModの方はModのAPIで、ModTheSpireはSlay the SpireをMOD入りで動かすために必要です。まあどっちも必要です。はい。あ、SteamのWorkshopからも入れられます。入れときましょう。

それから同じフォルダに、Slay the Spireのフォルダの中からdesktop-1.0.jarを探して入れておいてください。

さて「Environment Setup」の項目ですが、開発環境なので好きなのを選んでください。
簡単って書いてあったので僕はInteliJでやりましたし、以後その説明しかしないです。というわけまず、Java SE Development Kit 8をインストールして、そのあとInteliJもインストールしてください。

でInteliJを起動したらCreate New Projectしてくださいって書いてあるのでします。
JDK1.8とMaven選んでNext、GroupIDとArtifactIdはこれから作るModの名前にしましょう例はExampleModになってます。
最後にさっき作ったフォルダの中にプロジェクトファイルが作られるようにしてFinish。

えーと、そしたら急に重要なことがぽんっと書いてあるんですけど、プロジェクトフォルダ直下にpom.xmlがあると思うのでそれを編集します。

Example Mod pom.xml
https://gist.github.com/alexdriedger/fb74397086ee80073417f19d6305bb05

ここでさっきlibフォルダに入れた3つのjarファイルの在処と、そのバージョン指定がされてます。ディレクトリ構造が例と違う場合は注意してください。

あと確かここで54行目からのpackageの部分編集が必要だったと思います。ビルドした後の出力先なので書いてください。

pomの編集が終わったら「Maven projects need to be imported」とかいうポップアップが出てると思うので、Import Changesを押して反映。でもってCtrl+Alt+Shift+Sを押してStructureの画面を出したらModulesを選んで、Mavenが3つ出てるからチェック入れてApplyです。

これでとりあえず「View > Tool Windows > Maven Projects」で開いたウィンドウの中の「Lifecycle > package」をダブルクリックするとビルドできてtargetフォルダ内に.jarファイルができるようになります。

適当なキャラを作るまでがんばろう

カラーとカード追加

Slay the Spire のキャラクラスはCOLORって呼ばれていて、アイアンクラッドがRED、サイレントがGREEN、ディフェクトがBLUEです。新しいキャラを作ろうと思ったら、まずこのCOLORを追加しないといけません(こいつらに対して追加カードを入れるだけなら楽なんだけど)。

Custom Colors
https://github.com/daviscook477/BaseMod/wiki/Custom-Colors

これだけ読んで理解するの難しいので、ここから先は自前で解説します。一つずつ行きましょう。
まずはカラーとカードを追加します。カラーだけだと追加されたかよくわからんので、一気にカード追加までやりきります。

あと説明めんどくさいので「よく分からん」ってなったら以下のリポジトリ見てください。画像用意するのめんどくせえ、とかも、ここからパクってください。
https://github.com/levelnineteen/stsmod

プロジェクト作った時点で構成的にはこうなってると思うんですけど、クラス(プログラム部)はjavaの下、テキストとか画像はresourceの下に置きます。

examle_mod
+.idea
+src
-+main
--+java
--+resource

ではまずjava以下にmod名のディレクトリ、さらにその下にpatchesってディレクトリを作って、以下二つのクラスを作ります。

examle_mod
+.idea
+src
-+main
--+java
---+example_mod
----+patches
--+resource

AbstractCardEnum.java


package example_mod.patches; //ここは自動で入ると思います

import com.evacipated.cardcrawl.modthespire.lib.SpireEnum;
import com.megacrit.cardcrawl.cards.AbstractCard;

public class AbstractCardEnum {

    @SpireEnum
    public static AbstractCard.CardColor EXAMPLE_COLOR; //好きな変数名。これからずっと使う。

}

LibraryTypeEnum.java


package example_mod.patches; //ここは自動で入ると思います

import com.evacipated.cardcrawl.modthespire.lib.SpireEnum;
import com.megacrit.cardcrawl.helpers.CardLibrary;

public class LibraryTypeEnum {

    @SpireEnum
    public static CardLibrary.LibraryType EXAMPLE_COLOR; //上と同じだよ。これからずっと使う。

}

下準備できたのでjava/example_mod直下にMain.javaもしくはまあ分かりやすいように、MOD名.javaクラスを作ります。

Main.java


import basemod.BaseMod;
import basemod.interfaces.*;
import com.evacipated.cardcrawl.modthespire.lib.SpireInitializer;
import com.megacrit.cardcrawl.core.Settings;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.megacrit.cardcrawl.helpers.CardHelper;
import com.megacrit.cardcrawl.localization.CardStrings;
import com.megacrit.cardcrawl.localization.CharacterStrings;

import example_mod.patches.*; //さっき作ったパッチを読んでおきます

@SpireInitializer
public class Main implements
        EditCardsSubscriber,   // カードを追加する場合にimplementする
        EditStringsSubscriber,  // 言語ファイルを読み込む場合に implementする
{
    //まずはカラーの基本設定用の変数です。
    private static final Color EXAMPLE_COLOR_BG = CardHelper.getColor(100.0f, 50.0f, 50.0f); //カードリスト選択とかに出てくるときのバーの背景の色です。
    private static final String ATTACK_EXAMPLE         = "img/cards/bg_attack_512.png"; //この辺は後で説明します
    private static final String SKILL_EXAMPLE          = "img/cards/bg_skill_512.png"; //とりあえず変数名を書いてください
    private static final String POWER_EXAMPLE           = "img/cards/bg_power_512.png";
    private static final String ENERGY_ORB_EXAMPLE         = "img/cards/orb_512.png";
    private static final String ATTACK_PORT_EXAMPLE        = "img/cards/bg_attack_1024.png";
    private static final String SKILL_PORT_EXAMPLE         = "img/cards/bg_skill_1024.png";
    private static final String POWER_PORT_EXAMPLE         = "img/cards/bg_power_1024.png";
    private static final String ENERGY_ORB_PORT_EXAMPLE = "img/cards/orb_1024.png";
    private static final String ENERGY_ORB_CARD_EXAMPLE = "img/cards/orb_ui.png";

    public Main(){
        BaseMod.subscribe(this);
        BaseMod.addColor(
                //カラー追加部分です。上で設定した変数を入れていくだけ。
                AbstractCardEnum.EXAMPLE_COLOR    //color
                , EXAMPLE_COLOR_BG //bgColor
                , EXAMPLE_COLOR_BG//backColor
                , EXAMPLE_COLOR_BG//frameColor
                , EXAMPLE_COLOR_BG//frameOutlineColor
                , EXAMPLE_COLOR_BG//descBoxColor
                , EXAMPLE_COLOR_BG //trailVfColor
                , EXAMPLE_COLOR_BG//glowColor
                , ATTACK_EXAMPLE//attackBg
                , SKILL_EXAMPLE//skillBg
                , POWER_EXAMPLE//powerBG
                , ENERGY_ORB_EXAMPLE//energyOrb
                , ATTACK_PORT_EXAMPLE//attackBgPortrait
                , SKILL_PORT_EXAMPLE//skillBgPortrait
                , POWER_PORT_EXAMPLE//powerBgPortrait
                , ENERGY_ORB_PORT_EXAMPLE//energyOrbPortrait
                , ENERGY_ORB_CARD_EXAMPLE//CardEnergyOrb
        );
    }

    public static void initialize() {
        Main main = new Main();
    }

    @Override
    public void receiveEditStrings() {
        //言語ファイルとかはこのあと説明します。
        BaseMod.loadCustomStringsFile(CardStrings.class, "localization/cards-" + Settings.language + ".json");
    }

    @Override
    public void receiveEditCards() {
        //カード追加部分です。これもこのあと説明します。
        BaseMod.addCard(new example_mod.cards.TestAttack());
    }
}

まだ書くことがあります。肝心のカードの内容を書かないといけないので。
java/example_mod直下にcardsってディレクトリを用意して、以下のクラスを入れます。

TestAttack.java


package example_mod.cards; //まあここは自動で入るはず

import basemod.abstracts.CustomCard;
import com.megacrit.cardcrawl.actions.AbstractGameAction;
import com.megacrit.cardcrawl.cards.AbstractCard;
import com.megacrit.cardcrawl.cards.DamageInfo;
import com.megacrit.cardcrawl.characters.AbstractPlayer;
import com.megacrit.cardcrawl.core.CardCrawlGame;
import com.megacrit.cardcrawl.dungeons.AbstractDungeon;
import com.megacrit.cardcrawl.localization.CardStrings;
import com.megacrit.cardcrawl.monsters.AbstractMonster;
import example_mod.patches.AbstractCardEnum; //これをimportしないとカードカラーが扱えない

public class TestAttack extends CustomCard {
    public static final String ID = "examplemod:TestAttack"; //言語ファイルの参照IDなので間違えないように。
    private static CardStrings cardStrings = CardCrawlGame.languagePack.getCardStrings(ID);
    public static final String NAME = cardStrings.NAME;
    public static final String DESCRIPTION = cardStrings.DESCRIPTION;
    public static final String IMG_PATH = "img/cards/card.png"; //カードの絵柄です。後で説明します。
    private static final int COST = 0; //エネルギーコスト
    private static final int ATTACK_DMG = 4; //ダメージ
    private static final int UPGRADE_PLUS_DMG = 3; //アップグレードしたときのダメージ

    public TestAttack() {
        super(ID, NAME, IMG_PATH, COST, DESCRIPTION,
                CardType.ATTACK, //カードの種類
                AbstractCardEnum.EXAMPLE_COLOR, //さっきから使ってるカードカラーの変数。間違えない
                CardRarity.COMMON,   //カードのレアリティ
                CardTarget.ENEMY //ターゲットが誰なのか
        );
        this.damage=this.baseDamage = ATTACK_DMG;
    }

    @Override
    public void use(AbstractPlayer p, AbstractMonster m) {
        // ダメージを与える
        AbstractDungeon.actionManager.addToBottom(
                new com.megacrit.cardcrawl.actions.common.DamageAction(
                        m,
                        new DamageInfo(p, this.damage, this.damageTypeForTurn),
                        AbstractGameAction.AttackEffect.SLASH_DIAGONAL) // 画面効果
        );
    }

    @Override
    public AbstractCard makeCopy() {
        return new TestAttack();
    }

    // カードアップグレード時の処理
    @Override
    public void upgrade() {
        if (!this.upgraded) {
            upgradeName();
            upgradeDamage(UPGRADE_PLUS_DMG);
        }
    }
}

はいお疲れさまでした。新種のカード追加するだけでやることたくさんですね。まだありますよ。

examle_mod
+.idea
+src
-+main
--+java
---Main.java
---+example_mod
----+cards
-----TestAttack.java
----+paches
-----LibraryTypeEnum.java
-----AbstractCardEnum.java
--+resource

さてリソースに画像ファイルと言語ファイルを追加する必要があります。resource以下にimg/cardsディレクトリ、localizationディレクトリを作りましょう。
img/cardsの中には、以下のファイルが必要です。全部透過pngファイルです。

bg_attack_512.png アタックカードの背景。512x512
bg_skill_512.png  スキルカードの背景。512x512
bg_power_512.png パワーカードの背景。512x512 カード名の下の帯は用意しなくてもOKです。
orb_512.png    エネルギーオーブ。512x512 カードの上に載せられるので左上に小さく描かないといけない。
bg_attack_1024.png この辺は512のを1024x1024に拡大すればいいです。
bg_skill_1024.png
bg_power_1024.png
orb_1024.png    ところがこいつは512を拡大しちゃいけないです(中心点違うし)。まあこの大きいのをそれぞれ縮小して使うのがいいのでは。
orb_ui.png     たぶんツールチップとかで使われるときの表示。23x23
card.png      カードの絵柄。本来はカード名(カードクラス名)と合わせるのが分かりやすいです。まあテストなので。250x190
card_p.png     絵柄の大きいバージョン。上のを拡大でいいです。500x380。自動で使われるのでどのクラスでも特に明記しません。

localizationの中には、以下のjsonファイルが必要です。

cards-JPN.json


{
  "examplemod:TestAttack": { //mod用識別子:カード名、で書く。でないとかぶっちゃうしね。
  "NAME": "カード名",
  "DESCRIPTION": "カードの説明文。!d!って書くとダメージの数値が出たりするけどそこは調べてください"
   }
}

まあ海外製なのでcards-ENG.jsonも作って、英語用意しておいてください。

そういや忘れてましたけどresources直下に以下のjsonがないとMod選択時にいろいろ言われるので入れといてください。

ModTheSpire.json

{
  "modid": "examplemod",
  "name": "examplemod",
  "author_list": ["yourname"],
  "description": "",
  "version": "0.0.1",
  "sts_version": "01-01-2019",  //Slay the Spireがどのバージョン以上ならいいのかとか
  "mts_version": "3.6.0", //Mod the Spireがどのバージョン以上ならいいのかって話だと思うので適当に
  "dependencies": ["basemod"],
  "update_json": ""
}

お疲れ様です。これで動くようになったはずです。

--+java
---Main.java
---+example_mod
----+cards
-----TestAttack.java
----+paches
-----LibraryTypeEnum.java
-----AbstractCardEnum.java
--+resource
---ModTheSpire.json
---+img
----+cards
-----カード用の画像いっぱい
---+localization
----cards-JPN.json
----cards-ENG.json

「View > Tool Windows > Maven Projects」で開いたウィンドウの中の「Lifecycle > package」をダブルクリックするとビルドできてtargetフォルダ内に.jarファイルができると思います。エラー出たら対処してください。

MODを起動する

Steamライブラリの中のsteamapps\common\SlayTheSpireの中に、modsってフォルダを作ります。
先ほど作ったexample_mod.jarと、BaseMod.jarをこのmodsの中に入れてください。

steamapps\common\SlayTheSpire直下には、ModTheSpire.zip解凍したときに中に入ってた、MTS.cmdを入れてください。Macの人は.shのほうらしいですよ。

で、MTS.cmdを実行すると、

image.png

ってなるので、BaseModとexample_modにチェックを入れて、あとDebugにもチェック入れといてPlayを押してください。なんかexample_modが灰色で選べない、っていうのはModTheSpire.jsonで指定したmts_versionとかが間違ってます。

これで無事起動して、カードライブラリにexample_modっていうのが追加されてて、かっこいいカードが追加されていればここまで成功です。お疲れさまでした。

キャラを追加する……前に、レリックとかパワーとか

キャラ追加しようと思ったら、スターターレリックは必要だし、カードもスキルの他にパワーが必要になるわけです。
というわけで一気にいきましょう。レリックの追加からです。java/example_mod以下にrelicsディレクトリを作って、以下のクラスを作ります。

TestRelic.java


package example_mod.relics;

import basemod.abstracts.CustomRelic;
import com.megacrit.cardcrawl.helpers.ImageMaster;
import com.megacrit.cardcrawl.relics.AbstractRelic;

//能力によって入れる
import com.megacrit.cardcrawl.dungeons.AbstractDungeon;
import com.megacrit.cardcrawl.powers.RegenPower;
import com.megacrit.cardcrawl.actions.common.ApplyPowerAction;
import com.megacrit.cardcrawl.actions.common.RelicAboveCreatureAction;

public class TestRelic extends CustomRelic {

    public static final String ID = "examplemod:TestRelic";
    public static final String IMG = "img/relics/TestRelic.png";
    public static final String OUTLINE_IMG = "img/relics/outline/TestRelic.png";

    public TestRelic(){
        super(
                ID,
                ImageMaster.loadImage(IMG),
                ImageMaster.loadImage(OUTLINE_IMG),
                RelicTier.STARTER,  //レアリティ。ここではスターターレリック
                LandingSound.FLAT
        );
    }

    public String getUpdatedDescription(){
        return DESCRIPTIONS[0];
    }

    @Override
    public AbstractRelic makeCopy(){
        return new TestRelic();
    }

    //戦闘開始時に5の再生を得る
    @Override
    public void onEquip() {
        AbstractDungeon.rareRelicPool.remove("Dead Branch");
    }

    public void atBattleStart() {
        AbstractDungeon.actionManager.addToBottom(
                new RelicAboveCreatureAction(AbstractDungeon.player, this)
        );
        AbstractDungeon.actionManager.addToBottom(
                new ApplyPowerAction(
                        AbstractDungeon.player,
                        AbstractDungeon.player,
                        new RegenPower(AbstractDungeon.player, 5),
                        5
                )
        );
    }
}

resources/img以下にrelicsディレクトリを作って、以下の画像ファイルを用意してください。

TestRelic.png  128x128の透過png

さらにrelicsディレクトリ以下にoutlineディレクトリを作って、以下の画像ファイルを用意してください。

TestRelic.png  128x128の透過png。上のディレクトリにある画像を白く塗りつぶしたやつ。

さてテキスト用のファイルも必要なので、resources/localizationの中に以下のファイルを用意してください。

relics-JPN.json


{
  "examplemod:TestRelic": {
    "NAME": "レリック名",
    "FLAVOR": "フレーバーテキスト",
    "DESCRIPTIONS": [
      "説明文。Sと複数形になってるのに注意。変数を説明文に入れるときは分解して間に挟む"
    ]
  }
}

海外製なのでrelics-ENG.jsonも作って、英語用意しておいてください。

続いてパワーの追加をします。パワー自体の追加と、パワーを得るカードの追加です。
example_mod以下にpowersディレクトリを作って、以下のクラスを作ってください。

TestPowerGold.java


package example_mod.powers;

import com.badlogic.gdx.graphics.Texture;
import com.megacrit.cardcrawl.core.AbstractCreature;
import com.megacrit.cardcrawl.core.CardCrawlGame;
import com.megacrit.cardcrawl.powers.AbstractPower;
import com.megacrit.cardcrawl.localization.PowerStrings;

public class TestPowerGold extends AbstractPower {
    public static final String POWER_ID = "examplemod:TestPowerGold";
    private static final PowerStrings powerStrings =
            CardCrawlGame.languagePack.getPowerStrings(POWER_ID);
    public static final String NAME = powerStrings.NAME;
    public static final String[] DESCRIPTIONS = powerStrings.DESCRIPTIONS;

    public TestPowerGold(AbstractCreature owner, int amount) {
        this.name = NAME;
        this.ID = POWER_ID;
        this.owner = owner;
        this.amount = amount;
        this.type = AbstractPower.PowerType.BUFF;
        updateDescription();
        this.img = new Texture("img/powers/TestPower.png");
    }

    public void updateDescription() {
        this.description = (DESCRIPTIONS[0] + this.amount + DESCRIPTIONS[1]); //説明文の分割ってこういうことです。
    }
}

テキスト用のファイルも必要なので、resources/localizationの中に以下のファイルを用意してください。

powers-JPN.json


{
  "examplemod:TestPowerGold": {
    "NAME": "パワー名",
    "DESCRIPTIONS": [
      "戦闘終了時ゴールド",
      "を得る"
      ]
  }
}

ここで用意されたのはパワーのひな形です。具体的なパワーはさらにカードの方で書く必要があります。
example_mod/cardsの中に、以下のクラスを用意します。

TestPower.java


package example_mod.cards;

import basemod.abstracts.CustomCard;
import com.megacrit.cardcrawl.actions.common.ApplyPowerAction;
import com.megacrit.cardcrawl.cards.AbstractCard;
import com.megacrit.cardcrawl.characters.AbstractPlayer;
import com.megacrit.cardcrawl.core.CardCrawlGame;
import com.megacrit.cardcrawl.dungeons.AbstractDungeon;
import com.megacrit.cardcrawl.localization.CardStrings;
import com.megacrit.cardcrawl.monsters.AbstractMonster;

import example_mod.patches.AbstractCardEnum;
import example_mod.powers.*; //さっき作ったパワーのやつ。めんどいので*

import com.megacrit.cardcrawl.rooms.AbstractRoom.RoomPhase;


public class TestPower extends CustomCard {
    public static final String ID = "examplemod:TestPower";
    private static CardStrings cardStrings = CardCrawlGame.languagePack.getCardStrings(ID);
    public static final String NAME = cardStrings.NAME;
    public static final String DESCRIPTION = cardStrings.DESCRIPTION;
    public static final String IMG_PATH = "img/cards/card.png"; //カードのイメージ。とりあえず使いまわす
    private static final int COST = 0; //エネルギーコスト
  //カード独自の変数
    private static final int GETMONEY = 100;
    private static final int UPGRADE_GETMONEY = 10;

    public TestPower() {
        super(ID, NAME, IMG_PATH, COST, DESCRIPTION,
                CardType.POWER,
                AbstractCardEnum.EXAMPLE_COLOR, //何度も言うけどカラー名
                CardRarity.COMMON,   //レアリティ
                CardTarget.SELF //カードの対象。パワーはだいたいSELF
        );
        this.magicNumber = this.baseMagicNumber = GETMONEY;
    }

    @Override
    public void use(AbstractPlayer p, AbstractMonster m) {
        // 戦闘終了時にお金をもらう能力です
        if (AbstractDungeon.getCurrRoom().phase == RoomPhase.COMBAT) {
            AbstractDungeon.getCurrRoom().addGoldToRewards(this.magicNumber);
            AbstractDungeon.actionManager.addToBottom(
                    new ApplyPowerAction(
                            p,
                            p,
                            new TestPowerGold(p, this.magicNumber), //さっきつくったパワーのやつ
                            this.magicNumber
                    )
            );
        }
    }

    @Override
    public AbstractCard makeCopy() {
        return new TestPower();
    }

    // カードアップグレード時の処理
    @Override
    public void upgrade() {
        if (!this.upgraded) {
            this.upgradeName();
            this.upgradeDamage(UPGRADE_GETMONEY);
        }
    }
}

あとはパワーのアイコン画像が必要ですね。
resources/img以下にpowersディレクトリを作って、以下の画像ファイルを用意してください。

TestPower.png 32x32の透過ファイル。パワーはこれしかない。

--+java
---Main.java
---+example_mod
----+cards
-----TestAttack.java
-----TestPower.java
----+relics
-----TestRelic.java
----+paches
-----LibraryTypeEnum.java
-----AbstractCardEnum.java
----+powers
-----TestPowerGold.java
--+resource
---ModTheSpire.json
---+img
----+cards
-----カード用の画像いっぱい
----+relics
-----TestRelic.png
-----+outline
------TestRelic.png
----+powers
-----TestPower.png
---+localization
----cards-JPN.json
----cards-ENG.json
----relics-JPN.json
----relics-ENG.json
----powers-JPN.json
----powers-ENG.json

さて、これだけだと動きません。example_mod/Main.javaに追記が必要です。

Main.javaを編集


//importに以下の追加が必要。
import com.megacrit.cardcrawl.localization.RelicStrings;
import com.megacrit.cardcrawl.localization.PowerStrings;
import example_mod.relics.*;

//implementsするところが増える
public class Main implements
        PostInitializeSubscriber,   //バッジとか表示するやつ?
        EditCardsSubscriber,   // カードを追加する場合にimplementする
        EditRelicsSubscriber,   //レリックを追加する場合にimplement
        EditStringsSubscriber,  // 言語ファイルを読み込む場合に implementする
{

//言語ファイルを増やす
   @Override
    public void receiveEditStrings() {
        BaseMod.loadCustomStringsFile(CardStrings.class, "localization/cards-" + Settings.language + ".json");
        BaseMod.loadCustomStringsFile(RelicStrings.class, "localization/relics-" + Settings.language + ".json");
        BaseMod.loadCustomStringsFile(PowerStrings.class, "localization/powers-" + Settings.language + ".json");
    }

//追加するカードを増やす
    @Override
    public void receiveEditCards() {
        BaseMod.addCard(new training_mod.cards.TestAttack());
        BaseMod.addCard(new training_mod.cards.TestPower());
    }

//レリックを追加する
    public void receiveEditRelics() {
        //レリックの追加
        BaseMod.addRelicToCustomPool(
                new TestRelic(),
                AbstractCardEnum.EXAMPLE_COLOR //カラーに対して追加する。共通レリックはまた別のメソッドになる
        );
    }

ビルドしてカード一覧にパワーカードがあって、レリック一覧のスターターレリックに表示されていれば成功です。
説明してないのにスキルカードの作成がありますが、アタックのCardTypeのところを、CardType.SKILLにするだけでとりあえずできます。

ようやくキャラクターを作る

お待たせしました。キャラクターを作りましょう。普通に考えてまずキャラ画像が必要なので、作っていきましょう。
BaseModさんの説明だと、Spriterを使えと書いてありますね。そのあとの説明はSpineで作った画像セットによる設定ですが。……Spineは有料ソフトなので、Spriterでいきましょう。

ここからダウンロードです。
https://brashmonkey.com/download_spriter/

インストールしたらとりあえず以下のチュートリアルでも流し見してください。
https://www.youtube.com/watch?v=aQy7eX_CWPM&list=PL8Vej0NhCcI5sxOT64-Z31LW59VUyAVfr&t=0s&index=2

ようするにパーツごとに透過pngファイルをつくって(腕とか足とか)、Spriterで配置して、タイムライン上でアニメーションさせて、っていうことができる楽しいソフトです。

楽しすぎてやってられないので、まずimg以下にchar/animディレクトリを作り、その下にSpriterのプロジェクトを作ります。まあexampleCharとかいうプロジェクト名でいいんじゃないでしょうか。その中にキャラ画像を配置します。

--+java
---Main.java
---+example_mod
----+cards
----+relics
----+paches
----+powers
--+resource
---+img
----+char
-----+anim
------CharacterImage.png 512x512の透過pngファイル

そうしたらCharacterImage.pngがSpriter上でも見えるようになるので、ダブルクリックして基準点を決めるやつは真下を選択して、画像を放り込んでx0、y0に配置しましょう。

image.png

いいんですよ適当で……だいたい特にアニメーションしてないし、見てないでしょう? STSのキャラ。

そんなことよりほかにも画像が必要なんですよ。以下のを用意してください。

--+java
---Main.java
---+example_mod
----+cards
----+relics
----+paches
----+powers
--+resource
---+img
----+char
-----ExampleCorpse.png  512x512の透過pngファイル。死んだときのやつです。
-----ExampleShoulder.png 1920x1136の透過pngファイル。キャンプとかで表示される背後からの映像です。
-----+anim
------CharacterImage.png 512x512の透過pngファイル。さっきの
------ExampleChar.scml  Spriterによって保存されたファイル。配置とかアニメーションとかの情報。このあと使います。
----+charSelect
-----ExampleButton.png 200x200の透過pngファイル。キャラセレ画面で押すボタン
-----ExamplePortrait.png 1920x1200の透過pngファイル。上のボタンを押した後表示される背景。かっこいいのよろしく。

おつかれさまです。これで素材は一通りそろいました。ではキャラ追加をしていきましょう。
まずexample_mod/pachesに以下のクラスを追加します。

ExampleClassEnum.java


package example_mod.patches;
import com.evacipated.cardcrawl.modthespire.lib.SpireEnum;
import com.megacrit.cardcrawl.characters.AbstractPlayer;

//クラス名のExampleのところは作るMODの名前でいいですよ

public class ExampleClassEnum {
    @SpireEnum
    public static AbstractPlayer.PlayerClass ExampleClass;
}

example_mod/characterディレクトリを作ってその中に以下のクラスを追加します。

ExampleChar.java


package example_mod.character;

//使ってないのもimportしちゃってるけどまあなんか必要になるときがくるんじゃないでしょうか。
import java.util.ArrayList;
import java.util.List;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import basemod.abstracts.CustomPlayer;
import basemod.animations.SpriterAnimation;

import com.megacrit.cardcrawl.actions.AbstractGameAction;
import com.megacrit.cardcrawl.cards.AbstractCard;
import com.megacrit.cardcrawl.characters.AbstractPlayer;
import com.megacrit.cardcrawl.core.CardCrawlGame;
import com.megacrit.cardcrawl.core.EnergyManager;
import com.megacrit.cardcrawl.cutscenes.CutscenePanel;
import com.megacrit.cardcrawl.dungeons.AbstractDungeon;
import com.megacrit.cardcrawl.events.city.Vampires;
import com.megacrit.cardcrawl.helpers.*;
import com.megacrit.cardcrawl.localization.CardStrings;
import com.megacrit.cardcrawl.localization.CharacterStrings;
import com.megacrit.cardcrawl.screens.CharSelectInfo;
import com.megacrit.cardcrawl.screens.stats.CharStat;
import com.megacrit.cardcrawl.unlock.UnlockTracker;
import com.megacrit.cardcrawl.actions.AbstractGameAction.AttackEffect;

import example_mod.cards.*;
import example_mod.relics.*;
import example_mod.patches.*;

public class TrainingChar extends CustomPlayer {
    public static final CharacterStrings charStrings = CardCrawlGame.languagePack.getCharacterString("examplemod:ExampleChar");
    public static final int ENERGY_PER_TURN = 3; // エネルギーが1ターンいくらもらえるか
    public static final String EXAMPLECHAR_SHOULDER_2 = "img/char/ExampleShoulder.png"; // キャンプでの背後画像
    public static final String EXAMPLECHAR_SHOULDER_1 = "img/char/ExampleShoulder.png"; // それ以外の背後画像。分けたければ作ったらいい。
    public static final String EXAMPLECHAR_CORPSE = "img/char/ExampleCorpse.png"; // 死体画像

    public ExampleChar(String name){
        super(name,
                ExampleClassEnum.ExampleClass,
                null,
                null,
                null,
                new SpriterAnimation("img/char/anim/ExampleChar.scml") //Spriterで作られたファイル
        );
        initializeClass(null,
                EXAMPLECHAR_SHOULDER_2,
                EXAMPLECHAR_SHOULDER_1,
                EXAMPLECHAR_CORPSE,
                getLoadout(),
                20.0F,
                -10.0F,
                220.0F,
                290.0F,
                new EnergyManager(ENERGY_PER_TURN)
        );

    }

    @Override
    public String getTitle(AbstractPlayer.PlayerClass playerClass){
        return charStrings.NAMES[0];
    }
    @Override
    public String getSpireHeartText(){
        return charStrings.TEXT[0];
    }
    @Override
    public String getLocalizedCharacterName() {
        return charStrings.NAMES[0]; //上のといったいどこで違うのか分からない。同じでいいのでは?
    }
    @Override
    public String getVampireText() {
        //ヴァンパイアイベントのこと。たぶん0がアイアンクラッドで1がサイレントだと思うけど分かんない。
        return Vampires.DESCRIPTIONS[0];
    }
    public Color getCardRenderColor() {
        return CardHelper.getColor(100.0f, 50.0f, 50.0f);   //EXAMPLE_COLOR_BGと同じがいい?
    }
    public Color getCardTrailColor() {
        return CardHelper.getColor(100.0f, 50.0f, 50.0f);   //EXAMPLE_COLOR_BGと同じがいい?
    }
    public Color getSlashAttackColor() {
        return CardHelper.getColor(100.0f, 50.0f, 50.0f);   //EXAMPLE_COLOR_BGと同じがいい?
    }
    public AttackEffect[] getSpireHeartSlashEffect() {
        //エフェクトの話らしいけど何があるのかよくわかりません。
        return new AttackEffect[]{
                AttackEffect.SLASH_HEAVY,
                AttackEffect.FIRE,
                AttackEffect.SLASH_DIAGONAL,
                AttackEffect.SLASH_HEAVY,
                AttackEffect.FIRE,
                AttackEffect.SLASH_DIAGONAL
        };
    }
    public AbstractCard getStartCardForEvent() {
        return new TestPower() ;    //何用か分からないけどとりあえず
    }
    public BitmapFont getEnergyNumFont() {
        return FontHelper.energyNumFontBlue;
    }
    public void doCharSelectScreenSelectEffect() {
        //キャラ選択したときのサウンドと画面エフェクトだと思う
        CardCrawlGame.sound.playV("AUTOMATON_ORB_SPAWN", 1.75f);
        CardCrawlGame.screenShake.shake(ScreenShake.ShakeIntensity.LOW, ScreenShake.ShakeDur.SHORT, true);
    }
    public String getCustomModeCharacterButtonSoundKey() {
        return "AUTOMATON_ORB_SPAWN";
    }

    public AbstractCard.CardColor getCardColor() {
        //このキャラで使うカードのカラー
        return AbstractCardEnum.EXAMPLE_COLOR; //カードカラーの変数。間違えない。
    }

    public AbstractPlayer newInstance() {
        return new ExampleChar(this.name);
    }


    public ArrayList<String> getStartingDeck() { // 初期デッキの中身です。基本BASICレアリティのものを入れる。好きなように。
        ArrayList<String> retVal = new ArrayList<>();
        retVal.add(TestAttack.ID);
        retVal.add(TestAttack.ID);
        retVal.add(TestAttack.ID);
        retVal.add(TestAttack.ID);
        retVal.add(TestAttack.ID);
        retVal.add(TestAttack.ID);
        retVal.add(TestAttack.ID);
        retVal.add(TestAttack.ID);
        retVal.add(TestAttack.ID);
        return retVal;
    }

    public ArrayList<String> getStartingRelics() { // スターターレリックの指定です。
        ArrayList<String> retVal = new ArrayList<>();
        retVal.add(TestRelic.ID);
        UnlockTracker.markRelicAsSeen(TestRelic.ID);
        return retVal;
    }

    //キャラ能力を決める部分
    private static final int STARTING_HP = 75;
    private static final int MAX_HP = 75;
    private static final int STARTING_GOLD = 99;
    private static final int HAND_SIZE = 5;
    private static final int ORB_SLOTS = 0;
    private static final int ASCENSION_MAX_HP_LOSS = 5;

    public int getAscensionMaxHPLoss() {
        return ASCENSION_MAX_HP_LOSS;
    }

    public CharSelectInfo getLoadout() { // キャラセレ画面で出る情報だと思う
        return new CharSelectInfo(
                charStrings.NAMES[0],
                charStrings.TEXT[0],
                STARTING_HP,
                MAX_HP,
                ORB_SLOTS,
                STARTING_GOLD,
                HAND_SIZE,
                this,
                getStartingRelics(),
                getStartingDeck(),
                false);
    }
}

resources/localizationに以下のファイルを追加します。

char-JPN.json

{
  "examplemod:ExampleChar": {
    "NAMES": ["キャラ名"],
    "TEXT": ["説明文。場所によって変えたければ分割したりする"]
  }
}

海外ryなのでchar-ENG.jsonも用意してください。

さて、最後のexample_mod/Main.javaの編集です。

Main.java

import basemod.BaseMod;
import basemod.interfaces.*;
import com.evacipated.cardcrawl.modthespire.lib.SpireInitializer;
import com.megacrit.cardcrawl.core.Settings;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.megacrit.cardcrawl.helpers.CardHelper;
import com.megacrit.cardcrawl.localization.CardStrings;
import com.megacrit.cardcrawl.localization.CharacterStrings;
import com.megacrit.cardcrawl.localization.RelicStrings;
import com.megacrit.cardcrawl.localization.PowerStrings;

//ここはデバッグログを出したいときに
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import example_mod.patches.*;
import example_mod.relics.*;

@SpireInitializer
public class Main implements
        EditCardsSubscriber,   // カードを追加する場合にimplementする
        EditRelicsSubscriber,   //レリックを追加する場合にimplement
        EditStringsSubscriber,  // 言語ファイルを読み込む場合に implementする
        EditCharactersSubscriber //キャラを追加する場合にimplementする
{
    private static final Color EXAMPLE_COLOR_BG = CardHelper.getColor(100.0f, 50.0f, 50.0f);
    private static final String ATTACK_EXAMPLE         = "img/cards/bg_attack_512.png";
    private static final String SKILL_EXAMPLE          = "img/cards/bg_skill_512.png";
    private static final String POWER_EXAMPLE           = "img/cards/bg_power_512.png";
    private static final String ENERGY_ORB_EXAMPLE         = "img/cards/orb_512.png";
    private static final String ATTACK_PORT_EXAMPLE        = "img/cards/bg_attack_1024.png";
    private static final String SKILL_PORT_EXAMPLE         = "img/cards/bg_skill_1024.png";
    private static final String POWER_PORT_EXAMPLE         = "img/cards/bg_power_1024.png";
    private static final String ENERGY_ORB_PORT_EXAMPLE = "img/cards/orb_1024.png";
    private static final String ENERGY_ORB_CARD_EXAMPLE = "img/cards/orb_ui.png";

    //デバッグログを出したいときに。
    public static final Logger logger = LogManager.getLogger(Main.class.getName());


    public Main(){
        BaseMod.subscribe(this);
        BaseMod.addColor(
                AbstractCardEnum.EXAMPLE_COLOR    //カラーの変数
                , EXAMPLE_COLOR_BG //bgColor
                , EXAMPLE_COLOR_BG//backColor
                , EXAMPLE_COLOR_BG//frameColor
                , EXAMPLE_COLOR_BG//frameOutlineColor
                , EXAMPLE_COLOR_BG//descBoxColor
                , EXAMPLE_COLOR_BG //trailVfColor
                , EXAMPLE_COLOR_BG//glowColor
                , ATTACK_EXAMPLE//attackBg
                , SKILL_EXAMPLE//skillBg
                , POWER_EXAMPLE//powerBG
                , ENERGY_ORB_EXAMPLE//energyOrb
                , ATTACK_PORT_EXAMPLE//attackBgPortrait
                , SKILL_PORT_EXAMPLE//skillBgPortrait
                , POWER_PORT_EXAMPLE//powerBgPortrait
                , ENERGY_ORB_PORT_EXAMPLE//energyOrbPortrait
                , ENERGY_ORB_CARD_EXAMPLE//CardEnergyOrb
        );
    }

    public static void initialize() {
        Main main = new Main();
    }

    @Override
    public void receiveEditStrings() {
        BaseMod.loadCustomStringsFile(CardStrings.class, "localization/cards-" + Settings.language + ".json");
        BaseMod.loadCustomStringsFile(RelicStrings.class, "localization/relics-" + Settings.language + ".json");
        BaseMod.loadCustomStringsFile(CharacterStrings.class, "localization/char-" + Settings.language + ".json");
        BaseMod.loadCustomStringsFile(PowerStrings.class, "localization/powers-" + Settings.language + ".json");
    }

    @Override
    public void receiveEditCards() {
        BaseMod.addCard(new example_mod.cards.TestAttack());
        BaseMod.addCard(new example_mod.cards.TestPower());
        //あとで説明
    }

    @Override
    public void receiveEditCharacters() {
        //独自定義のキャラの追加
        BaseMod.addCharacter(
                new example_mod.character.ExampleChar("ExampleCharacter"),
                "img/charSelect/ExampleButton.png",
                "img/charSelect/ExamplePortrait.png",
                ExampleClassEnum.ExampleClass
        );

    }

    public void receiveEditRelics() {
        //レリックの追加
        logger.info("begin editing relics"); //デバッグログ出すのはこういう感じ。
        BaseMod.addRelicToCustomPool(
                new TestRelic(),
                AbstractCardEnum.EXAMPLE_COLOR //カラーに対して追加する。共通レリックはまた別のメソッド
        );
        logger.info("done editing relics");
    }
}

さあ、これでビルドしてキャラセレクトしてゲームが開始すれば完成です! お疲れさまでした!

……え? 戦闘終了したら固まる?

ああ、それはほら、カードの数が足りないからです。戦闘終了時にカードをランダムで3枚表示するでしょう? 今この通り進めていたら2種類しかないのでクラッシュするのですよ。それっぽいところでデバッグログも止まっていると思います。

じゃあキャラMODに最低何枚必要かっていうと、分かりません(おしえてください)。

分かりませんが、カード選択は通常3枚で、レリックによって+1されることがあり、ボス倒したときは全部レアなので、
まあアタック・スキル・パワーのCOMMON、UNCOMMON、RAREがそれぞれ4種類あれば最低動くんじゃないでしょうか。
3カテゴリ×3レア×4種類で、36枚ですかね? 他の人のMOD見ると70枚とかはざらにあるので……。

とりあえず自分のリポジトリでは同じ能力のカード増産して動くようにしましたので、まあひな形にはなったかなと思います。

なんでリポジトリは「example」じゃなくて「training」なのって、なんか恥ずかしかったんで。exampleの方がBaseModさんの説明と同じでいいかなって。

以上です。

その他メモ
・効果により通常入手できないカードをドローする場合、それ用にカードカラーを用意してレアリティをSPECIALにする?
・アップグレードで説明文を変えるときは"UPGRADE_DESCRIPTION"を用意する。

15
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
8