Fishboy.
An interactive piece about memory, childhood and the quiet metaphors of the sea.
Exploration
Can patience be the mechanic?
Clicking anywhere gives a fragment. Only staying in one place makes something whole. The player has to choose to return to the same spot, again and again, which is also how memory works.
Narrative exploration
A child catches a fish by the sea and hides it in his belly. The fish learns to breathe on land, and the child slowly can’t.
The story is ten short lines, in Chinese and English. Each time a finished fish is clicked, the next line appears, in order.
The last lines turn the story around. The boy is the one in the water now, looking up, and the fish sits with his mother.
The story is told in the first person by the boy, as an adult looking back. The player is not the boy. The player is the one who makes the fish, and so the one who lets him remember.
Ten lines, in order, like a short memoir told one sentence at a time. Each line costs six patient clicks, so the reader can’t skim. After the last line the fish keep whispering random lines, the way a memory replays long after it has been told.
The fish
A secret kept inside the body. Something that belongs to the sea, carried onto land.
The mother
The one who said let it go, and the one the fish ends up with.
The fish tank
The reversal. The boy who took the sea inside ends up inside the glass himself.
- 01
Scattered
Early clicks leave fragments everywhere. The screen looks like a half-remembered childhood.
- 02
Holding still
The player learns to stay in one place, and the first fish appears. The story begins.
- 03
Reversal
The last lines arrive: the boy in the tank, the fish with his mother. The fragments on screen now read as everything he lost.
Result and iteration
Patience became visible. The screen fills with fragments from every time the player wandered, and only a few whole fish. The feeling that came out of it is the slowness of remembering.
When I was a child, I caught a fish by the sea.小时候,我在海边抓到了一条鱼。
My mother was frightened and told me to let it go.母亲很害怕,叫我放掉它。
I secretly hid the fish in my belly.我偷偷地把鱼藏在肚子里。
Later, it learned to breathe, while I gradually could not adapt to life on land.后来,它学会了呼吸,而我,渐渐无法适应在陆地的生活。
Now, it sits with my mother.现在,它和母亲坐在一起。
And I turned over in the fish tank, all alone.而我,在鱼缸里寂寞地翻了个身。
Six clicks
Few enough to finish, many enough that it feels like staying.
A 30 px circle
The six clicks must fall within 30 px of the first one. Close, not exact, so a hand can manage it.
Lines in order, then at random
The ten lines play in sequence. After the last, fish return random lines, like a memory replaying.
Code
story[]- Ten [Chinese, English] pairs, read with storyIndex.
Blob- Soft, noise-shaped background forms that drift slowly.
FishPart- The fragment left by a click that didn’t complete a fish.
CompleteFish- A finished fish, with isClicked() to tell the story.
mousePressed()- Checks fish first, then keeps the last six clicks and tests whether they all fall in one place.
function mousePressed() {
// clicking a finished fish tells the next line of the story
for (let f of completeFishes) {
if (f.isClicked(mouseX, mouseY)) {
let [cn, en] = storyIndex < story.length
? story[storyIndex++]
: random(story);
currentText = cn + "\n" + en;
return;
}
}
// remember the last six clicks
clickHistory.push(createVector(mouseX, mouseY));
if (clickHistory.length > 6) clickHistory.shift();
// six clicks within 30 px of each other become a fish
if (clickHistory.length === 6) {
let consistent = clickHistory.every(v =>
dist(v.x, v.y, clickHistory[0].x, clickHistory[0].y) < 30);
if (consistent) {
completeFishes.push(new CompleteFish(mouseX, mouseY));
clickHistory = [];
return;
}
}
// otherwise, only a fragment is left behind
fishParts.push(new FishPart(mouseX, mouseY));
}Lightly condensed from the original for reading. The whole mechanic is a six-item memory of where the player clicked.
Full source on OpenProcessing ↗The embedded sketch above also has a Code tab with the complete source.
Method
Make waiting visible
Fragments stay on screen, so time spent is never lost.
Forgive the hand
A 30 px tolerance asks for intention, not precision.
Pay out story slowly
One line per fish keeps the narrative tied to the effort.
鱼男孩.
一件关于记忆、母亲与童年隐喻的互动作品。
探索
耐心本身能不能就是玩法?
随便点一下,只会得到一个碎片;只有守在同一个地方,才能拼出完整的东西。玩家得选择一次又一次回到同一个位置,记忆也是这样运作的。
叙事探索
一个孩子在海边抓到一条鱼,把它藏进了肚子里。鱼学会了在陆地上呼吸,孩子却渐渐不能。
故事是十句短句,中英对照。每点一次完整的鱼,就按顺序出现下一句。
最后几句把故事翻了过来:现在在水里抬头往上看的是男孩,鱼和母亲坐在一起。
故事由男孩以第一人称讲述,是长大后的他在回望。玩家不是男孩,玩家是造出鱼的人,也就是让他得以回忆的人。
十句话,按顺序讲,像一篇一次只说一句的短回忆录。每一句都要用六次耐心的点击换来,读者没法跳着看。最后一句之后,鱼仍会随机低语其中某句,就像一段记忆讲完很久以后还在回放。
鱼
藏在身体里的秘密,属于大海、却被带上陆地的东西。
母亲
说“放掉它”的人,也是鱼最后陪着的人。
鱼缸
反转。把海藏进身体的男孩,最后自己待在了玻璃里面。
- 01
零散
一开始的点击把碎片留得到处都是,屏幕像一段记不全的童年。
- 02
守住
玩家学会停在一个地方,第一条鱼出现,故事开始了。
- 03
反转
最后几句来了:鱼缸里的男孩,陪着母亲的鱼。屏幕上的碎片此时读起来,像是他失去的一切。
结果与迭代
耐心变得看得见:屏幕上留满了玩家每次走神留下的碎片,完整的鱼只有几条。由此得到的感受是“回忆的缓慢”。
小时候,我在海边抓到了一条鱼。When I was a child, I caught a fish by the sea.
母亲很害怕,叫我放掉它。My mother was frightened and told me to let it go.
我偷偷地把鱼藏在肚子里。I secretly hid the fish in my belly.
后来,它学会了呼吸,而我,渐渐无法适应在陆地的生活。Later, it learned to breathe, while I gradually could not adapt to life on land.
现在,它和母亲坐在一起。Now, it sits with my mother.
而我,在鱼缸里寂寞地翻了个身。And I turned over in the fish tank, all alone.
六次点击
少到能完成,多到让人感觉是在“守着”。
30 px 的圆
六次点击都要落在第一次点击 30 px 以内:接近即可,不必精确,手能做到。
先按顺序,后随机
十句按顺序出现;读完最后一句后,鱼会随机重复某一句,像记忆在回放。
代码解读
story[]- 十组【中文,英文】句子,用 storyIndex 依次读取。
Blob- 柔软的、由噪声塑形的背景形状,慢慢漂移。
FishPart- 没能拼成鱼的那次点击留下的碎片。
CompleteFish- 完整的鱼,isClicked() 用来触发故事。
mousePressed()- 先检查是否点中了鱼,再记录最近六次点击,判断它们是否落在同一处。
function mousePressed() {
// clicking a finished fish tells the next line of the story
for (let f of completeFishes) {
if (f.isClicked(mouseX, mouseY)) {
let [cn, en] = storyIndex < story.length
? story[storyIndex++]
: random(story);
currentText = cn + "\n" + en;
return;
}
}
// remember the last six clicks
clickHistory.push(createVector(mouseX, mouseY));
if (clickHistory.length > 6) clickHistory.shift();
// six clicks within 30 px of each other become a fish
if (clickHistory.length === 6) {
let consistent = clickHistory.every(v =>
dist(v.x, v.y, clickHistory[0].x, clickHistory[0].y) < 30);
if (consistent) {
completeFishes.push(new CompleteFish(mouseX, mouseY));
clickHistory = [];
return;
}
}
// otherwise, only a fragment is left behind
fishParts.push(new FishPart(mouseX, mouseY));
}为方便阅读略作精简。整个机制就是一段只记六次的“记忆”:玩家刚才点在了哪里。
在 OpenProcessing 查看完整源码 ↗上方嵌入的速写里也有 Code 标签,可以查看完整源码。
方法
让等待被看见
碎片会留在屏幕上,花掉的时间不会白费。
宽容手的误差
30 px 的容差要的是意图,不是精确。
故事慢慢给
一条鱼一句话,让叙事和付出绑在一起。