序文疫病流行中に「Mount & Blade 2」が発売され、その後はゲームMODを書いていたため、1年以上ブログを更新していませんでした。 私は約7か月間、毎晩遅くまでC#でゲームMODを書き続けました。その間、このゲームMODの紹介を通じてPRを学び、その後、BilibiliのUPホストになりました。 その後、別のアイデアや会社の業務調整もあり、ブログを書くのが面倒になり、気がつけば1年以上が経過していました。 まだ利益はあります:
さて、本題に戻りましょう。 現在、MOD は基本的に廃止されており、UP の所有者は真剣に作業を続けるのが面倒になっています。 ここでは主に技術関連のこと、つまり純粋なフロントエンド実装、MOD を記述するための XML オンライン エディターについて説明します。 これは、ゲーム MOD ファイル生成の制約ルールを自動的に学習して、コードプロンプトとコード検証の実装を支援する VSCode スタイルのエディターです。 さらに重要なのは、コンピューター上のファイルを直接変更できることです。 これは最終的な製品コードリポジトリです: https://gitee.com/vvjiang/mod-xml-editor 完成品の写真: このブログで取り上げるテクノロジー:
最初から始めましょう。 オンラインXMLエディタの必要性Mount & Blade 2 の MOD を作成する場合、XML ファイルを頻繁に記述する必要があります。 Mount & Blade 2 のデータ構成は XML 形式で保存されており、MOD がロードされた後、MOD の XML が公式 XML を上書きするために使用されるためです。 通常、MOD データを扱うときは、公式 XML を参考にして、自分で XML ファイルを作成します。 しかし、これは問題を引き起こします。XML にはコードヒントやコード検証がないため、間違った文字を検出するのは困難です。 また、ゲームが更新されると、XML ルールが変更される場合があります。 公式からはこれらの変更について通知は出ないので、以前の要素や属性をそのまま使用している場合は、書き方が間違っていることになります。 間違った書き方をすると、MOD をロードするときにゲームが直接クラッシュし、プロンプトが表示されなくなることがよくあります。ゆっくりとバグを探すことしかできません。 Mount & Blade 2は大規模なゲームであるため、毎回の起動に時間がかかり、MODデータが正しく設定されているかどうかをテストするテストプロセスが非常に長くなります。 ああ、ゲームがクラッシュした瞬間に泣き崩れた夜が何度もありました。 そこで、この問題を解決するために XML オンライン エディターを作成することを考えました。 テクノロジー事前調査ビジュアルプログラミング実は、この XML エディターは使いにくそうだったので、最初は作ろうと思っていませんでした。代わりに、要素や属性をドラッグ アンド ドロップするビジュアル プログラミングで実装したいと考えていました。 実は、予備的な計画を立てていたのですが、大きな XML の設定を何度もドラッグ アンド ドロップしているうちに、だんだん我慢できなくなり、この計画を断念してしまいました。 VSCODEプラグインコードヒントを提供できる VSCode プラグインがあるかどうかを確認したいと思います。コード検証に XSD を使用するプラグインがあり、これは IBM によって提供されているようです。 しかし残念ながら廃墟になってしまい、もう使えないのでこの計画は断念します。 オンラインエディターこれを実現するためにオンライン エディターを使用した理由は、3 月と 4 月に会社が Java プロジェクト環境の XML 構成ファイルをオンラインで編集できるものを作りたかったためです。 それから、自分で作ってみて、 CodeMirrorについて知りました。 CodeMirror はタグ自体を構成することで XML コードヒントをサポートしますが、XML コードの検証はサポートしていないため、XML コードの検証を自分で行う必要があります。 また、通常は xsd を使用して xml を検証するため、xsd をCodeMirrorのタグ構成に変換する必要もあります。 BaiduでもGoogleでもGithubでも、対応する解決策が見つからないので、自分でコードを書いて実装するしかありません。 この過程で、 CodeMirror 、 xsd 、 htmllintについての理解が深まり、最終的にプロジェクトを完了しました。 これは以前の会社のコードなので、ここでは公開しません。 つまり、この過程でCodeMirrorについて知り、 CodeMirrorを使って MOD 用のオンライン エディターを作るというアイデアを思いついたのです。 元の形式: シンプルなオンライン XML エディターさて、これ以上何も言わずに、キーボードを手に取って作業を始めましょう。 初期のフォームでは、左側にファイル ツリーはなく、シンプルなエディターとルール学習ポップアップ ボックスのみがありました。 関係する技術は 3 つあります。 コードミラー ファイルリーダー xmldom CodeMirror をエディターとして使用するCodeMirror は主に react のパッケージ版である react-codemirror2 を使用します。とにかく、ドキュメントとデモを読んで自分で設定してください。 唯一の難点は、インターネット上の CodeMirror 構成紹介の多くがコピーされ、再現されており、一部は依然として間違っているという点です。これはとんでもないことです。 つまり、いじってみたい場合は、公式ドキュメント (https://codemirror.net/) とドキュメント上のデモを読んで、自分で勉強するのが最善です。他の人の設定をコピーすると、水が非常に深くなり、制御できなくなります。 ここで、カプセル化したエディター コンポーネントの構成コードの一部を投稿します。これは間違いなく使用可能であり、エディターのほとんどの機能を備えていますが、XML の編集にのみ適しています。 その中には、よく使われるコードの折りたたみやコードのフォーマットなど、非常に詳細なコメントがあります。 一つ一つ説明するのは面倒なので、公式 Web サイトを参照して自分で確認してください。 参照されているコードの一部はここでは掲載しません。興味があれば、上記のコード リポジトリにアクセスしてご覧ください。 'react' から {useEffect} をインポートします。 'react-codemirror2' から {Controlled を ControlledCodeMirror としてインポートします。 'codemirror' から CodeMirror をインポートします。 'codemirror/lib/codemirror.css' をインポートします 'codemirror/theme/ayu-dark.css' をインポートします。 'codemirror/mode/xml/xml.js' をインポートします。 // カーソル行のコードハイライト import 'codemirror/addon/selection/active-line' // 折りたたみコード import 'codemirror/addon/fold/foldgutter.css' 'codemirror/addon/fold/foldcode.js' をインポートします。 'codemirror/addon/fold/xml-fold.js' をインポートします。 'codemirror/addon/fold/foldgutter.js' をインポートします。 'codemirror/addon/fold/comment-fold.js' をインポートします。 // コードヒントの補完と 'codemirror/addon/hint/xml-hint.js' のインポート 'codemirror/addon/hint/show-hint.css' をインポートします。 './hint.css' をインポートします 'codemirror/addon/hint/show-hint.js' をインポートします。 // コード検証 import 'codemirror/addon/lint/lint' 'codemirror/addon/lint/lint.css' をインポートします './xml-lint' から CodeMirrorRegisterXmlLint をインポートします。 // 入力時に自動的に終了タグを入力します > import 'codemirror/addon/edit/closetag.js' // コメントをインポート 'codemirror/addon/comment/comment.js' // codeMirror のテーマ スタイルを調整するために使用されます。import style from './index.less' // XML コード検証を登録する CodeMirrorRegisterXmlLint(CodeMirror) // フォーマット関連 CodeMirror.extendMode("xml", { コメント開始: "<!--", コメント終了: "-->", newlineAfterToken: 関数 (タイプ、コンテンツ、textAfter、状態) { 戻り値 (type === "tag" && />$/.test(content) && state.context) || /^</.test(テキスト後); } }); // 指定された範囲をフォーマットします CodeMirror.defineExtension("autoFormatRange", function (from, to) { var cm = this; var outer = cm.getMode()、text = cm.getRange(from, to).split("\n"); var state = CodeMirror.copyState(outer, cm.getTokenAt(from).state); var tabSize = cm.getOption("tabSize"); var out = "", 行 = 0、 atSol = from.ch === 0; 関数newline() { 出力 += "\n"; atSol = true; ++行; } (var i = 0; i < text.length; ++i) の場合 { var stream = new CodeMirror.StringStream(text[i], tabSize); while (!stream.eol()) { var inner = CodeMirror.innerMode(outer, state); var style = outer.token(stream, state), cur = stream.current(); ストリームの開始位置 = ストリームの位置; if (!atSol || /\S/.test(cur)) { 出力 += 現在の値; atSol = 偽; } if (!atSol && inner.mode.newlineAfterToken && inner.mode.newlineAfterToken(スタイル、cur、stream.string.slice(stream.pos) || text[i + 1] || "", inner.state)) 改行(); } stream.pos と outer.blankLine が等しい場合、outer.blankLine(state); if (!atSol && i < text.length - 1) 改行(); } cm.操作(関数() { cm.replaceRange(out, from, to); (var cur = from.line + 1、end = from.line + 行数; cur <= end; ++cur) の場合 cm.indentLine(cur, "スマート"); cm.setSelection(from, cm.getCursor(false)); }); }); // XML エディター コンポーネント関数 XmlEditor(props) { const { tags, value, onChange, onErrors, onGetEditor, onSave } = props 使用効果(() => { // タグが変更されるたびに、検証ルールも変更されます CodeMirrorRegisterXmlLint(CodeMirror, tags, onErrors) }, [onErrors, タグ]) // 開始タグ function completeAfter(cm, pred) { if (!pred || pred()) setTimeout(function () { (!cm.state.completionActive) の場合 cm.showHint({ 完全シングル: false }); }, 100); CodeMirror.Pass を返します。 } // 終了タグ関数 completeIfAfterLt(cm) { 完了後に(cm, 関数() を返す { var cur = cm.getCursor(); cm.getRange(CodeMirror.Pos(cur.line, cur.ch - 1), cur) === "<" を返します。 }); } // 属性と属性値 function completeIfInTag(cm) { 完了後に(cm, 関数() を返す { var tok = cm.getTokenAt(cm.getCursor()); tok.type === "string" && (!/['"]/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length === 1)) の場合は false を返します。 var inner = CodeMirror.innerMode(cm.getMode(), tok.state).state; inner.tagName を返します。 }); } 戻る ( <div className={style.editor} > <ControlledCodeMirror 値={値} オプション={{ モード: { 名前: 'xml', // xml 属性が multilineTagIndentPastTag をラップするときにタグの長さを追加するかどうか: false }, indentUnit: 2, // 改行時のデフォルトのインデント数 theme: 'ayu-dark', // エディターテーマ lineNumbers: true, // 行番号を表示するかどうか autofocus: true, // 自動的にフォーカスを取得する styleActiveLine: true, // カーソル行のコードにハイライト表示 autoCloseTags: true, // 入力時に終了要素を自動的に入力する >toggleComment: true, // コメントを開く // コードを折りたたむ begin 行折り返し: true、 折り返しガター: true、 ガター: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'], //折りたたみコード終了 追加キー: { // コードヒント "'<'": completeAfter, "'/'": 完了後Lt、 "' '": タグ内で完了、 "'='": タグ内で完了、 // コメント関数 "Ctrl-/": (cm) => { cm.toggleコメント() }, // 保存関数 "Ctrl-S": (cm) => { 保存() }, // フォーマット "Shift-Alt-F": (cm) => { 定数totalLines = cm.lineCount(); cm.autoFormatRange({ 行: 0, ch: 0 }, { 行: 合計行数 }) }, // タブは自動的にスペースに変換されます "Tab": (cm) => { if (cm.somethingSelected()){// 選択後の全体のインデント cm.indentSelection('add') } それ以外 { cm.replaceSelection(Array(cm.getOption("indentUnit") + 1).join(" "), "end", "+input") } } }, // コードヒント hintOptions: { schemaInfo: tags, matchInMiddle: true }, 糸くず: 真 }} editorDidMount={onGetEditor} onBeforeChange={onChange} /> </div> ) } デフォルトの XmlEditor をエクスポートする XMLを学び、タグのルールを抽出するCodeMirror を使用してシンプルなエディターを作成する場合、XML コードプロンプトを提供するにはタグを使用する必要があります。 当然のことながら、ゲームによって XML ルールは異なり、ゲームが更新されると XML ルールも変更される可能性があります。 したがって、これらの XML ルールを継続的に学習するメカニズムがあることを確認する必要があります。そのため、ここでは、これを実現するために XML ファイル ルールを学習するためのポップアップ ウィンドウを作成しました。 エディターの左上隅にある制約ルールをクリック -> 制約ルールを追加 次のようなポップアップ ウィンドウが表示されます。 指定されたフォルダー内の XML ファイルを FileReader で読み取り、xmldom を使用してこれらの XML ファイルのテキストを解析し、ドキュメント オブジェクトを生成します。 次に、これらのドキュメント オブジェクトを分析して、最終的なタグ ルールを取得します。 このステップでは XML に関する多少の理解のみが必要ですが、これは実際には非常に基本的なため、説明はしません。 つまり、これで初期フォームが完成しました。使用するたびに、編集した XML ファイルの内容をこのオンライン エディターにコピーする必要があります。編集後、完成したテキストを元の XML ファイルにコピーして保存し、上書きします。 Evolution: ツリー状のファイル構造と完全なファイル検証を備えたオンライン XML エディター上記のエディターは実際には使用シナリオが非常に限られており、新しい XML を記述するときにのみ使用できます。 MOD は数十、数百、あるいは数千のファイルで構成されることが多く、それらを 1 つずつエディターに貼り付けて検証することは不可能です。 したがって、このエディターで MOD のすべての XML ファイルをロードし、コード検証を実行する必要があります。 関係する技術は 2 つあります。
左ファイルツリー左側のファイル ツリーは Ant Design の Tree コンポーネントを使用して完成されているため、ここでは構成の詳細については説明しません。 フォルダを開くボタンをクリックすると また、FileReader を使用して MOD フォルダー内のファイルを読み取ります。 ただし、FileReader はファイルの配列を取得します。左側のツリー構造を生成するには、各 XML ファイルのパスを手動で解析し、それに基づいてツリー構造を生成する必要があります。 完全なファイル検証機能フォルダーを開いた瞬間に、すべての XML ファイルに対してコード検証を実行する必要があります。検証が正しくない場合は、左側のフォルダーで、関連するすべてのファイルと、その親レベルおよび祖先レベルの一連のフォルダーを赤でマークする必要があります。 この機能は単純に見えますが、実際には多くの落とし穴があります。検証計算の量は実際には少なくなく、特に MOD に数百または数千のファイルがある場合、js がブロックされ、ページが応答しなくなる可能性が非常に高くなります。 ここでは、Web Worker を使用して新しいスレッドを開き、検証プロセスを処理し、検証が完了した後に結果を返します。 この過程で、Web Workers の使用についてもさらに学びました。 ずっと思っていたのですが、新しい Worker (js ファイル) なので、React のモジュール開発と組み合わせて使うのは難しい気がします。 しかし実際には、webpack で worker-loader を設定することで、Web Workers を非常に便利に使用できるようになりました。 まず、ワーカー コードは次のように記述できます。 '@/utils/files' から { lintFileTree } をインポートします。 onmessage = ({データ}) => { lintFileTree(data.fileTree, data.currentTags).then(コンテンツ => { postMessage(コンテンツ) }) } このWorkerを使うときは次のようにします。 'react-webworker-hook' から { useWebWorkerFromWorker } をインポートします。 '@/utils/webWorker/lintFileTree.webworker' から lintFileTreeWorker をインポートします。 const worker4LintFileTree = 新しい lintFileTreeWorker() const [lintedFileTree、startLintFileTree] = useWebWorkerFromWorker(worker4LintFileTree) 次に、useEffect を使用してこの lintedFileTree に依存し、変更があった場合に何かを実行します。そのため、useState を使用するのと同じくらい簡単に記述できます。 非再帰的なツリートラバーサルコードを検証するためにファイル ツリーをトラバースするなど、上記で使用した多くのものがツリーに関連していることがわかります。 または、制約ルールを切り替えた後、再チェックのためにファイル ツリー全体を走査する必要もあります。 トラバーサル処理中は、ツリー全体をトラバースするために再帰を使用しました。この方法の欠点は、再帰中にメモリを解放できないことです。そのため、後でアルゴリズムを変更し、ツリー全体をトラバースするために非再帰的な方法を使用しました。 IndexDBはファイルの内容を保存します当社の MOD ファイルはサイズが大きく、多くのコンテンツが含まれているため、大量のメモリを占有する可能性があり、これらのファイルのコンテンツを常にメモリ内に保持することは不可能です。 そこで、ファイルの内容を一つずつ読み取って IndexDB に格納し、現在編集中のファイルの内容のみを表示します。 ファイル全体の検証やファイルの切り替えなど、必要な場合にのみ、ファイルの内容が IndexDB から再度取得されます。 究極の進化: ブラウザサンドボックスの制限を突破し、コンピュータ上のローカルファイルの追加、削除、変更を実現しますこれまでの作業を経て、ようやく基本的に使えるオンライン XML エディターが完成しました。 しかし、これには致命的な欠陥があり、ブラウザのサンドボックス環境によって制限されます。ファイルを変更した後、それを直接コンピューターに保存することはできず、変更したコードを対応するファイルに 1 つずつ手動でコピーする必要があります。 この操作は面倒で複雑なため、エディターの機能はコードの作成とバッチ検証を支援するためにのみ使用できます。 以前はこれがすべてだと思っていましたが、偶然 Zhihu の投稿を読んで、Chrome86+ バージョンには FileSystemAccess という追加の機能 API があることを知りました。 また、ローカルの localhost 環境でない限り、この API は https 環境でのみ呼び出すことができます。つまり、http の Web サイトにいる場合は、Chrome86 以降や Edge86 以降を使用していても呼び出すことはできません。 この API を使用すると、FileReader のように読み取りのみが可能であったり、FileSystem のようにブラウザ サンドボックス内でのみ操作が可能であったりするのではなく、ローカル コンピュータ上のファイルを直接操作できるようになります。 FileSystemAccess を使用すると、フォルダー内のファイルの読み取りと変更だけでなく、ファイルの追加と削除も行えます。 そこで、この API を使用して、これまで FileReader を使用していた箇所をすべて完全に置き換え、ファイル ツリーを右クリックすることでフォルダーやファイルの追加と削除を実現しました。 (ここではファイル名の変更はサポートされていませんが、削除してから追加することで名前の変更をシミュレートすることはできますが、面倒なので実行しません) 同時に、[保存] ボタンまたは保存ショートカット キー Ctrl+S を押すと、ファイルを直接保存できます。 以下は、FileSystemAccess を使用してフォルダーを開くコンポーネント コードです。 'react' から React をインポートします // カスタマイズされたフォルダオープンコンポーネント const FileInput = (props) => { const { children, onChange } = プロパティ const handleClick = 非同期() => { const dirHandle = window.showDirectoryPicker() を待機します dirHandle.requestPermission({ mode : "readwrite" }) onChange(dirHandle) } <span onClick={handleClick}> を返します {子供たち} </span> } デフォルトのFileInputをエクスポートする このコンポーネントによってラップされた要素 (ボタンなど) がクリックされると、showDirectoryPicker がすぐに呼び出され、フォルダーを開くように要求します。 フォルダを開いた後、取得したフォルダ ハンドルを通じてフォルダへの書き込み権限を要求し、このフォルダ ハンドルを外部に渡してファイル ツリー構造を取得します。 ここでの操作には欠陥があります。フォルダーを開くことを要求すると、ブラウザーはフォルダーの読み取り許可をユーザーに求めるボックスをポップアップ表示します。 開いた後、書き込み権限を取得するための 2 番目のボックスがポップアップ表示されます。つまり、フォルダーを開くと 2 つのボックスがポップアップ表示されます。 ただし、この方法では一度にすべての権限を要求することしかできません。そうしないと、保存するときに再度権限を要求するのは得策ではありません。 ただし、メリットはデメリットを上回ります。この API は、ファイルの追加、削除、変更を実現するだけでなく、IndexDB の使用も排除します。 ファイル ハンドルを通じていつでも対応するファイルの内容を取得できるため、ファイルの内容を IndexDB に保存する必要はありません。 その他の機能と詳細上記は、コアとなる技術機能の概要にすぎません。実際、このエディターにはさらに多くの詳細があります。 たとえば、タグ ルールを調整するためのパネル、ツールバーのボタン、dva の単純なカプセル化、XML を分析するときに、属性値が数値の場合、プロンプトは表示されずに直接無視されます。これは、数値はあまり意味をなさず、列挙値が大きすぎるためです。 このようなものはたくさんありますが、その応用は比較的基本的なものなので、詳細には触れません。そうしないと、このブログが非常に長くなり、核となるアイデアを強調するのが難しくなります。 欠点と要約ここでの欠点は、怠惰によるところが大きいです。たとえば、前述のフォルダーとファイルの名前変更機能や、タグルールを調整するためのカスタムルールは、変更と削除をサポートしていません。 それは実行可能です、ただそれをするのが面倒なのです。 この作品の制作には数か月かかりました。毎晩書いていたわけではありません。インスピレーションが湧いたときや、改善できる点を見つけたときに書いていました。 合計で、私は約 2 ~ 3 週間、毎晩これに取り組みました。そして、それがより完成度が高く、使いやすくなるにつれて、私はだんだんと怠惰になっていきました。 残りの操作はそれほど重要ではなく、少しの想像力があれば完了できるため、難しい部分はそれほど多くありません。 しかし、全体的に見ると、この製品の使いやすさは依然として非常に優れています。 「Mount & Blade 2」、「The Great Cultivation Simulator」、「Civilization 6」などの一連のゲーム用の XML ファイルの作成を支援するだけでなく、XSD ルールがなく複雑すぎる XML 構成にも使用できます。カスタム XML ルールを学習することもできます。 js を使用して XML オンライン エディターを作成する方法についての記事はこれで終わりです。js を使用して XML オンライン エディターを作成する方法の詳細については、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。 以下もご興味があるかもしれません:
|
<<: MySQL データベースのエンコーディングを utf8mb4 に変更する方法
>>: Linux仮想マシンを作成し、仮想マシンネットワークを設定する方法に関するVMwareの詳細なチュートリアル
MTR は Mini-Transaction の略です。名前が示すように、これは「最小のトランザクシ...
序章nginx が優れたリバース プロキシ サービスであることは誰もが知っています。nginx を使...
新しい CSS 機能を使用する場合、その互換性は常に考慮されます。おそらく、その互換性、どのブラウザ...
MySQL データベース管理ソフトウェアには、エンタープライズ エディションとコミュニティ エディシ...
以前、上司からログイン後にチェックマークを表示できるプログラムを作るように言われたのですが、Baid...
目次1. 概要2. dockerを使用してTomcatをデプロイし、Skywalkingに接続する要...
表では、左上の境界線の色を個別に定義したり、セルの右下の境界線の色を定義したりできます。これら 2 ...
解決 関数 mergeImgs(リスト) { const imgDom = document.cre...
今日、仕事中に左結合に関するSQLの問題に遭遇しました。後で解決しましたが、この問題を通じてSQLの...
1. 動的クエリルール動的クエリルールは、おおよそ次の図のようになります。ユーザのカスタマイズに応じ...
目次テーブルを作成するときにNOT NULL制約を設定するテーブルを変更するときに非NULL制約を追...
この記事の例では、参考のためにjsカスタム右クリックメニューの具体的なコードを共有しています。具体的...
序文最近、MySQL のインデックスについて読んでいました。結合されたインデックスを見ると、左端の原...
シナリオ: laradock 開発環境 (php7.3+mysql5.7) がローカルに構築されてい...
目次序文原因現象なぜ?分析要約する序文今日は、非常に典型的な MySQL の「落とし穴」についてお話...