Start your journey of self-exploration in (Un)Faced here:
https://suqichen777.github.io/CCLab/UnFaced-Final-Version/
Scroll and Click are enough for this immersive trip. Enjoy yourself and look into the mirror from time to time.
Here is a detailed blog post about my design on (Un)Faced. If you are only interested in the final showing condition, you can go to the simplified blog or GitHub page. The following content in this blog post will include my Design Process, Coding Process, Reflection & Future Development, and Credits & Reference.
(Un)Faced is a scrolling story about identity and conformity in a future ruled by masks.
You are a member of "We": You are the same, non-deviant, and always safe with your mask on—no fear of being different as "yourself." However, when your mask falls and reveals a void, your journey to rediscover the self begins.
(Un)Faced contains 4 chapters:
Chapter 1 (Wear Your Mask): I put on the mask with "everyone in We" saying, "Wear Your Mask." However, one day my mask falls down, and I find that there is nothing on my face.
Chapter 2 (Escape): I am terrified. Looking at the map of the "We" organization, I make up my mind to escape.
Chapter 3 (Factory): Reaching the exit, I accidentally find another room. There are so many conveyor belts, and what's on them is someone with or without a face.
Chapter 4 (We): At the far end of the factory building, I saw the neon sign for WE. After the frantic crash, W gradually tilted, eventually becoming ME. I looked in the mirror again, and this time I saw my own face.
The flowchart of (Un)Faced is:
It should be noted that each chapter may have different scenes to organize different layers and assets. It is controlled by a global state with different boundaries for different scenes, which will be further explained in the coding part. This ASCII-style art flowchart is generated by AI, but I filled in every slot manually myself.
Looking into the mirror was designed to be perfectly connected to the end of each chapter as a smooth transition, but given the time limit, I chose a separate "transition mode" that allows "me" to go back to a spiritual world. Thus, I could avoid complex 3-D room building for a reasonable appearance of mirrors, yet still mark a new step in the whole story.
Each transition starts with the eyes blinking to simulate the situation of “waking up in a new environment after fainting.” In fact, the transition room represents where the "self" is stored deep in "my" heart, and entering the transition room means experiencing a new step of self-exploration. Thus, each time "I" look into the mirror (after a period of "Chapter" self-exploration), my face will be clearer.
Reference Link: YouTube
I have three identifiers for the recognition of self-awareness designed for (Un)Faced. Color (or complexity), art style, and mirror image.
The first chapter starts with black and white, with simple lines and shapes. It becomes more complex with more color in the following chapters. For example, in Chapter 2 (Escape), when viewing "myself" on the map, it is light red at the entrance, and totally red at the exit. Refinding "color" also means rediscovering self-awareness.
I used the "hand-drawn" style to refer to the "world inside the mirror," while the realistic style was for our real life. Since the first transition implies stepping into the mirror world, all the following chapters happened with the "hand-drawn" style as a whole, and other realistic images are inserted in the form of a magazine collage to create a sense of absurdity.
Every time I look in the mirror, my face becomes a little bit clearer. Mirror is also what I have put in the Time Capsule for the final presentation.
After Chapter 1 (Wear Your Mask)
After Chapter 2 (Escape), where I became "red" (from "white") and got my color back.
After Chapter 3 (Factory), where I realized that "we" and "us" behind masks without faces are not born like that but produced by this society.
I have made sound effects for the key scenes where the audience might not know how to operate. For example, at the beginning of each transition, I inserted the sound of a heartbeat. It is because the eye blinking will only appear when the audience starts scrolling, while the default transition page is all in black. The sound effect is used to remind them that it is not a webpage or network error; instead, their interaction is needed. I have also used sound effects to make something not implemented visually more vivid; for example, in Chapter 1, when the user clicks on the red button, the door opens. However, I was not able to draw a 3D opening process with p5; thus, I chose to use sound as an alternative.
My initial default page was implemented with a FaceMesh consisting of flashing characters. And the initial design was that when clicking, the characters "condense" to form a mask, which could lead to a smooth transition to the first chapter with a mask on. This idea was eventually given up due to the page lag caused by insufficient performance with the machine learning model.
Face was expected to appear on the screen with each point changing, representing characters. What happened on the left was the frequent face disappearance as a result of insufficient performance.
This is my project structure explained in README.md:
The core storytelling flow is controlled by state.js and constants.js. They are exported into other js files to make sure all global variables can be used and read by different scenes and transitions. Images below show the basic structure of the state, and some important elements in it (not all of them are in the screenshot).
The image above shows constants.js.
All the global images and sound assets are also included in the state. Both the scene length and bounds and the transition duration are defined in the constants.js.
Input routing is defined in input.js: mouse wheel controls scroll or transition time, which is defined in the global state as state.currentScrollingPosition (in Chapter/Scene), or state.; click starts story or triggers door; keyboard is only used for chapter 2 as an alternative to scrolling on the x-axis. More detailed explanation could be found in the code image below.
The other OOP design could be found here. I would mainly explain how transitions and scenes work as single classes here.
The transition class is way more complex than the Scene class because the rendering logic of all transitions is almost the same, and they are implemented as methods for inner rendering and calling. Only slight changes would be made by the current transitionCode. However, the render functions for different scenes are different from each other, and they are defined separately. Thus, the rendering function for different scenes could be attached to them respectively.
To explain the transition class in detail: each transition is created in setup() in the global transition array, and at the end of each chapter, the corresponding scene would call the state.transitions[i].startTransition() method to initialize the current transition and change the global state during transition to true. Then, the draw() function calls the current transition's render method. It decides what to show on the screen depending on the current scrolling position inside a transition, and calls other transition methods correspondingly. At the render method, the endTransition() method is called, and then, all global variables or layers are reset to prepare for the next scene, given the current transition code.
I have to admit that the update function of my self-defined Rectangle Class is a bad design: it takes the global variable (current scrolling position) as the calculating dependency. It should have been changed to pass-in parameters
However, due to my later use of this rectangle being almost the same: it is only used in Chapter 1 and Transitions, I turned out another developing mode.
To further explain, updateTransitionScene2 (a transition method) remaps transition progress to the room’s scroll span to sceneBounds[2].start and sceneBounds[2].end. It temporarily overrides state.currentScrollingPosition, as it is totally not used in the transitions, so the existing room code can resize the noisy rectangle and recompute wall endpoints. Room size lerps from large to final, creating a zooming walk-in feel. Finally, it restores the previous scroll value to avoid affecting normal scene flow.
SceneMe runs on a separate layer that holds the “W” and “E” letters. Everything, including letter position and physical variables, would be used later. Inside SceneMe, each frame would update the current physical variables by integrating air friction, wall bouncing(damping), and updating the current letter positions. Moreover, Qualified bounces that are fast enough and spaced by 500 ms would increment sceneMeBounceCount and gradually rotate the W up to 180° to form "M". The time period is set to prevent the numerous impacts in a short period of time in a corner.
Bad Project Organization: (Un)Faced is a rather big project compared to my former mini-projects. I started it with less preparation and wanted to organize all the visual elements by scenes. However, I found that only scenes could not solve the problem because of my scene's boundary settings. Then I introduced the "Transition" class to be inserted between scenes. It worked, but destroyed my original architecture and forced me to rebuild the project with extra structures. Then I continued developing the following modules in different files, which led to a mess in the end. I had to reorganize all the files after completing the project to make sure my technical explanations resonable.
I hope that in the future I could write the storytelling first, and then decide on a rather defined and available framework, and finally develop the whole project inside this structure, instead of developing while writing the script.
Edge Case Protection: the scrolling feature of the mouse makes the global state.currentScrollingPosition changes without expectation since even when I stop scrolling, there might still be an inner inertia changing without interaction. Referring to my former code in the INPUT explanation, you can find that there are many "if" and "return" flow controls in the mouse wheel function. It was because I did not design a method to accommodate all edge cases, and scrolling back and forth would frequently lead to bugs. Therefore, my current design is to prevent all possible bugging conditions manually, but further development would still invent new edge-case bugs. I think it could be addressed by editing the inner properties of the Scene class by constraining in-scene data changing.
First, I am satisfied with the completion of my final results since my practice in creating 3D scenes and drawing was really time-consuming. Although the total story length and my role as a "director" were not satisfactory due to the time limit and technical issues, the overall project objective of expression on conformity and self-exploration is still fully achieved. I appreciate my design on the final chapter "We" most because of the use of pixel art and letter rotation to transform from "We" to "Me" super visually and creatively.
The concept of "Mask" should be used more frequently. The mask class was the first OOP element I used in my code in my very initial and ambitious proposal. However, only the beginning of the first chapter used this class in my final version.
More interaction: I hope more interaction with the keyboard could be implemented. Currently the keyboard is only used for Chapter 2 Escape for those who cannot use a touchboard for movement on the x-axis.
3D scenes with WebGL (not only simulating a 3D first-person camera using 2D graphics): I think the non-completion of my original proposal should also be explained by the difficulty of 3D programming with native P5. Since WebGL could be integrated into the whole canvas, I think it might also be a better choice to use 3D modeling and a first-person camera for the room transition and scene building.
Smooth transition and consistency (Feedback): I chose to continuously enlarge a certain element for scene-to-scene transition, such as the map in the second chapter, the "WE" identifier in the third chapter, and the mirror in transition. It might not be smooth and would lead to misunderstanding, which can be improved using the true 3D scene building mentioned in the third point.
Longer storytelling on "exploration" and less transition length (Feedback): due to the ease of reusing, my narrative is largely composed of transitions, while chapters, due to their developing difficulty, are simplified into 2D mini-games or purely scrolling interactive scenes with minimal scene changes. I hope to enrich the script content and make the stories in the chapters more attractive.
Images: Unsplash (Mirror and Factory images)
Sound Effect: elevenLabs
FaceMesh: ml5.js and CCLab live code document
Scrolling Design Inspiration: Gohai’s class code
First-Person Mask Design: Minecraft first-person design on the right (With Pumpkin Equipped)
Room Design (Transition / Chapter 2) Inspiration : The Substance