Concept Sketchbook · 02 · Gathering

moonn.

还月光

Gather the moonlight from the tree, and return it to the moon.

Date
July 19, 2025
Input
Click along the branches
Built with
p5.js · OpenProcessing
Status
Sketch · playable
Live sketch · Click along the branchesOpen on OpenProcessing ↗
(01)

Exploration

The feeling · Gathering

What if collecting changed the world instead of a score?

There is no counter on screen. The only sign of progress is the sky. Every catch makes the background darker and the tree brighter, so collecting slowly turns the day into night.

(02)

Narrative exploration

The moonlight has ended up in a tree. The player’s job is to shake it loose and give it back. Giving it back is a feeling, not a place on screen.

Each drop of light floats slowly upward as it fades, as if it already knows where it is going.

Collecting light makes the world darker. The more you gather, the closer it comes to night, the hour that belongs to the moon.

Point of view

The player is a go-between. The light belongs to the moon and is caught in the tree. The player owns none of it and is only there to help it go home.

Narrative structure

The story is a single task that takes the length of a day. Its progress is told by light: every return makes it later. The ending is not a message on screen but the moment the sky has gone fully dark.

Motifs

Moonlight in a tree

Something borrowed and not yet given back. A debt of light.

The branching tree

Patience and searching. The light hides in the finest twigs.

Day turning to night

Giving back costs the day. The world grows darker as the debt is paid.

Emotional arc
  1. 01

    Searching

    A pale, bright day and a dark tree. The player looks closely for where to click.

  2. 02

    Rhythm

    Some clicks shake light loose and some don’t. The player settles into a quiet back and forth with the tree.

  3. 03

    Night

    The sky is dark and the tree glows. The light has all gone up, and nothing is left to do but look.

Mechanic as meaning
ActionMeaning
Shaking a branch→Asking the tree to let go
A miss (50%)→The tree doesn’t always answer
Light rising and fading→Being returned
The sky darkening→Time moving toward the moon’s hour
(03)

Result and iteration

What came out of it

Gathering turned into a slow change of time. After thirty catches the day is gone and the tree glows on its own. The feeling that came out of it is giving back, not taking.

Decisions in the code

Hit distance · 12 px

Clicks have to land on a branch, so the player looks closely at the tree.

Chance · 50%

Half the clicks give nothing. The tree feels alive, not like a dispenser.

Thirty catches to night

Long enough to feel the light change, short enough to finish in one sitting.

(04)

Code

Structure
setup()
Grows the tree once with drawTree() and stores every branch as a line segment.
drawTree()
Recursive: each branch splits into 2–3 children, turning up to 60° and shrinking to 60–85% of its length, until branches are shorter than 6 px.
mousePressed()
Measures the click’s distance to each branch segment with distToSegment(), then rolls the 50% chance.
draw()
Maps the number of catches to sky and tree colour, then moves each drop of light up and fades it.
draw(): the sky keeps the scorep5.js
function draw() {
  // 0 → 30 catches: the sky goes from day (250) to night (80)
  let bgBase = map(lightCount, 0, 30, 250, 80, true);
  background(bgBase, bgBase - 10, bgBase - 20);

  // ...and the tree brightens as the sky darkens
  let treeBrightness = map(lightCount, 0, 30, 40, 180, true);
  for (let b of branches) {
    strokeWeight(b.w);
    stroke(treeBrightness, treeBrightness - 10, treeBrightness - 30, b.alpha);
    line(b.x1, b.y1, b.x2, b.y2);
  }

  // each drop of light rises slowly and fades
  for (let i = lights.length - 1; i >= 0; i--) {
    let l = lights[i];
    l.y -= 0.3;
    l.alpha -= 0.8;
    fill(255, 240, 180, l.alpha);
    noStroke();
    ellipse(l.x, l.y, l.size);
    if (l.alpha <= 0) lights.splice(i, 1);
  }
}

Comments added for reading. Two map() calls replace a score display: the same counter drives the sky and the tree in opposite directions.

Full source on OpenProcessing ↗The embedded sketch above also has a Code tab with the complete source.

(05)

Method

What this sketch taught me about the method
01

Let the world keep score

Progress is a colour change, not a number, so the player watches the world instead of the HUD.

02

Misses are part of the voice

A 50% chance makes the tree feel like it is choosing.

03

Let things leave

Light that rises and fades is returned rather than owned.

See the method behind the series →

概念速写本 · 02 · 收集

还月光.

moonn

收集树上的月光,把它还给月亮。

日期
2025 年 7 月 19 日
操作
沿着树枝点击
工具
p5.js · OpenProcessing
状态
速写 · 可试玩
在线试玩 · 沿着树枝点击在 OpenProcessing 打开 ↗
(01)

探索

感受 · 收集

如果收集改变的不是分数,而是整个世界?

画面上没有计数器,唯一的进度提示是天空。每收集一次,背景就暗一点,树就亮一点,收集这件事慢慢把白天变成了夜晚。

(02)

叙事探索

月光落在了一棵树上。玩家要做的,是把它摇下来,还回去。“还给月亮”是一种感觉,不是画面上的某个地方。

每一滴光都会一边淡去,一边慢慢往上飘,好像它已经知道要去哪里。

收集光会让世界变暗。收得越多,天就越接近夜晚,那是属于月亮的时刻。

视角

玩家是一个中间人:光属于月亮,被树留住了。玩家什么也不拥有,只是帮它回家。

叙事结构

故事是一件需要一整天来完成的事。它的进展由光来讲述:每归还一次,天就晚一点。结尾不是屏幕上的一句话,而是天空完全暗下来的那一刻。

意象

树上的月光

借来还没还的东西,一笔光的债。

分叉的树

耐心与寻找。光藏在最细的枝丫里。

白天变成黑夜

归还要付出白天。债越还越清,世界也越来越暗。

情绪曲线
  1. 01

    寻找

    明亮苍白的白天,一棵深色的树。玩家仔细地找可以点的地方。

  2. 02

    节奏

    有的点击能摇下光,有的不能。玩家和树之间慢慢有了一来一回的节奏。

  3. 03

    夜晚

    天黑了,树亮着。光都已经飘上去,剩下的只有看。

机制即意义
动作含义
摇动树枝→请树放手
落空(50%)→树并不总是回应
光上升、淡去→被归还
天色变暗→时间走向属于月亮的时刻
(03)

结果与迭代

得到了什么

收集变成了时间缓慢的流转。收满三十次,白天就过去了,树自己亮了起来。由此得到的感受是“归还”,而不是“拿走”。

代码里的决定

点击判定 · 12 px

点击必须落在树枝上,玩家会仔细地看这棵树。

成功率 · 50%

一半的点击什么也得不到,树显得是活的,而不是一台出货机器。

三十次入夜

长到能感觉光在变化,短到一次就能玩完。

(04)

代码解读

结构
setup()
用 drawTree() 一次性长出整棵树,把每根枝条存成一条线段。
drawTree()
递归:每根枝条分出 2–3 根子枝,偏转最多 60°,长度缩到 60–85%,直到短于 6 px。
mousePressed()
用 distToSegment() 算出点击到每根枝条的距离,再掷一次 50% 的概率。
draw()
把收集次数映射成天空和树的颜色,再让每滴光上升、淡去。
draw():天空就是计分板p5.js
function draw() {
  // 0 → 30 catches: the sky goes from day (250) to night (80)
  let bgBase = map(lightCount, 0, 30, 250, 80, true);
  background(bgBase, bgBase - 10, bgBase - 20);

  // ...and the tree brightens as the sky darkens
  let treeBrightness = map(lightCount, 0, 30, 40, 180, true);
  for (let b of branches) {
    strokeWeight(b.w);
    stroke(treeBrightness, treeBrightness - 10, treeBrightness - 30, b.alpha);
    line(b.x1, b.y1, b.x2, b.y2);
  }

  // each drop of light rises slowly and fades
  for (let i = lights.length - 1; i >= 0; i--) {
    let l = lights[i];
    l.y -= 0.3;
    l.alpha -= 0.8;
    fill(255, 240, 180, l.alpha);
    noStroke();
    ellipse(l.x, l.y, l.size);
    if (l.alpha <= 0) lights.splice(i, 1);
  }
}

注释为方便阅读而加。两个 map() 取代了分数显示:同一个计数同时驱动天空和树,方向相反。

在 OpenProcessing 查看完整源码 ↗上方嵌入的速写里也有 Code 标签,可以查看完整源码。

(05)

方法

这个速写对方法的启发
01

让世界来计分

进展是颜色的变化,不是数字,玩家看的是世界,而不是界面。

02

落空也是一种声音

50% 的概率让树显得像在做选择。

03

让东西离开

会上升、会淡去的光是被归还,而不是被拥有。

查看整个系列的方法 →