本文へ移動
cccskills
無料GitHub で公開日本語紹介

melonjs-renderables

melonJSのRenderable描画、入力イベント、描画順、ポストエフェクトの扱いを学び、位置ずれやクリックが届かないなどの見落としやすい不具合を防ぎます。

原文原文の説明を見る

Use this skill when subclassing Renderable, writing a custom draw() method, handling pointer or click events on game objects, applying post effects and shaders, or controlling draw order. Covers the pos-relative draw contract, isKinematic and input, addPostEffect vs the deprecated shader property, and z-ordering. Triggers on: Tween, easing, animate, Renderable, extends Renderable, draw(, update(, isKinematic, pointerEvent, registerPointerEvent, onClick, addPostEffect, removePostEffect, ShaderEffect, addChild, z-order, anchorPoint, floating.

melonjs/melonJSスター 6,429(GitHub 6,429 + cccskills 0)

実行スコア 未検証

インストール方法を見る

こんなときに便利

  • 独自Renderableを描画したいとき
  • クリック入力が届かない原因を探す
  • スプライトの重なり順を調整
  • ポストエフェクトを設定・切り替え

日本語での紹介

できること

Renderableを継承したオブジェクトの独自描画や、クリックなどの入力イベント、描画順、ポストエフェクトの設定を扱います。draw()では自身の位置を参照する必要があること、アンカー設定が描画位置に影響すること、入力や衝突判定にはisKinematicの変更が必要な場合があることなど、動作しているように見えて結果がずれる原因を確認できます。addChild(child, z)による順序指定や、エフェクトを一時停止するときの扱いも説明します。

こんなときに便利

独自のゲームオブジェクトを描画したいときや、登録したポインターイベントが反応しないときに役立ちます。スプライトの重なり順が想定と違う場合、シェーダー効果が表示されない場合、オブジェクトの位置や透明度が意図せず変わる場合にも、設定の確認に使えます。

使い方の例

  • 「このRenderableサブクラスの描画位置がずれる原因を確認して」
  • 「クリックイベントが届かないボタンの設定を見直して」
  • 「スプライトの描画順とポストエフェクトの使い方を教えて」

注意点

ポストエフェクトのシェーダー処理はGPUバックエンドが必要です。Canvas rendererでは効果が無効になります。renderable.shaderプロパティは非推奨で、addPostEffect()などを使います。

この紹介文は、公開されている SKILL.md をもとに AI(OpenAI Codex)が作成しました。正確な仕様は下の原文を確認してください。

含まれるファイル(1)

  • SKILL.md15.0 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Renderables, input and effects

Subclassing Renderable, custom drawing, receiving pointer events, and applying post effects — with the traps that produce silently wrong output rather than an error.

Everything here is a silent failure: the code runs, nothing throws, and the result is wrong. Those are the cases worth memorising, because nothing in the type signatures warns you.

1. A custom draw() must position itself from this.pos

The renderer is not pre-translated to the renderable's position when draw() is called. Container.draw translates by the container's own pos and then calls child.preDraw / draw / postDraw; preDraw never translates by the child's pos. Read this.pos and draw relative to it.

class Marker extends Renderable {
    draw(renderer) {
        // ✗ draws at the parent container's origin, not at the marker
        renderer.fillRect(0, 0, this.width, this.height);

        // ✓
        renderer.fillRect(this.pos.x, this.pos.y, this.width, this.height);
    }
}

The symptom is an object that renders correctly at position (0, 0) and appears stuck at the top-left as soon as you move it.

Zero the anchor in custom-draw renderables. preDraw does not translate to pos, but it does apply the anchor offset (-width * anchorPoint.x, -height * anchorPoint.y). With the default centred anchor, drawing at this.pos therefore lands half a size off:

constructor(x, y, w, h) {
    super(x, y, w, h);
    this.anchorPoint.set(0, 0);   // ← or every custom draw is offset by -w/2, -h/2
}

2. isKinematic must be false to receive input or collisions

Renderable.isKinematic defaults to true, which means "physics collision and input events will not impact this renderable". A kinematic renderable is skipped by the pointer-event dispatcher entirely.

class Button extends Renderable {
    constructor(x, y) {
        super(x, y, 100, 40);
        this.isKinematic = false;          // ← required, or clicks never arrive
        input.registerPointerEvent("pointerdown", this, () => { /* … */ });
    }
}

The symptom is a handler that is registered, looks correct, and never fires.

Note that Container, Camera2d, Draggable, DropTarget and the UI* elements set it to false for you, and so does the Body constructor on its owner (which covers Entity and anything you give a body to) — so a plain Renderable or Sprite subclass with no body is the case that catches people.

The same flag has a second, less obvious effect: it is also the opt-in to the world broadphase. The quadtree/octree insert skips any child with isKinematic === true, so body-less objects you want adapter.queryAABB / querySphere to find need isKinematic = false as well. The builtin narrowphase likewise requires both partners to be non-kinematic.

3. Draw order: only addChild(child, z) works

There are three separate traps here and they compound.

world.addChild(sprite, 10);   // ✓ the only reliable form
  1. renderable.z does not exist. There is no z accessor anywhere on Renderable (nor on Rect/Polygon above it) — only depth, which proxies pos.z, and the container's default sortOn: "z" comparator reads pos.z. Writing sprite.z = 100 creates an inert property that nothing reads. Several shipped examples still contain this.z = Number.POSITIVE_INFINITY; those HUDs draw on top only because they happen to be added last. (A z key in a Sprite/ImageLayer settings object is a different thing and is real — it assigns pos.z in the constructor — but trap 2 then overwrites it.)
  2. Setting depth before addChild is overwritten. With autoDepth on (the default), addChild without an explicit z assigns child.pos.z = children.length after the push, discarding whatever you set.
  3. Setting depth after addChild does not reorder. A "z" sort runs when a child is added (autoSort, deferred to the next tick), not when pos.z changes. If you must change it later, call container.sort() yourself, or use moveToTop / moveUp / moveDown / moveToBottom, which reorder the children themselves. (An "x" / "y" sort is different: see below.)

Sorting on "y" (or "x"), and sortOffset

A container with sortOn = "y" draws its children from the smallest pos.y to the largest, so whatever is lower on screen covers what is above it (top-down and isometric games). "x" does the same along x. A different pos.z still decides first, so keep the children that should interleave at one z.

  • Moving a child re-sorts by itself. With autoSort on (the default), the container checks its order at the end of every update, after physics and the children's own updates, and re-sorts when anything is out of place: a walking character needs no sort() call. A frame where nothing moved costs one pass over the children and does not cause a redraw. Turn autoSort off to keep the children in the order you put them.
  • Ties keep insertion order, and the child added LAST is drawn BEHIND. A torch placed at its wall's exact position disappears behind the wall.

sortOffset changes the ORDER only, never where anything is: the child is lined up as if it stood that many pixels lower (positive) or higher (negative), and is drawn exactly where it was. Use it when pos is not the point to sort by:

torch.sortOffset = 1;                        // win the tie with the wall it hangs on
dust.sortOffset = door.pos.y - dust.pos.y;   // an emitter's pos is the top of its
                                             // spawn area: line it up with the door
prop.sortOffset = prop.height;               // sort by the bottom of the image

A container that keeps itself sorted picks a change up at its next update (with autoSort off, call its sort()). "z" and "depth" sorting ignore it. It must be a finite number (a string or NaN throws, rather than scrambling every sibling), and a pooled renderable starts again at 0.

The exception: under Camera3d the world's sortOn is "depth", and the camera calls container.sortNow(true) every frame. Note that mode does not sort by pos.z as a layer index — it sorts by squared distance from the camera in world space (nearest first in the array, painted far→near). So changing depth does reorder there, but as a position change, not as a layer number.

// ✗ overwritten by autoDepth
sprite.depth = 10;
world.addChild(sprite);

// ✗ no resort happens (except under Camera3d)
world.addChild(sprite);
sprite.depth = 10;

// ✓
world.addChild(sprite, 10);

4. removePostEffect() destroys the effect

To disable an effect temporarily, toggle it. Removing it frees its GPU resources and leaves you holding a dead object.

// ✗ effect is destroyed; re-adding it will not work
sprite.removePostEffect(effect);

// ✓
effect.enabled = false;

Exception worth knowing: an effect with effect.shared === true opts out of auto-destroy, because it is reused across renderables.

Use addPostEffect() / getPostEffect() / removePostEffect(). Assigning renderable.shader = … is deprecated since 19.2.0.

Post effects need a programmable pipeline. A ShaderEffect constructed against the Canvas renderer (renderer.shaderLanguage === null) logs a warning, stays at enabled === false and no-ops every method rather than throwing — and beginPostEffect filters disabled effects out. Since video.AUTO picks the backend at runtime, an effect that works on your machine may do nothing on another.

5. Opacity cascades; setChildsProperty assigns

alpha composes down the tree. Fading a container fades everything inside it, and the values multiply — a child at 0.5 inside a parent at 0.5 draws at 0.25. Each renderable keeps its own value; the composition happens at draw time and is undone when the renderable is finished.

model.alpha = 0.3;                             // fades the whole rig
container.setChildsProperty("alpha", 0.3, true); // REWRITES each child's alpha

The two are not interchangeable. setChildsProperty is a bulk assignment: it changes what the children are, so their own alpha now reads 0.3 and stays that way after the container goes back to full. Reach for it when you mean to change the children, and for alpha when you mean to fade a subtree.

Do not assign alpha down a hierarchy by hand. The renderer composes it as it walks the tree, so setting it at more than one level multiplies: in a rig nested three deep, 0.3 applied at every level lands parts on 0.3, 0.09 and 0.027 depending where they sit. The symptom does not look like an opacity problem — a model whose parts hold different opacities, and which appears to change opacity as it turns, because turning changes which parts face the camera.

A useful consequence: a GLTFModel is a Container, so model.alpha fades every part of a loaded rig — which is what you want for a ghost, a fade-in, or a reflection.

6. Vector3d.set(x, y) silently zeroes z

v.set(10, 20);        // z becomes 0, not "unchanged"
v.set(10, 20, v.z);   // keep it

pos is an ObservableVector3d, whose set(x = 0, y = 0, z = 0) does the same — so this.pos.set(x, y) wipes the object's depth.

Renderable#setPosition(x, y, z?) is the renderable-level form and does not: omit z and the depth is left exactly as it was. Reach for it whenever you are moving an object rather than editing a vector, and keep v.set(x, y, v.z) for the vector itself.

7. A settings key the class never reads is silently ignored

A settings object is a plain literal: nothing rejects a key, so a name that belongs somewhere else does nothing at all and says nothing about it.

// WRONG — only the RENDERER reads a `blendMode` setting, never a renderable
const glow = new Sprite3d(x, y, { image: "sun", blendMode: "additive" });

// right: it is a property
glow.blendMode = "additive";

The same shape catches transparent on a GLTFModel (its parts carry it — model.setChildsProperty("transparent", true, true)) and any Mesh setting handed to a class that forwards only a curated subset. If a visual option appears to do nothing, check whether the class actually reads it before tuning the value.

8. isRenderable does not hide anything

It gates updateBounds, not drawing. Setting it false leaves the object on screen and merely stops its bounds tracking, which is worse than doing nothing.

sprite.isRenderable = false;   // WRONG — still drawn, bounds now stale
sprite.alpha = 0;              // hides it

Use alpha for a blink, or remove the child for a long absence.

A mask belongs to you, not to the renderable

renderable.mask takes a shape you created, and destroy() releases the reference without recycling it: the same instance may still be masking something else, so the engine has no claim on it. If you pool masks, release them yourself.

Before 20.7.0 destroy() tried to recycle it and threw for every type mask accepts, because it used the legacy name-keyed pool and no geometry class is registered there. It failed mid-teardown, so a masked renderable could not be destroyed or removed without keepalive at all.

Update and draw

class Enemy extends Renderable {
    update(dt) {
        this.pos.x += this.speed * (dt / 1000);
        return true;                       // ← true means "redraw me"
    }
    draw(renderer) { /* … */ }
}

update() returns a boolean meaning "this object changed and needs redrawing". The parent accumulates it into the container's isDirty, and the application skips the whole draw pass while nothing is dirty — so returning nothing (falsy) can leave the scene frozen even though state is updating. The base implementation returns this.isDirty, so return super.update(dt) is the other correct ending.

update() is only called when obj.inViewport === true or obj.alwaysUpdate === true. An off-screen object with the default alwaysUpdate = false does not tick at all.

Anchors and floating

  • anchorPoint defaults to centre (0.5, 0.5) on Renderable, but Container forces (0, 0). Mixing them up shifts children by half their size.
  • floating = true pins a renderable to the screen rather than the world, which is what you want for HUD elements. It opts the object out of camera transforms and out of ancestor position accumulation.
  • visibilityLayer decides which cameras draw a renderable: a camera draws it only if it shares a bit with the camera's cullMask (every camera, by default). See melonjs-camera-and-drawing for keeping something off a minimap.

Prefer built-in features

Before writing custom draw() code, check whether the engine already covers it — ParticleEmitter, Tween, the ShaderEffect presets, UISpriteElement and UITextButton, the physics adapters, and the Tiled tilemap renderers all exist. Hand-rolled equivalents miss the batching and the multi-backend support.

Symptom → cause

symptomcause
object draws at its parent's origin, ignores its positiondraw() not reading this.pos
pointer handler registered but never firesisKinematic left at its true default
wrong draw orderanything other than addChild(child, z) — see section 3
me.pool: object ... cannot be recycled while destroyinga mask on a renderable, before 20.7.0
effect cannot be re-enabled after removalremovePostEffect() destroyed it
effect silently does nothingrunning on the Canvas renderer
object appears frozen while its state changesupdate() not returning true
off-screen object never ticksupdate() is gated on inViewport — set alwaysUpdate = true
custom draw offset by half the sizecentred anchorPoint default not zeroed
spatial query never finds an objectisKinematic left true — not in the broadphase
z unexpectedly 0 after a setVector3d.set(x, y) defaults z to 0 — setPosition(x, y) on the renderable leaves the depth alone
a settings option appears to do nothingthe class never reads that key — see section 7
isRenderable = false did not hide the objectit gates bounds, not drawing — use alpha

Related skills

  • melonjs-scenes-and-state — Tween and the easing families, for animating any of this

  • melonjs-getting-started — Application lifecycle and the scene graph

  • melonjs-20-migration — the deprecated shader property and other pre-20 APIs

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

コメント

ログインなしで書き込めます。使ってみた感想、質問、動かなかった環境などを気軽にどうぞ。

まだコメントはありません。最初のコメントを書いてみませんか。

送信するとすぐに公開されます。メールアドレスや電話番号などの個人情報は書かないでください。URL は 2 つまでです。

0 / 1000 文字

このフォームは reCAPTCHA で保護されており、Google のプライバシーポリシーと利用規約が適用されます。

同じリポジトリのスキル

melonjs

無料概要あり

melonJSを使ったゲーム制作で、目的に合う専門ガイドを選ぶための入口です。初期化から描画、入力、物理演算、音声、2D・3D機能まで案内します。

  • melonJSで作る内容に合うガイドを探したいとき
  • 新しいゲームの初期化手順を知りたいとき
  • 3Dやモデル読み込みの参照先を探したいとき
melonjs/melonJS6,4292026年10月29日 更新

melonjs-20-migration

無料概要あり

melonJS 19.x以前の知識を20.x向けに更新し、初期化や描画効果、廃止・非推奨APIの変更点を確認して移行時の不具合を調べられます。

  • 19.x以前のゲームを20.xへ移行したいとき
  • 古いAPIが見つからない原因を調べたいとき
  • 非推奨の描画効果を置き換えたいとき
melonjs/melonJS6,4292026年10月29日 更新

melonjs-3d

無料概要あり

melonJSの3D・2.5Dゲーム向けに、カメラ、メッシュ、ライト、奥行き順、透明表現やフォグの設定と座標系の注意点を確認できます。

  • 3Dカメラをステージに設定したいとき
  • メッシュやビルボードを配置したいとき
  • 奥行き順や透明表現を調整したいとき
melonjs/melonJS6,4292026年10月29日 更新

melonjs-3d-assets

無料概要あり

melonJSでglTF・GLBやOBJ・MTLの3Dモデルを読み込み、マテリアルやライト、アニメーション、影、インスタンスの扱い方を確認できます。

  • glTF・GLBのシーンを読み込みたいとき
  • OBJモデルとMTL素材を使いたいとき
  • モデルの向きや大きさを調整したいとき
melonjs/melonJS6,4292026年10月29日 更新

melonjs-audio

無料概要あり

melonJSで効果音やBGMを読み込み、再生・停止・音量調整するほか、位置に応じた音や音声合成、ミックス全体へのエフェクト設定を扱うスキルです。

  • 効果音とBGMを読み込みたいとき
  • ゲーム内の位置に応じて音を鳴らしたいとき
  • 音声ファイルなしで短い音を作りたいとき
melonjs/melonJS6,4292026年10月29日 更新

melonjs-camera-and-drawing

無料概要あり

melonJSのカメラ追従や画面効果、座標変換、図形描画を実装する際の要点を確認できます。描画のクリッピングやCanvasへの焼き込み、ピクセルの読み出しも扱います。

  • プレイヤーを追うカメラの範囲を設定したいとき
  • ワールド座標と画面座標を変換したいとき
  • 独自描画に図形やグラデーションを使いたいとき
melonjs/melonJS6,4292026年10月29日 更新

melonjs のスキルをすべて見る

このスキルの問題を報告する