From fbaf2cc671fed23b555e218499301af0f50327b0 Mon Sep 17 00:00:00 2001 From: Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> Date: Sat, 3 Oct 2026 15:44:38 +0900 Subject: public/bluebey-studio: 移動範囲と影・壁の追従、看板の文字 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移動スライダーの範囲を広げ、キーライトと影カメラがぶるべーに追従するようにした(遠くへ動かしても影が切れない)。地面も広げた。 足もとの丸い影は既定で非表示にし、「足もとの丸い影」で切り替えられるようにした。原点に取り残されないよう、ぶるべーに追従させる。 見えない壁は体の向き(master のY回転と見る先の体向き)にも追従するようにした。 「舞台」をやめ、「小物」「擬音」「見えない壁」をそれぞれ独立したメニューにした。看板に文字を書けるようにした(複数行可)。 --- public/bluebey-studio/src/panel.js | 52 ++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 14 deletions(-) (limited to 'public/bluebey-studio/src/panel.js') diff --git a/public/bluebey-studio/src/panel.js b/public/bluebey-studio/src/panel.js index e9764c1..4b2a4c0 100644 --- a/public/bluebey-studio/src/panel.js +++ b/public/bluebey-studio/src/panel.js @@ -10,7 +10,7 @@ import { STYLE_DEFS } from './styles.js'; import { EYE_VARIANTS } from './model.js'; import { ENVIRONMENTS } from './look.js'; import { BACKGROUND_PRESETS, EFFECT_PRESETS } from './background.js'; -import { PROP_LIBRARY, applyPropFaceScale } from './props.js'; +import { PROP_LIBRARY, applyPropFaceScale, applyPropText } from './props.js'; import { GION_SHEETS, gionSheetUrl, cropFromMarquee, fitSheet, normalizeRect, clamp, DEFAULT_STAMP_WIDTH, } from './gion.js'; @@ -244,9 +244,9 @@ export function buildPanel(app, root) { // こまかい設定: the whole-body nudge and the rotation gizmo are tweaks, not the // main way in, so they stay folded away inside the bone section. const rootSliders = [ - { label: 'よこ移動', min: -5, max: 5 }, - { label: 'たて移動', min: -2, max: 6 }, - { label: 'おくゆき', min: -5, max: 5 }, + { label: 'よこ移動', min: -20, max: 20 }, + { label: 'たて移動', min: -10, max: 20 }, + { label: 'おくゆき', min: -20, max: 20 }, ].map(({ label, min, max }, index) => slider({ label, min, max, step: 0.01, value: 0, format: (v) => v.toFixed(2), @@ -1526,7 +1526,15 @@ export function buildPanel(app, root) { onChange: (value) => { state.render.contactShadow = value; app.actions.refresh('render'); }, }); lookSection.add(controlRow(null, contactShadowToggle.el, { wide: true })); - lookSection.add(hint('影の向きは「ひかり」の光の向き(パッド)にしたがって変わります。「接地影だけ」は足もとの丸い影だけにします。')); + const contactBlobToggle = check({ + label: '足もとの丸い影', + value: false, + onChange: (value) => { state.render.contactBlob = value; app.actions.refresh('render'); }, + }); + lookSection.add(controlRow(null, contactBlobToggle.el, { wide: true })); + lookSection.add(hint('影の向きは「ひかり」の光の向き(パッド)にしたがって変わります。' + + '「接地影だけ」は足もとの丸い影だけにします。' + + '「足もとの丸い影」は、足もとに薄く敷く丸い影です(はじめは出ていません)。')); const environmentSegment = segmented({ label: '照明の種類', @@ -1721,10 +1729,10 @@ export function buildPanel(app, root) { /* ------------------------------------------------------------------- props */ - // 舞台: the things you put the character *among* - the props and the wall - in - // one place, since they are set up together. - const stageSection = section(tabScene, '舞台'); - const propSection = details(stageSection.body, '小物'); + // 小物: the things you put the character *among*. Each stage feature gets its + // own top-level section rather than one shared 舞台 drawer, so they are easier + // to find and open independently. + const propSection = section(tabScene, '小物'); propSection.add(buttons({ label: '追加', items: PROP_LIBRARY.map((entry) => ({ @@ -1780,7 +1788,21 @@ export function buildPanel(app, root) { // state and carries only the whole-prop scale, so the props syncer // re-applies this to the fresh mesh (see `applyPropFaceScale`). let faceScale = null; + let signText = null; if (item.kind === 'sign') { + signText = textArea({ + label: '看板の文字(改行できます)', + value: '', + onChange: (value) => { + const target = state.props?.items?.[index]; + if (target) target.text = value; + // Paint straight onto the live face rather than rebuilding every prop + // on each keystroke; a later rebuild re-applies it (see main.js). + applyPropText(placedPropGroups(app)[index], value); + app.needsRender = true; + }, + }); + block.insertBefore(signText.el, numbers.el); faceScale = slider({ label: '文字部分の大きさ', min: 0.4, max: 2.4, step: 0.01, value: 1, format: (v) => v.toFixed(2), onInput: patch('faceScale'), @@ -1801,7 +1823,7 @@ export function buildPanel(app, root) { }], }).el); propListBox.append(block); - propWidgets.push({ index, sliders, faceScale }); + propWidgets.push({ index, sliders, faceScale, signText }); }); }; renderProps(); @@ -1812,7 +1834,7 @@ export function buildPanel(app, root) { // so the user picks the crop with a marquee instead of the app slicing them // (see src/gion.js for why). Only the sheet name and the crop rectangle live in // the state - never the bitmap - so a shared link stays small. - const gionSection = details(stageSection.body, '擬音(マンガのオノマトペ)'); + const gionSection = section(tabScene, '擬音(マンガのオノマトペ)'); gionSection.add(hint('擬音(ドドド、バン!など)の画像から、四角く切り出したスタンプを画面に貼れます。' + '「擬音をえらぶ」で画像を開き、ドラッグで四角を描いて「追加」を押してください。' + '貼ったあとはビューポートでドラッグして動かせます。')); @@ -2069,7 +2091,7 @@ export function buildPanel(app, root) { // 見えない壁: place up to two finite "boards" and bury the character in them, so // only the part in front still shows. A wall's position is kept *relative to the // character*, so moving ぶるべー carries its walls along (see src/clip.js). - const wallSection = details(stageSection.body, '見えない壁'); + const wallSection = section(tabScene, '見えない壁'); wallSection.add(hint('見えない板(有限の壁)を置くと、その向こう側が隠れます。' + 'ぶるべーを壁に埋め込むと、体の一部だけが見えるようになります。' + '板は2枚まで置けます。板の位置はぶるべーからの相対なので、ぶるべーを動かすと壁も一緒に動きます。' @@ -2443,8 +2465,8 @@ export function buildPanel(app, root) { // order the user meets them: what is behind the character, what is beside it, // the speech bubble, then the light and the read-out. tabScene.append( - backdropSection.el, stageSection.el, captionSection.el, - screenLightSection.el, infoSection.el, + backdropSection.el, propSection.el, gionSection.el, wallSection.el, + captionSection.el, screenLightSection.el, infoSection.el, ); boneSyncers.push(() => { @@ -2584,6 +2606,7 @@ export function buildPanel(app, root) { shadowOpacitySlider.set(state.render?.shadowOpacity ?? 0.22); shadowSoftnessSlider.set(state.render?.shadowSoftness ?? 1.6); contactShadowToggle.set(state.render?.contactShadow === true); + contactBlobToggle.set(state.render?.contactBlob === true); environmentSegment.set(state.render?.environment ?? 'gradient'); }); @@ -2618,6 +2641,7 @@ export function buildPanel(app, root) { entry.sliders.scale.set(item.scale ?? 1); entry.faceScale?.set(item.faceScale ?? 1); applyPropFaceScale(groups[entry.index], item.faceScale); + entry.signText?.set(item.text ?? ''); } }); -- cgit v1.3.1