Skip to content

Instantly share code, notes, and snippets.

@rndmcnlly
Last active January 11, 2026 03:27
Show Gist options
  • Select an option

  • Save rndmcnlly/5aed9953724bba983b46e7f2ce494c7b to your computer and use it in GitHub Desktop.

Select an option

Save rndmcnlly/5aed9953724bba983b46e7f2ce494c7b to your computer and use it in GitHub Desktop.
CMPM 171 W26 Interest and Experience Survey results, interactive scene summary, for some reason
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CMPM 171 Survey Infographic</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #1a1a2e;
overflow: hidden;
}
#container {
width: 100vw;
height: 100vh;
position: relative;
}
#canvas-container {
width: 100%;
height: 100%;
}
#fullscreen-btn {
position: absolute;
top: 16px;
right: 16px;
background: rgba(255, 222, 173, 0.95);
border: 3px solid #a67c52;
border-radius: 12px;
padding: 10px 18px;
font-size: 16px;
font-weight: bold;
color: #5d4037;
cursor: pointer;
z-index: 100;
transition: transform 0.2s, background 0.2s;
}
#fullscreen-btn:hover {
background: #ffe4b5;
transform: scale(1.05);
}
#title-banner {
position: absolute;
top: 16px;
left: 16px;
background: rgba(255, 183, 197, 0.95);
border: 3px solid #e91e63;
border-radius: 12px;
padding: 12px 20px;
z-index: 100;
}
#title-banner h1 {
font-size: 18px;
color: #880e4f;
margin: 0;
}
#title-banner p {
font-size: 12px;
color: #ad1457;
margin-top: 4px;
}
#tooltip {
position: absolute;
display: none;
background: rgba(255, 253, 231, 0.98);
border: 3px solid #8bc34a;
border-radius: 16px;
padding: 16px;
max-width: 320px;
pointer-events: none;
z-index: 200;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
#tooltip.visible {
display: block;
animation: tooltipIn 0.2s ease-out;
}
@keyframes tooltipIn {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
#tooltip-id {
font-family: 'Courier New', monospace;
font-size: 11px;
color: #7cb342;
background: #f1f8e9;
padding: 4px 8px;
border-radius: 6px;
margin-bottom: 8px;
display: inline-block;
}
#tooltip-title {
font-size: 18px;
font-weight: bold;
color: #33691e;
margin-bottom: 8px;
}
#tooltip-body {
font-size: 14px;
color: #555;
line-height: 1.5;
}
#instructions {
position: absolute;
bottom: 16px;
left: 50%;
transform: translateX(-50%);
background: rgba(179, 229, 252, 0.95);
border: 3px solid #03a9f4;
border-radius: 12px;
padding: 10px 20px;
font-size: 14px;
color: #01579b;
z-index: 100;
}
</style>
</head>
<body>
<div id="container">
<div id="canvas-container"></div>
<div id="title-banner">
<h1>🎮 CMPM 171 Interest Survey Results</h1>
<p>Hover over objects to explore insights</p>
</div>
<button id="fullscreen-btn">⛶ Fullscreen</button>
<div id="tooltip">
<div id="tooltip-id"></div>
<div id="tooltip-title"></div>
<div id="tooltip-body"></div>
</div>
<div id="instructions">🖱️ Hover to inspect • Drag to orbit • Scroll to zoom</div>
</div>
<script src="https://unpkg.com/[email protected]/build/three.min.js"></script>
<script src="https://unpkg.com/[email protected]/examples/js/controls/OrbitControls.js"></script>
<script>
const OrbitControls = THREE.OrbitControls;
const isTouch = ('ontouchstart' in window) || navigator.maxTouchPoints > 0;
// ==================== SCENE DEFINITION DSL ====================
const SCENE_DATA = {
camera: {
position: [8, 10, 12],
lookAt: [0, 1, 0],
fov: 50
},
lighting: {
ambient: { color: "#ffeeff", intensity: 0.4 },
directional: { color: "#fffaf0", intensity: 0.6, position: [10, 20, 10] },
fill: { color: "#e0f7fa", intensity: 0.2, position: [-10, 5, -10] }
},
objects: [
// === FLOOR ===
{
id: "floor_main",
type: "cuboid",
position: [0, -0.25, 0],
dimensions: [16, 0.5, 16],
color: "#a8e6cf",
tooltip: {
title: "Common Ground 🏠",
body: "145 of you responded to the survey. You're standing on shared foundation now—most of you run Windows, mass around Unity, and have played more browser games than you've admitted in polite company. This is the floor where prototypes will be born, tested, and honorably retired."
}
},
// === MAIN TABLE ===
{
id: "table_main_surface",
type: "cuboid",
position: [0, 2.4, 0],
dimensions: [5, 0.2, 3],
color: "#ffcc80",
tooltip: {
title: "The Collaboration Surface 🪵",
body: "Your teammates are not your only collaborators. CMPM 171 is a peak moment for peeking into other teams' processes, helping them out, and learning from them. This table has room for more chairs than just your team's. Pull one up from another group sometime."
}
},
{
id: "table_leg_fl",
type: "cuboid",
position: [-2.2, 1.15, 1.2],
dimensions: [0.25, 2.3, 0.25],
color: "#ffab40",
tooltip: {
title: "Pillar: Pre-Production 🦵",
body: "One of four legs holding up your work. Pre-production is NOT production. CMPM 171 is about building the machinery that clarifies how the final product will look and work—not the final product itself. Be proud of wisdom about what's NOT in your Vertical Slice."
}
},
{
id: "table_leg_fr",
type: "cuboid",
position: [2.2, 1.15, 1.2],
dimensions: [0.25, 2.3, 0.25],
color: "#ffab40",
tooltip: {
title: "Pillar: Prototyping 🦵",
body: "Quick-and-dirty work is a skill. Many of you came from CMPM 170 with an unclear distinction between focused prototypes and high-polish-but-merely-small videogames. Set a 90-minute timer. Answer ONE design question. Stop when the timer stops."
}
},
{
id: "table_leg_bl",
type: "cuboid",
position: [-2.2, 1.15, -1.2],
dimensions: [0.25, 2.3, 0.25],
color: "#ffab40",
tooltip: {
title: "Pillar: Release Practice 🦵",
body: "There's a gap between getting something playable onto the public web for real playtesters (practice early and often!) and landing your final deployment on Steam (a corrupting distraction from pre-production). Know which leg you're standing on."
}
},
{
id: "table_leg_br",
type: "cuboid",
position: [2.2, 1.15, -1.2],
dimensions: [0.25, 2.3, 0.25],
color: "#ffab40",
tooltip: {
title: "Pillar: Scope Discipline 🦵",
body: "You shouted this one at the top of your lungs in the survey: SCOPE MANAGEMENT. Frequent prototyping facilitates communication about how your game's core can be deepened and how it can live WITHOUT distracting add-ons. This leg bears the most weight."
}
},
// === MONITOR ===
{
id: "monitor_base",
type: "cuboid",
position: [0, 2.55, -0.8],
dimensions: [0.8, 0.1, 0.5],
color: "#90a4ae",
tooltip: {
title: "Stable Foundation 🖥️",
body: "PC was your most common gaming platform. But here's the thing—iOS, desktop browser, and mobile browser were ALL more common than any brand-name console. Your audience might not be where you assume."
}
},
{
id: "monitor_stand",
type: "cuboid",
position: [0, 2.85, -0.8],
dimensions: [0.15, 0.5, 0.15],
color: "#78909c",
tooltip: {
title: "The Neck (Don't Crane Yours) 🦒",
body: "Ergonomics matter for avoiding crunch. You wanted better self-management: predicting task duration, avoiding last-minute death marches, balancing this course with your other responsibilities. The stand keeps the screen at eye level. Keep your tasks there too."
}
},
{
id: "monitor_screen",
type: "cuboid",
position: [0, 3.5, -0.9],
dimensions: [1.8, 1.1, 0.08],
color: "#4fc3f7",
tooltip: {
title: "The Browser Window 🌐",
body: "Platform diversity looks LOW in your releases. Be wary of trying to make your mark with yet another Unity/PC game. Consider the browser platform—portfolio readers can experience your work FIRST-HAND without downloading anything. That's magic."
}
},
{
id: "monitor_bezel",
type: "cuboid",
position: [0, 3.5, -0.85],
dimensions: [2, 1.3, 0.05],
color: "#37474f",
tooltip: {
title: "The Frame Around Your Work 🖼️",
body: "Constraints can be creative. Some of you expressed pride in working within interesting technical or resource constraints. The bezel isn't the art—but it shapes how people see the art. What frames will you choose for your prototypes?"
}
},
// === KEYBOARD & MOUSE ===
{
id: "keyboard",
type: "cuboid",
position: [0, 2.55, 0.3],
dimensions: [1.2, 0.08, 0.4],
color: "#fff9c4",
tooltip: {
title: "The Input Device ⌨️",
body: "For almost all programming language categories, you felt comfortable starting or continuing projects with peer support. The main exception: assembly languages, where nearly half of you would need to learn from scratch. Good news: nobody's making you write assembly in CMPM 171. Probably."
}
},
{
id: "mouse",
type: "cuboid",
position: [1.0, 2.53, 0.3],
dimensions: [0.2, 0.12, 0.35],
color: "#fff9c4",
tooltip: {
title: "Precision Pointing 🖱️",
body: "Future collaborators will appreciate your TASTE for when to do quick-and-dirty work. An appropriately rough prototype with surgical precision in how it answers a specific question can boost their perception of you more than a polished game with no clear design question behind it."
}
},
// === LAPTOP ===
{
id: "laptop_base",
type: "cuboid",
position: [-1.8, 2.55, 0.6],
dimensions: [1.0, 0.06, 0.7],
color: "#b0bec5",
tooltip: {
title: "The Portable Workstation 💻",
body: "Windows dominates, but about 30% of you also use macOS. Your laptop comes to class, goes home, travels to game jams. Make sure your project builds on your teammates' machines too. 'Works on my machine' is not a shipping platform."
}
},
{
id: "laptop_screen",
type: "cuboid",
position: [-1.8, 3.0, 0.25],
dimensions: [1.0, 0.7, 0.04],
color: "#81d4fa",
rotation: [-0.3, 0, 0],
tooltip: {
title: "The Second Screen Angle 📐",
body: "You have surface-level knowledge of engines and want to go deeper. Team structures in CMPM 171 let you specialize and direct your own learning. Expect to access deeper knowledge in office hours or direct expert contact—not from lecture. Tilt toward experts."
}
},
// === COFFEE CUP ===
{
id: "coffee_cup",
type: "cuboid",
position: [1.8, 2.7, 0.8],
dimensions: [0.25, 0.35, 0.25],
color: "#ffab91",
tooltip: {
title: "Contraband Fuel ☕",
body: "Not technically allowed in the lab, but here anyway. Like the shortcuts and quick-and-dirty work that experienced developers know when to deploy. Future collaborators will appreciate your taste for when rules serve you and when they don't. Set yourself a 90-minute timer and get a playable answer to a specific design question."
}
},
// === BINDER STACK ===
{
id: "binder_1",
type: "cuboid",
position: [2.0, 2.58, -0.4],
dimensions: [0.4, 0.12, 0.5],
color: "#ef9a9a",
tooltip: {
title: "The Fowler Book (Red) 📕",
body: "Design patterns and architecture—you want more. Read the rest of the Fowler book from CMPM 121. Did you know Unity is assembled from many separately-available and often open-source libraries? Patterns are everywhere once you look."
}
},
{
id: "binder_2",
type: "cuboid",
position: [2.0, 2.70, -0.4],
dimensions: [0.4, 0.12, 0.5],
color: "#ce93d8",
tooltip: {
title: "The Postmortem Archive (Purple) 📒",
body: "Document your skills growth. Some of you expressed pride in exactly this—tracking your own development over time. Leaders: get hard evidence of leadership into the codebase as plans, reports, visions, or convince teammates to commit their non-code ideas in writing."
}
},
{
id: "binder_3",
type: "cuboid",
position: [2.0, 2.82, -0.4],
dimensions: [0.4, 0.12, 0.5],
color: "#80cbc4",
tooltip: {
title: "The Playtest Notes (Teal) 📗",
body: "Many of you remarked on the difficulty of disentangling feedback that needs addressing from feedback that needs discarding. Having specific QUESTIONS in mind for playtests helps you own which aspects feel rough and sort relevant answers from noise."
}
},
// === CHAIR ===
{
id: "chair_seat",
type: "cuboid",
position: [0, 1.5, 2.5],
dimensions: [1.2, 0.15, 1.2],
color: "#f48fb1",
tooltip: {
title: "The Hot Seat 🪑",
body: "Some of you have been leaders; others are eager to be led this quarter. There's reserved optimism that team dynamics from earlier courses won't recur because we're finally in 'prime time.' Take a seat. The dynamics start fresh here."
}
},
{
id: "chair_back",
type: "cuboid",
position: [0, 2.3, 3.0],
dimensions: [1.2, 1.4, 0.12],
color: "#f48fb1",
tooltip: {
title: "Back Support 🛋️",
body: "Some are reluctant to cover for lagging teammates yet again, even if that strategy 'worked' before. Others want to avoid a parent/baby dynamic. The chair back is here for support—but you shouldn't need to carry the whole team's weight on your spine."
}
},
{
id: "chair_leg_fl",
type: "cuboid",
position: [-0.5, 0.75, 2.0],
dimensions: [0.1, 1.5, 0.1],
color: "#78909c",
tooltip: {
title: "Stability Point #1 🔩",
body: "Predicting task duration. One of your self-management goals. How long does a feature ACTUALLY take? Track it this quarter. Your estimates will improve."
}
},
{
id: "chair_leg_fr",
type: "cuboid",
position: [0.5, 0.75, 2.0],
dimensions: [0.1, 1.5, 0.1],
color: "#78909c",
tooltip: {
title: "Stability Point #2 🔩",
body: "Avoiding last-minute crunch. Another self-management goal. If you're crunching, something in planning failed. Work backward from the failure to find it."
}
},
{
id: "chair_leg_bl",
type: "cuboid",
position: [-0.5, 0.75, 3.0],
dimensions: [0.1, 1.5, 0.1],
color: "#78909c",
tooltip: {
title: "Stability Point #3 🔩",
body: "Balancing this course with other courses and responsibilities. You only have so many hours. CMPM 171 is important, but so is eating and sleeping. The chair needs all four legs."
}
},
{
id: "chair_leg_br",
type: "cuboid",
position: [0.5, 0.75, 3.0],
dimensions: [0.1, 1.5, 0.1],
color: "#78909c",
tooltip: {
title: "Stability Point #4 🔩",
body: "Conflict resolution. For a third of you, conflict avoidance is a major issue. Some want to avoid conflicts; others want strategies beyond avoidance. Resolution—not avoidance—is the professional skill. The fourth leg matters."
}
},
// === WHITEBOARD ===
{
id: "whiteboard_frame",
type: "cuboid",
position: [-7, 4, 0],
dimensions: [0.2, 3, 5],
color: "#a1887f",
tooltip: {
title: "The Boundary 🖼️",
body: "Scope has edges. The frame defines where the whiteboard STOPS. Your game needs the same. What's inside the frame ships. What's outside... maybe in CMPM 172. Maybe never. Both are okay."
}
},
{
id: "whiteboard_surface",
type: "cuboid",
position: [-6.85, 4, 0],
dimensions: [0.1, 2.6, 4.6],
color: "#fafafa",
tooltip: {
title: "The Blank Canvas ⬜",
body: "Erasable by design. Like prototypes. Sketch ideas here knowing they'll be wiped. Whiteboards don't create attachment the way 40 hours of code does. Use this surface to explore BEFORE committing to implementation. Argue with dry-erase markers, not merge conflicts."
}
},
// === SHELF UNIT ===
{
id: "shelf_frame_left",
type: "cuboid",
position: [7, 2.5, -2],
dimensions: [0.15, 5, 0.8],
color: "#ffe082",
tooltip: {
title: "Vertical Support (Left) 📚",
body: "Multiplayer and networking—you want it. Check out Wireshark. The shelf frame runs floor to ceiling; networking runs client to server. Both are structural."
}
},
{
id: "shelf_frame_right",
type: "cuboid",
position: [7, 2.5, 2],
dimensions: [0.15, 5, 0.8],
color: "#ffe082",
tooltip: {
title: "Vertical Support (Right) 📚",
body: "Shaders and graphics—you want these too. Check out Shadertoy. The GPU is a parallel universe; these frames hold up the stuff you put between them."
}
},
{
id: "shelf_1",
type: "cuboid",
position: [7, 1.5, 0],
dimensions: [0.6, 0.12, 4.2],
color: "#ffecb3",
tooltip: {
title: "Bottom Shelf: Foundations 📦",
body: "Version control and git. Read critiques of git for gamedev, but still use git. It's imperfect, it's ubiquitous, it's the bottom shelf everything else rests on. Learn it well enough to be annoyed by its limitations."
}
},
{
id: "shelf_2",
type: "cuboid",
position: [7, 3.0, 0],
dimensions: [0.6, 0.12, 4.2],
color: "#ffecb3",
tooltip: {
title: "Middle Shelf: Tooling 🧰",
body: "CI/CD and automation. Check out game.ci. Almost half of you want test automation to reduce friction. Cheat keys serve YOU, the developer—they're for jumping into specific moments during playtests. Pre-production infrastructure."
}
},
{
id: "shelf_3",
type: "cuboid",
position: [7, 4.5, 0],
dimensions: [0.6, 0.12, 4.2],
color: "#ffecb3",
tooltip: {
title: "Top Shelf: Aspirations 🏆",
body: "Performance optimization and profilers. You reach for this one. Get comfortable with your engine's profiler before you 'need' it. The top shelf is for things you access intentionally, not accidentally."
}
},
// === ITEMS ON SHELVES ===
{
id: "shelf_box_1",
type: "cuboid",
position: [7.1, 1.75, -1],
dimensions: [0.4, 0.4, 0.4],
color: "#ff8a65",
tooltip: {
title: "Mystery Box: Roguelike Seeds 🎲",
body: "Many of you only know procgen from roguelikes. But procedural methods go deeper: skeletal animation, texturing, weather systems, dialog variation, environmental audio mixing. The mystery box has more inside than you assumed."
}
},
{
id: "shelf_box_2",
type: "cuboid",
position: [7.1, 1.75, 0.5],
dimensions: [0.35, 0.5, 0.35],
color: "#ba68c8",
tooltip: {
title: "Jar of Spare Scopes 🫙",
body: "One source of scope creep is authentic passion and design curiosity. That's GOOD energy, but it needs containment. Prototyping is the jar. Pour your passion into a 90-minute prototype, label it, put it on the shelf. Maybe it ships. Maybe it doesn't. The jar keeps it safe either way."
}
},
{
id: "shelf_box_3",
type: "cuboid",
position: [7.1, 3.25, 1],
dimensions: [0.5, 0.35, 0.5],
color: "#4dd0e1",
tooltip: {
title: "Asset Pack (Handle With Care) 📂",
body: "You're wary of bland or mis-targeted assets from packs and stores. Good instinct. But today's chatbots are excellent advisors on getting visual bang for low code investment. Technical Artists bridge this gap professionally—and many of you are implicitly aligned to that path without knowing it exists."
}
},
// === BACKPACK ===
{
id: "backpack_body",
type: "cuboid",
position: [-3, 0.6, 3],
dimensions: [0.8, 1.2, 0.5],
color: "#7986cb",
tooltip: {
title: "What You Brought 🎒",
body: "You think of yourself as a 'game programmer,' curious but self-deprecating about art skills. 3D feels like a gap. But you can sometimes see visual and sonic art as something learnable alongside your technical skills. The backpack has more room than you think."
}
},
{
id: "backpack_pocket",
type: "cuboid",
position: [-3, 0.4, 3.3],
dimensions: [0.6, 0.7, 0.15],
color: "#5c6bc0",
tooltip: {
title: "The Front Pocket 🗃️",
body: "Almost no mention of generative AI in your survey responses. Even if you don't want to use it in your project, you should still be growing your understanding—develop a GROUNDED sense of what it can or can't be used for and how those categories are shifting THIS year. Tuck that knowledge in the front pocket."
}
},
// === TRASH BIN ===
{
id: "trash_bin",
type: "cuboid",
position: [3.5, 0.5, 2.5],
dimensions: [0.6, 1.0, 0.6],
color: "#a5d6a7",
tooltip: {
title: "The Honorable Bin 🗑️",
body: "Just one third of you have released a game outside of coursework. By the end of CMPM 171, more of you will know the feeling of shipping—AND the wisdom of discarding. This bin is not for failures. It's for prototypes that successfully answered their questions and earned retirement."
}
},
// === SECOND SMALLER TABLE (for prototyping) ===
{
id: "prototype_table_surface",
type: "cuboid",
position: [-4, 2.0, -3],
dimensions: [2.5, 0.15, 1.5],
color: "#ffe0b2",
tooltip: {
title: "The Prototyping Bench 🔬",
body: "Game jams were your most frequent release context, with GDA/IGC and personal projects nearly tied as alternatives. This smaller table is for quick work—timed exercises, jam-style builds, surgical design questions. It's not the main table. That's the point."
}
},
{
id: "prototype_table_leg_1",
type: "cuboid",
position: [-5.0, 1.0, -3.5],
dimensions: [0.15, 2.0, 0.15],
color: "#ffcc80",
tooltip: {
title: "Prototype Leg: Hypothesis 🦵",
body: "A focused prototype starts with a question. What will you learn from building this that you couldn't learn another way? If you can't articulate the question, you might be building a small game, not a prototype."
}
},
{
id: "prototype_table_leg_2",
type: "cuboid",
position: [-3.0, 1.0, -3.5],
dimensions: [0.15, 2.0, 0.15],
color: "#ffcc80",
tooltip: {
title: "Prototype Leg: Timebox 🦵",
body: "90 minutes. Maybe 3 hours for something meatier. But if you're on day two and still 'prototyping,' you've drifted into production. The timebox is a constraint that keeps the prototype honest."
}
},
{
id: "prototype_table_leg_3",
type: "cuboid",
position: [-5.0, 1.0, -2.5],
dimensions: [0.15, 2.0, 0.15],
color: "#ffcc80",
tooltip: {
title: "Prototype Leg: Minimalism 🦵",
body: "Rough is fine. Ugly is fine. Placeholder assets are fine. The prototype's job is to ANSWER, not to IMPRESS. Save the polish for the Vertical Slice at the end of the quarter."
}
},
{
id: "prototype_table_leg_4",
type: "cuboid",
position: [-3.0, 1.0, -2.5],
dimensions: [0.15, 2.0, 0.15],
color: "#ffcc80",
tooltip: {
title: "Prototype Leg: Disposability 🦵",
body: "If you can't throw it away, it's not a prototype. Attachment is natural, but the prototype's value is the KNOWLEDGE it creates, not the code. Extract the insight, archive the repo, move on."
}
},
// === ITCH.IO POSTER ON WALL ===
{
id: "poster_itchio",
type: "cuboid",
position: [-7, 4, -4],
dimensions: [0.08, 1.5, 1.0],
color: "#fa5c5c",
tooltip: {
title: "The Itch.io Poster 🎮",
body: "The vast majority of your independent releases targeted Itch.io. Good! It's low-friction, it's where players explore, and it doesn't have Steam's corrupting gravity. Practice releases go here. The Vertical Slice might graduate elsewhere—but start here."
}
},
// === WINDOW ===
{
id: "window_frame",
type: "cuboid",
position: [0, 5, -7],
dimensions: [4, 3, 0.2],
color: "#a1887f",
tooltip: {
title: "The Window to CMPM 172 🪟",
body: "Pre-production here. Full production next quarter. The window shows you what's coming, but you can't climb through it early. Resist the temptation to 'finish' your game in 171. The Vertical Slice is a PROOF, not a product."
}
},
{
id: "window_glass",
type: "cuboid",
position: [0, 5, -6.85],
dimensions: [3.5, 2.5, 0.05],
color: "#b3e5fc",
tooltip: {
title: "The View Ahead 🌅",
body: "We're excited to see what you'll build and learn. We're also excited to see what you'll wisely throw away when the time is right. The light comes through the glass both ways—CMPM 172 teams will benefit from the taste you develop here."
}
},
// === FLOATING EMOJI ===
{
id: "emoji_trash",
type: "emoji",
emoji: "🗑️",
position: [3.5, 2.2, 2.5],
float: { amplitude: 0.15, speed: 1.1, phase: 0 },
tooltip: {
title: "Proud Disposal 🗑️",
body: "Prototypes are disposable by design. Be proud to discard them when they've fulfilled their design knowledge gathering function. A polished game with no concrete design question behind it can undermine perception of your taste—an appropriately rough prototype with surgical precision boosts it. The Vertical Slice comes at the END of the quarter."
}
},
{
id: "emoji_rocket",
type: "emoji",
emoji: "🚀",
position: [-2, 4.5, -1],
float: { amplitude: 0.2, speed: 0.9, phase: 1.2 },
tooltip: {
title: "Release Early, Release Ugly 🚀",
body: "Get your proto-games in the hands of the public continuously. Even mid-level prototypes benefit from real playtesters. The 'final' deployment to Steam is a distraction—there's an important gap between reaching real players (do it NOW) and commercial platforms (worry about that LATER). Launch ugly. Learn fast."
}
},
{
id: "emoji_people",
type: "emoji",
emoji: "👥",
position: [1, 5, 1],
float: { amplitude: 0.18, speed: 1.0, phase: 2.5 },
tooltip: {
title: "Cross-Team Collaboration 👥",
body: "Please don't limit yourself to communication within your team—share ideas ACROSS teams. CMPM 171 is a rare window where you can peek into other teams' processes, help out, and learn. Your teammates are not your only collaborators. The whole cohort is a resource."
}
},
{
id: "emoji_clock",
type: "emoji",
emoji: "⏱️",
position: [-5, 3.5, 2],
float: { amplitude: 0.12, speed: 1.3, phase: 0.8 },
tooltip: {
title: "The 90-Minute Timer ⏱️",
body: "Try setting yourself a timer. Tell yourself you need a playable answer to a specific design question within the next 90 minutes. This is the prototyping mindset. If you're still working on it tomorrow, you've wandered out of prototyping into production. Reset the clock. Ask a new question."
}
},
{
id: "emoji_tools",
type: "emoji",
emoji: "🛠️",
position: [5, 3, -1],
float: { amplitude: 0.16, speed: 0.85, phase: 3.1 },
tooltip: {
title: "Tech Skills to Develop 🛠️",
body: "Your wishlist: multiplayer/networking, shaders/graphics, optimization/performance, version control/git, design patterns/architecture, CI/CD/automation, libraries/frameworks. Office hours and direct expert contact are where you'll go deeper—not lecture. Bring specific questions to specific people."
}
},
{
id: "emoji_lightbulb",
type: "emoji",
emoji: "💡",
position: [0, 5.5, -2],
float: { amplitude: 0.22, speed: 0.95, phase: 1.9 },
tooltip: {
title: "The Design Question 💡",
body: "Prototypes answer questions. What is the question yours answers? 'Is this fun?' is too vague. 'Does the wall-jump mechanic feel responsive at 45-degree angles?' is a question a prototype can answer in 90 minutes. Be specific. Get answers. Move on."
}
},
{
id: "emoji_art",
type: "emoji",
emoji: "🎨",
position: [-6.5, 5.8, 1],
float: { amplitude: 0.14, speed: 1.15, phase: 0.3 },
tooltip: {
title: "The Technical Artist Path 🎨",
body: "Almost nobody mentioned Technical Artist as a career path, but many of you are implicitly aligned to it. You see visual and sonic art as something learnable alongside technical skills. You want to bridge programmer and artist. There's a job title for that. Look into it."
}
},
{
id: "emoji_bug",
type: "emoji",
emoji: "🐛",
position: [2.5, 4, -3],
float: { amplitude: 0.19, speed: 1.05, phase: 2.2 },
tooltip: {
title: "Testing & Automation 🐛",
body: "Almost half of you are eager to learn test automation. Cheat keys serve a development purpose—facilitating playtesting by jumping into or past specific moments. CMPM 171 isn't about the final product; it's about building machinery that clarifies the final product. Cheat keys are machinery."
}
},
{
id: "emoji_handshake",
type: "emoji",
emoji: "🤝",
position: [-3, 4, 0],
float: { amplitude: 0.17, speed: 0.92, phase: 1.5 },
tooltip: {
title: "Conflict Resolution 🤝",
body: "For a third of you, conflict avoidance is a major concern. Some want successful avoidance; others want strategies BEYOND avoidance. Conflict RESOLUTION is the professional skill. Conflicts are inevitable. Your strategy for moving through them—not around them—is what makes teams work."
}
},
{
id: "emoji_gamepad",
type: "emoji",
emoji: "🎮",
position: [4, 5, 2],
float: { amplitude: 0.2, speed: 1.08, phase: 0.6 },
tooltip: {
title: "Engine Interests 🎮",
body: "Almost everyone wants Unity (90%). Godot (60%) and Phaser (35%) beat Unreal (15%). Interestingly, Love2D (18%) beat Unreal too. You have surface-level knowledge and want to go deeper. CMPM 171 team structures let you specialize. Pick your engine; own it fully."
}
},
{
id: "emoji_computer",
type: "emoji",
emoji: "💻",
position: [-4, 5.5, -2],
float: { amplitude: 0.15, speed: 0.88, phase: 2.8 },
tooltip: {
title: "Platform Diversity 💻",
body: "Browser platforms (desktop and mobile) were more common in your play habits than ANY console. Yet your releases cluster on Unity/PC. Consider: portfolio readers can play a browser game IMMEDIATELY. No download, no install, no friction. That's a competitive advantage hiding in plain sight."
}
},
{
id: "emoji_sparkles",
type: "emoji",
emoji: "✨",
position: [0, 6.5, 0],
float: { amplitude: 0.25, speed: 0.75, phase: 0 },
tooltip: {
title: "Pride in Past Work ✨",
body: "Physics and game feel. Performance optimization. Engine mastery. Audio perfection. Solo ownership. Thematic cohesion. Greater scope than presumed possible. Full-game completion. Documented growth. Community adoption. Leadership. Cross-discipline coordination. Working within constraints. These are the things we want EVERYONE to feel in CMPM 171."
}
},
{
id: "emoji_seedling",
type: "emoji",
emoji: "🌱",
position: [6, 4.2, 1],
float: { amplitude: 0.13, speed: 1.2, phase: 1.8 },
tooltip: {
title: "Growth & Confidence 🌱",
body: "You want confidence building. Try doing some writing—like a blog—to practice being an expert. Many of you want to work through the insecurity of putting work in front of others. Having specific QUESTIONS for playtests helps you own what feels rough. You're not exposing flaws; you're seeking answers."
}
},
{
id: "emoji_fire",
type: "emoji",
emoji: "🔥",
position: [-1, 4.8, 3],
float: { amplitude: 0.21, speed: 1.25, phase: 3.5 },
tooltip: {
title: "Passion vs. Scope Creep 🔥",
body: "One source of scope creep is authentic passion and design curiosity. That's GOOD energy—but it's also fire. Prototyping is how you safely and productively explore these feelings without raising conflicts on a larger team. Contain the fire. Channel the heat. Don't let it burn down the schedule."
}
},
{
id: "emoji_dizzy",
type: "emoji",
emoji: "😵‍💫",
position: [3, 6, -1],
float: { amplitude: 0.18, speed: 1.0, phase: 0.4 },
tooltip: {
title: "The Feedback Spiral 😵‍💫",
body: "Disentangling feedback that needs to be addressed from feedback that needs to be discarded is HARD. Don't polish a prototype that's about to be rightfully thrown away. Have questions BEFORE the playtest. Feedback outside those questions is noise—useful noise sometimes, but noise."
}
},
{
id: "emoji_crystal_ball",
type: "emoji",
emoji: "🔮",
position: [-5, 4.8, -3],
float: { amplitude: 0.2, speed: 0.9, phase: 2.0 },
tooltip: {
title: "The AI Question 🔮",
body: "Almost no mention of generative AI in your survey responses. Many speak against 'AI art' without qualification of the kind of AI or kind of use. About a third of you are in CMPM 147, where you'll see 3000+ years of computational art tradition. Chatbots can advise on visual bang for low code investment. Know these tools—even if you choose not to use them."
}
},
{
id: "emoji_trophy",
type: "emoji",
emoji: "🏆",
position: [6.5, 5.5, -1],
float: { amplitude: 0.16, speed: 0.82, phase: 1.1 },
tooltip: {
title: "The Vertical Slice 🏆",
body: "The high-polish deliverable comes at the END of the quarter—not the beginning, not the middle. Everything before it is pre-production: prototypes, playtests, scope decisions, team calibration. The Vertical Slice is proof that you COULD build the full game. It's not the game itself."
}
}
]
};
// ==================== THREE.JS SETUP ====================
const container = document.getElementById('canvas-container');
const tooltip = document.getElementById('tooltip');
const tooltipId = document.getElementById('tooltip-id');
const tooltipTitle = document.getElementById('tooltip-title');
const tooltipBody = document.getElementById('tooltip-body');
const scene = new THREE.Scene();
scene.background = new THREE.Color('#e8f5e9');
const camera = new THREE.PerspectiveCamera(
SCENE_DATA.camera.fov,
container.clientWidth / container.clientHeight,
0.1,
1000
);
camera.position.set(...SCENE_DATA.camera.position);
camera.lookAt(...SCENE_DATA.camera.lookAt);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
container.appendChild(renderer.domElement);
// Controls
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.minDistance = 5;
controls.maxDistance = 30;
controls.maxPolarAngle = Math.PI / 2.1;
// Lighting
const ambientLight = new THREE.AmbientLight(
SCENE_DATA.lighting.ambient.color,
SCENE_DATA.lighting.ambient.intensity
);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(
SCENE_DATA.lighting.directional.color,
SCENE_DATA.lighting.directional.intensity
);
directionalLight.position.set(...SCENE_DATA.lighting.directional.position);
scene.add(directionalLight);
const fillLight = new THREE.DirectionalLight(
SCENE_DATA.lighting.fill.color,
SCENE_DATA.lighting.fill.intensity
);
fillLight.position.set(...SCENE_DATA.lighting.fill.position);
scene.add(fillLight);
// ==================== OBJECT CREATION ====================
const interactiveObjects = [];
const floatingObjects = [];
function createCuboid(objData) {
const geometry = new THREE.BoxGeometry(...objData.dimensions);
const material = new THREE.MeshLambertMaterial({ color: objData.color });
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(...objData.position);
if (objData.rotation) {
mesh.rotation.set(...objData.rotation);
}
mesh.userData = {
id: objData.id,
tooltip: objData.tooltip,
originalColor: objData.color
};
return mesh;
}
function createEmojiSprite(objData) {
const canvas = document.createElement('canvas');
const size = 128;
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
ctx.font = `${size * 0.8}px serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(objData.emoji, size / 2, size / 2);
const texture = new THREE.CanvasTexture(canvas);
const material = new THREE.SpriteMaterial({ map: texture });
const sprite = new THREE.Sprite(material);
sprite.position.set(...objData.position);
sprite.scale.set(1.2, 1.2, 1);
sprite.userData = {
id: objData.id,
tooltip: objData.tooltip,
float: objData.float,
baseY: objData.position[1]
};
return sprite;
}
// Build scene from data
SCENE_DATA.objects.forEach(objData => {
let obj;
if (objData.type === 'cuboid') {
obj = createCuboid(objData);
} else if (objData.type === 'emoji') {
obj = createEmojiSprite(objData);
floatingObjects.push(obj);
}
if (obj) {
scene.add(obj);
interactiveObjects.push(obj);
}
});
// ==================== RAYCASTING & TOOLTIPS ====================
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
let hoveredObject = null;
function onMouseMove(event) {
const rect = renderer.domElement.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(interactiveObjects);
if (intersects.length > 0) {
const obj = intersects[0].object;
if (hoveredObject !== obj) {
// Reset previous
if (hoveredObject && hoveredObject.material && hoveredObject.material.color) {
hoveredObject.material.color.set(hoveredObject.userData.originalColor);
}
hoveredObject = obj;
// Highlight new
if (obj.material && obj.material.color) {
obj.material.color.set('#ffffff');
}
}
// Update tooltip
showTooltip(obj.userData, event.clientX, event.clientY);
} else {
if (hoveredObject) {
if (hoveredObject.material && hoveredObject.material.color) {
hoveredObject.material.color.set(hoveredObject.userData.originalColor);
}
hoveredObject = null;
}
hideTooltip();
}
}
function onTap(event) {
const rect = renderer.domElement.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(interactiveObjects);
if (intersects.length > 0) {
const obj = intersects[0].object;
if (hoveredObject === obj) {
// Tap same object = dismiss
clearHover();
} else {
// Tap new object = switch
clearHover();
hoveredObject = obj;
if (obj.material && obj.material.color) {
obj.material.color.set('#ffffff');
}
showTooltipCentered(obj.userData);
}
} else {
// Tap empty space = dismiss
clearHover();
}
}
function showTooltipCentered(data) {
tooltipId.textContent = data.id;
tooltipTitle.textContent = data.tooltip.title;
tooltipBody.textContent = data.tooltip.body;
tooltip.classList.add('visible');
// Center horizontally, position in upper third
tooltip.style.left = '50%';
tooltip.style.top = '20%';
tooltip.style.transform = 'translateX(-50%)';
}
function clearHover() {
if (hoveredObject) {
if (hoveredObject.material && hoveredObject.material.color) {
hoveredObject.material.color.set(hoveredObject.userData.originalColor);
}
hoveredObject = null;
}
hideTooltip();
}
function showTooltip(data, x, y) {
tooltipId.textContent = data.id;
tooltipTitle.textContent = data.tooltip.title;
tooltipBody.textContent = data.tooltip.body;
tooltip.classList.add('visible');
// Position tooltip
const padding = 20;
let left = x + padding;
let top = y + padding;
// Keep on screen
const rect = tooltip.getBoundingClientRect();
if (left + rect.width > window.innerWidth) {
left = x - rect.width - padding;
}
if (top + rect.height > window.innerHeight) {
top = y - rect.height - padding;
}
tooltip.style.left = left + 'px';
tooltip.style.top = top + 'px';
tooltip.style.transform = 'none';
}
function hideTooltip() {
tooltip.classList.remove('visible');
}
renderer.domElement.addEventListener('mousemove', onMouseMove);
let pointerStart = { x: 0, y: 0 };
renderer.domElement.addEventListener('pointerdown', (e) => {
pointerStart.x = e.clientX;
pointerStart.y = e.clientY;
});
renderer.domElement.addEventListener('pointerup', (e) => {
const dist = Math.hypot(e.clientX - pointerStart.x, e.clientY - pointerStart.y);
if (dist < 10) {
onTap(e);
}
});
// ==================== FULLSCREEN ====================
const fullscreenBtn = document.getElementById('fullscreen-btn');
fullscreenBtn.addEventListener('click', () => {
if (!document.fullscreenElement) {
document.getElementById('container').requestFullscreen();
fullscreenBtn.textContent = '⛶ Exit Fullscreen';
} else {
document.exitFullscreen();
fullscreenBtn.textContent = '⛶ Fullscreen';
}
});
document.addEventListener('fullscreenchange', () => {
if (!document.fullscreenElement) {
fullscreenBtn.textContent = '⛶ Fullscreen';
}
});
// ==================== RESIZE HANDLING ====================
function onResize() {
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
}
window.addEventListener('resize', onResize);
// ==================== ANIMATION LOOP ====================
function animate(time) {
requestAnimationFrame(animate);
const t = time * 0.001;
// Animate floating emojis
floatingObjects.forEach(obj => {
const f = obj.userData.float;
const yOffset = Math.sin(t * f.speed + f.phase) * f.amplitude;
obj.position.y = obj.userData.baseY + yOffset;
// Subtle rotation wobble
const wobble = Math.sin(t * f.speed * 0.5 + f.phase) * 0.1;
obj.material.rotation = wobble;
});
// Hover scale pulse for hovered object
if (hoveredObject && hoveredObject.userData.float) {
const pulse = 1 + Math.sin(t * 5) * 0.05;
hoveredObject.scale.set(1.2 * pulse, 1.2 * pulse, 1);
}
controls.update();
renderer.render(scene, camera);
}
animate(0);
document.getElementById('instructions').innerHTML = isTouch
? '👆 Tap to inspect • Drag to orbit • Pinch to zoom'
: '🖱️ Hover to inspect • Drag to orbit • Scroll to zoom';
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment