기술교육의 미래를 그립니다.
오늘은 코드를 한 줄도 쓰지 않고 게임을 만듭니다
생성형 AI에게 원하는 것을 말로 설명하고, 결과를 확인하고, 다시 말로 다듬어 웹에서 돌아가는 게임을 완성합니다. 이 과정을 겪은 뒤, 그것이 왜 "바이브코딩"이라 불리는지 정리합니다.
| 구간 | 하는 일 |
|---|---|
| 실습 1. 테트리스 만들기 | 제미나이에게 코드를 받아 파일로 저장해 실행하고, 말로 기능을 덧붙입니다. |
| 바이브코딩 이해하기 | 방금 한 과정의 이름과 원리, 달라지는 사람의 역할과 한계를 정리합니다. |
| 실습 2. 제미나이 캔버스 | 파일 저장 없이 대화창 옆에서 바로 실행하는 방법을 익힙니다. |
| 실습 3. 나만의 게임 | 요청문을 직접 설계해 나만의 게임을 만들고 다듬습니다. |
준비물
| 구글 계정 | 제미나이 로그인에 필요합니다. |
| 크롬 브라우저 | 제미나이와 만든 게임을 실행합니다. |
| 메모장 | 받은 코드를 HTML 파일로 저장할 때 씁니다. (맥은 텍스트 편집기) |
확인
제미나이에 들어가 로그인합니다
- 아래 버튼을 눌러 제미나이를 새 탭으로 엽니다.
- 오른쪽 위 로그인을 눌러 구글 계정으로 로그인합니다.
확인
테트리스 코드를 요청합니다
아래 문장을 복사해 제미나이 입력창에 붙여 넣고 보냅니다.
내가 지금 웹페이지에서 작동할 수 있는 테트리스 게임을 만들어보려고해 HTML 형식으로 테트리스 게임 코드를 짜줘
잠시 뒤 긴 코드와 함께 설명이 나옵니다. 코드를 이해하려고 애쓰지 않아도 됩니다. 다음 단계에서 이 코드를 파일로 저장합니다.
HTML(HyperText Markup Language)은 웹페이지를 만드는 기본 언어입니다. 우리가 인터넷에서 보는 거의 모든 페이지가 HTML로 되어 있고, 크롬 같은 브라우저는 HTML 파일을 읽어 화면에 보여 줍니다.
"HTML 형식으로 짜줘"라고 하면 이 세 가지를 파일 하나에 모두 담아 줍니다. 그래서 파일 하나만 있으면 별도 프로그램 없이 브라우저에서 바로 실행할 수 있습니다.
확인
받은 코드를 HTML 파일로 저장합니다
- 제미나이 답변의 코드 상자 오른쪽 위 복사 아이콘을 눌러 코드 전체를 복사합니다.
- 메모장을 열고 붙여 넣습니다.
- 파일 → 다른 이름으로 저장을 누르고, 아래 세 가지를 맞춰 저장합니다.
tetris.html확장자 .html까지 직접 입력합니다.모든 파일 (*.*)텍스트 문서로 두면 tetris.html.txt가 되어 실행되지 않습니다.UTF-8다른 인코딩이면 한글이 깨집니다.왜 이렇게 저장해야 할까요?
코드는 글자만 있는 순수한 텍스트여야 합니다. 한글이나 워드는 글꼴, 줄 간격 같은 서식 정보를 파일 안에 함께 저장해서 코드가 망가집니다. 메모장은 입력한 글자만 그대로 저장하므로 코드를 담기에 알맞습니다.
컴퓨터는 파일 이름 끝의 확장자를 보고 어떤 프로그램으로 열지 정합니다. .txt는 메모장으로, .html은 브라우저로 엽니다. 그래서 확장자를 .html로 붙여야 더블클릭했을 때 게임이 실행됩니다.
메모장은 기본적으로 저장하는 파일 끝에 .txt를 자동으로 붙입니다. 이름을 tetris.html로 써도 실제로는 tetris.html.txt가 됩니다. "모든 파일"을 고르면 우리가 쓴 이름 그대로 저장됩니다.
인코딩은 글자를 컴퓨터가 저장하는 숫자로 바꾸는 약속입니다. UTF-8은 한글을 포함한 전 세계 문자를 표현하는 웹의 표준 약속입니다. 코드 안에도 "UTF-8로 읽으라"는 문구(charset="UTF-8")가 들어 있어서, 같은 약속으로 저장해야 한글이 깨지지 않습니다.
맥에서는 이렇게 저장하세요
tetris.html로 저장하고, ".txt를 붙일까요?"라고 물으면 .html 사용을 누릅니다.파일 아이콘이 메모장 모양이에요
tetris.html.txt가 된 경우입니다. 다시 저장하면서 파일 형식을 모든 파일 (*.*)로 바꿔 주세요.확인
파일을 열어 게임을 실행합니다
저장한 tetris.html을 더블클릭하면 브라우저에서 게임이 열립니다.
AI가 만든 코드마다 조작키가 조금씩 다를 수 있습니다. 게임 화면이나 제미나이 답변의 설명을 확인하세요.
예제 코드 1: 심플 테트리스
직접 진행하기 어려울 때연수 환경에 따라 로그인이 어렵거나, 코드를 받는 데 시간이 오래 걸릴 수 있습니다. 그럴 때는 직접 받은 코드 대신 아래 예제 코드를 사용하세요. ▶ 바로 플레이를 누르면 저장하지 않고 이 화면에서 바로 게임을 해 볼 수 있습니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>심플 테트리스</title>
<style>
body {
background: #202028;
color: #fff;
font-family: sans-serif;
font-size: 2em;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
}
#game-container {
position: relative;
border: 2px solid #fff;
}
canvas {
display: block;
background-color: #000;
}
.score {
margin-bottom: 10px;
}
</style>
</head>
<body>
<div class="score" id="score">0</div>
<div id="game-container">
<canvas id="tetris" width="240" height="400"></canvas>
</div>
<script>
const canvas = document.getElementById('tetris');
const context = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
context.scale(20, 20);
// 라인 삭제 로직
function arenaSweep() {
let rowCount = 1;
outer: for (let y = arena.length - 1; y > 0; --y) {
for (let x = 0; x < arena[y].length; ++x) {
if (arena[y][x] === 0) {
continue outer;
}
}
const row = arena.splice(y, 1)[0].fill(0);
arena.unshift(row);
++y;
player.score += rowCount * 10;
rowCount *= 2;
}
updateScore();
}
// 충돌 감지
function collide(arena, player) {
const [m, o] = [player.matrix, player.pos];
for (let y = 0; y < m.length; ++y) {
for (let x = 0; x < m[y].length; ++x) {
if (m[y][x] !== 0 &&
(arena[y + o.y] && arena[y + o.y][x + o.x]) !== 0) {
return true;
}
}
}
return false;
}
// 보드 생성
function createMatrix(w, h) {
const matrix = [];
while (h--) {
matrix.push(new Array(w).fill(0));
}
return matrix;
}
// 블록 모양 정의
function createPiece(type) {
if (type === 'I') {
return [
[0, 1, 0, 0],
[0, 1, 0, 0],
[0, 1, 0, 0],
[0, 1, 0, 0],
];
} else if (type === 'L') {
return [
[0, 2, 0],
[0, 2, 0],
[0, 2, 2],
];
} else if (type === 'J') {
return [
[0, 3, 0],
[0, 3, 0],
[3, 3, 0],
];
} else if (type === 'O') {
return [
[4, 4],
[4, 4],
];
} else if (type === 'Z') {
return [
[5, 5, 0],
[0, 5, 5],
[0, 0, 0],
];
} else if (type === 'S') {
return [
[0, 6, 6],
[6, 6, 0],
[0, 0, 0],
];
} else if (type === 'T') {
return [
[0, 7, 0],
[7, 7, 7],
[0, 0, 0],
];
}
}
function draw() {
context.fillStyle = '#000';
context.fillRect(0, 0, canvas.width, canvas.height);
drawMatrix(arena, {x: 0, y: 0});
drawMatrix(player.matrix, player.pos);
}
function drawMatrix(matrix, offset) {
matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value !== 0) {
context.fillStyle = colors[value];
context.fillRect(x + offset.x, y + offset.y, 1, 1);
}
});
});
}
// 데이터 합치기
function merge(arena, player) {
player.matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value !== 0) {
arena[y + player.pos.y][x + player.pos.x] = value;
}
});
});
}
// 회전 로직
function rotate(matrix, dir) {
for (let y = 0; y < matrix.length; ++y) {
for (let x = 0; x < y; ++x) {
[matrix[x][y], matrix[y][x]] = [matrix[y][x], matrix[x][y]];
}
}
if (dir > 0) {
matrix.forEach(row => row.reverse());
} else {
matrix.reverse();
}
}
function playerDrop() {
player.pos.y++;
if (collide(arena, player)) {
player.pos.y--;
merge(arena, player);
playerReset();
arenaSweep();
updateScore();
}
dropCounter = 0;
}
function playerMove(offset) {
player.pos.x += offset;
if (collide(arena, player)) {
player.pos.x -= offset;
}
}
function playerReset() {
const pieces = 'TJLOSZI';
player.matrix = createPiece(pieces[pieces.length * Math.random() | 0]);
player.pos.y = 0;
player.pos.x = (arena[0].length / 2 | 0) - (player.matrix[0].length / 2 | 0);
if (collide(arena, player)) {
arena.forEach(row => row.fill(0));
player.score = 0;
updateScore();
}
}
function playerRotate(dir) {
const pos = player.pos.x;
let offset = 1;
rotate(player.matrix, dir);
while (collide(arena, player)) {
player.pos.x += offset;
offset = -(offset + (offset > 0 ? 1 : -1));
if (offset > player.matrix[0].length) {
rotate(player.matrix, -dir);
player.pos.x = pos;
return;
}
}
}
let dropCounter = 0;
let dropInterval = 1000;
let lastTime = 0;
function update(time = 0) {
const deltaTime = time - lastTime;
lastTime = time;
dropCounter += deltaTime;
if (dropCounter > dropInterval) {
playerDrop();
}
draw();
requestAnimationFrame(update);
}
function updateScore() {
scoreElement.innerText = player.score;
}
const colors = [
null,
'#FF0D72',
'#0DC2FF',
'#0DFF72',
'#F538FF',
'#FF8E0D',
'#FFE138',
'#3877FF',
];
const arena = createMatrix(12, 20);
const player = {
pos: {x: 0, y: 0},
matrix: null,
score: 0,
};
// 조작키 설정
document.addEventListener('keydown', event => {
if (event.keyCode === 37) { // 왼쪽 화살표
playerMove(-1);
} else if (event.keyCode === 39) { // 오른쪽 화살표
playerMove(1);
} else if (event.keyCode === 40) { // 아래 화살표
playerDrop();
} else if (event.keyCode === 81) { // Q (왼쪽 회전)
playerRotate(-1);
} else if (event.keyCode === 87) { // W (오른쪽 회전)
playerRotate(1);
} else if (event.keyCode === 38) { // 위쪽 화살표 (오른쪽 회전)
playerRotate(1);
}
});
playerReset();
updateScore();
update();
</script>
</body>
</html>확인
원하는 기능을 말로 덧붙입니다
게임을 해 보면 아쉬운 점이 보입니다. 다음 블록이 무엇인지 알 수 없고, 휴대폰에는 키보드가 없어 조작할 수 없습니다. 코드를 직접 고치는 대신, 같은 대화창에서 말로 요청합니다.
이제 다음에 나올 블럭이 무엇인지 알려주는 미리보기 기능을 추가했으면 좋겠어. 또한 모바일로도 할 수 있게, 방향키를 시각화해줘.
확인
새 코드로 다시 확인합니다
- 새로 받은 코드를 4단계와 같은 방법으로 저장합니다. 이름을
tetris2.html로 하면 두 버전을 비교할 수 있습니다. - 파일을 열어 오른쪽의 다음 블록 미리보기(NEXT)와 화면 아래 방향 버튼이 생겼는지 확인합니다.
예제 코드 2: 미리보기와 모바일 버튼이 추가된 테트리스
직접 진행하기 어려울 때▶ 바로 플레이를 누르면 저장하지 않고 이 화면에서 바로 새 버전을 해 볼 수 있습니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>테트리스 플러스</title>
<style>
body {
background: #202028;
color: #fff;
font-family: 'Arial', sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
overflow: hidden;
touch-action: manipulation;
}
.game-layout {
display: flex;
gap: 20px;
align-items: flex-start;
}
#game-container {
border: 4px solid #fff;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
}
.side-panel {
display: flex;
flex-direction: column;
gap: 20px;
}
.info-box {
background: #333;
padding: 10px;
border: 2px solid #555;
text-align: center;
min-width: 80px;
}
.info-label { font-size: 0.5em; color: #aaa; margin-bottom: 5px; }
.info-value { font-size: 1em; font-weight: bold; }
canvas { background-color: #000; display: block; }
#nextCanvas { background-color: #111; }
/* 모바일 컨트롤러 스타일 */
.controls {
margin-top: 20px;
display: grid;
grid-template-areas:
". up ."
"left down right";
gap: 10px;
}
.btn {
width: 60px;
height: 60px;
background: #444;
border: none;
border-radius: 10px;
color: white;
font-size: 24px;
display: flex;
align-items: center;
justify-content: center;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.btn:active { background: #666; transform: scale(0.95); }
.btn-up { grid-area: up; background: #555; }
.btn-left { grid-area: left; }
.btn-down { grid-area: down; }
.btn-right { grid-area: right; }
@media (max-width: 400px) {
.game-layout { transform: scale(0.9); }
}
</style>
</head>
<body>
<div class="game-layout">
<div id="game-container">
<canvas id="tetris" width="200" height="400"></canvas>
</div>
<div class="side-panel">
<div class="info-box">
<div class="info-label">NEXT</div>
<canvas id="nextCanvas" width="80" height="80"></canvas>
</div>
<div class="info-box">
<div class="info-label">SCORE</div>
<div id="score" class="info-value">0</div>
</div>
</div>
</div>
<div class="controls">
<div class="btn btn-up" onclick="playerRotate(1)">↻</div>
<div class="btn btn-left" onclick="playerMove(-1)">←</div>
<div class="btn btn-down" onclick="playerDrop()">↓</div>
<div class="btn btn-right" onclick="playerMove(1)">→</div>
</div>
<script>
const canvas = document.getElementById('tetris');
const context = canvas.getContext('2d');
const nextCanvas = document.getElementById('nextCanvas');
const nextContext = nextCanvas.getContext('2d');
const scoreElement = document.getElementById('score');
context.scale(20, 20);
nextContext.scale(20, 20);
const colors = [null, '#FF0D72', '#0DC2FF', '#0DFF72', '#F538FF', '#FF8E0D', '#FFE138', '#3877FF'];
function createPiece(type) {
if (type === 'I') return [[0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0]];
else if (type === 'L') return [[0, 2, 0], [0, 2, 0], [0, 2, 2]];
else if (type === 'J') return [[0, 3, 0], [0, 3, 0], [3, 3, 0]];
else if (type === 'O') return [[4, 4], [4, 4]];
else if (type === 'Z') return [[5, 5, 0], [0, 5, 5], [0, 0, 0]];
else if (type === 'S') return [[0, 6, 6], [6, 6, 0], [0, 0, 0]];
else if (type === 'T') return [[0, 7, 0], [7, 7, 7], [0, 0, 0]];
}
function drawMatrix(matrix, offset, ctx = context) {
matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value !== 0) {
ctx.fillStyle = colors[value];
ctx.fillRect(x + offset.x, y + offset.y, 1, 1);
}
});
});
}
function drawNext() {
nextContext.fillStyle = '#111';
nextContext.fillRect(0, 0, nextCanvas.width, nextCanvas.height);
// 중앙 정렬 계산
const xOff = (4 - player.next.length) / 2;
const yOff = (4 - player.next.length) / 2;
drawMatrix(player.next, {x: xOff, y: yOff}, nextContext);
}
function arenaSweep() {
let rowCount = 1;
outer: for (let y = arena.length - 1; y > 0; --y) {
for (let x = 0; x < arena[y].length; ++x) {
if (arena[y][x] === 0) continue outer;
}
const row = arena.splice(y, 1)[0].fill(0);
arena.unshift(row);
++y;
player.score += rowCount * 10;
rowCount *= 2;
}
updateScore();
}
function collide(arena, player) {
const [m, o] = [player.matrix, player.pos];
for (let y = 0; y < m.length; ++y) {
for (let x = 0; x < m[y].length; ++x) {
if (m[y][x] !== 0 && (arena[y + o.y] && arena[y + o.y][x + o.x]) !== 0) return true;
}
}
return false;
}
function createMatrix(w, h) {
const matrix = [];
while (h--) matrix.push(new Array(w).fill(0));
return matrix;
}
function merge(arena, player) {
player.matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value !== 0) arena[y + player.pos.y][x + player.pos.x] = value;
});
});
}
function rotate(matrix, dir) {
for (let y = 0; y < matrix.length; ++y) {
for (let x = 0; x < y; ++x) [matrix[x][y], matrix[y][x]] = [matrix[y][x], matrix[x][y]];
}
if (dir > 0) matrix.forEach(row => row.reverse());
else matrix.reverse();
}
function playerDrop() {
player.pos.y++;
if (collide(arena, player)) {
player.pos.y--;
merge(arena, player);
playerReset();
arenaSweep();
}
dropCounter = 0;
}
function playerMove(offset) {
player.pos.x += offset;
if (collide(arena, player)) player.pos.x -= offset;
}
function playerReset() {
const pieces = 'TJLOSZI';
if (player.next === null) {
player.next = createPiece(pieces[pieces.length * Math.random() | 0]);
}
player.matrix = player.next;
player.next = createPiece(pieces[pieces.length * Math.random() | 0]);
player.pos.y = 0;
player.pos.x = (arena[0].length / 2 | 0) - (player.matrix[0].length / 2 | 0);
if (collide(arena, player)) {
arena.forEach(row => row.fill(0));
player.score = 0;
updateScore();
}
drawNext();
}
function playerRotate(dir) {
const pos = player.pos.x;
let offset = 1;
rotate(player.matrix, dir);
while (collide(arena, player)) {
player.pos.x += offset;
offset = -(offset + (offset > 0 ? 1 : -1));
if (offset > player.matrix[0].length) {
rotate(player.matrix, -dir);
player.pos.x = pos;
return;
}
}
}
function updateScore() {
scoreElement.innerText = player.score;
}
let dropCounter = 0;
let dropInterval = 1000;
let lastTime = 0;
function update(time = 0) {
const deltaTime = time - lastTime;
lastTime = time;
dropCounter += deltaTime;
if (dropCounter > dropInterval) playerDrop();
context.fillStyle = '#000';
context.fillRect(0, 0, canvas.width, canvas.height);
drawMatrix(arena, {x: 0, y: 0});
drawMatrix(player.matrix, player.pos);
requestAnimationFrame(update);
}
const arena = createMatrix(10, 20);
const player = {
pos: {x: 0, y: 0},
matrix: null,
next: null,
score: 0,
};
document.addEventListener('keydown', event => {
if (event.keyCode === 37) playerMove(-1);
else if (event.keyCode === 39) playerMove(1);
else if (event.keyCode === 40) playerDrop();
else if (event.keyCode === 38 || event.keyCode === 87) playerRotate(1);
});
playerReset();
update();
</script>
</body>
</html>확인
방금 한 것이 바로 바이브코딩입니다
핵심은 "어떻게 만들 것인가(How)"를 AI에게 맡기고, 사람은 "무엇을 만들 것인가(What)"와 "제대로 되었는가(Check)"에 집중한다는 점입니다.
"코드가 존재한다는 사실조차 잊고, 흐름(vibe)에 완전히 몸을 맡기는 새로운 방식의 코딩"
프로그래밍 문법을 몰라도, 원하는 결과를 분명하게 설명하고 판단할 수 있으면 소프트웨어를 만들 수 있습니다. 실습 1에서 여러분이 한 일이 정확히 이것입니다.
확인
짧은 게시글 하나가 1년 만에 올해의 단어가 되었습니다
시점을 눌러 보세요.
확인
바이브코딩은 네 단계를 반복하는 대화 루프입니다
단계를 누르면 설명과 실습 1의 예가 나옵니다. 루프 재생하기를 누르면 차례로 돌아갑니다.
확인
대화 루프가 모여 하나의 앱이 됩니다
대화 루프는 기능 하나를 다듬는 작은 반복입니다. 이 반복이 모여, 아이디어가 실제로 쓰이는 앱이 되기까지는 다섯 단계를 거칩니다. 오늘 연수는 파란 테두리의 세 단계까지 경험합니다.
만들고 싶은 앱 전체를 높은 수준의 요청 하나로 설명합니다.
사람AI가 화면, 동작 로직, 파일 구조를 포함한 첫 버전을 만듭니다.
AI직접 써 보며 후속 요청으로 기능을 더하거나 바꿉니다.
사람 AI보안, 품질, 정확성을 사람이 검토합니다. 학생에게 쓰기 전 교사가 반드시 거칠 단계입니다.
사람다른 사람이 쓸 수 있도록 인터넷에 올리고 링크를 공유합니다. 방법은 다음 단계에서 자세히 다룹니다.
사람 AI확인
만든 파일을 인터넷에 올려 링크로 나눕니다
내 컴퓨터에 저장한 HTML 파일은 나만 열 수 있습니다. 배포는 이 파일을 인터넷에 올려 누구나 접속할 수 있는 주소를 만드는 일입니다. Netlify를 쓰면 가입만 해 두고 파일을 끌어다 놓는 것만으로 몇 초 만에 배포할 수 있습니다.
1. Netlify에 가입하고 Drop 화면 열기
- 아래 버튼으로 Netlify Drop 페이지를 엽니다.
- 오른쪽 위에서 Sign up(가입)을 누르고, 구글 계정 등으로 가입해 로그인합니다.
2. HTML 파일을 끌어다 놓기
로그인하면 아래와 같은 화면이 나옵니다. 저장해 둔 tetris.html 파일을 가운데 영역에 끌어다 놓으면 끝입니다. file을 눌러 파일을 직접 골라도 됩니다.
publish instantly
3. 생긴 주소 확인하기
몇 초 뒤 프로젝트 화면으로 바뀝니다. 초록색으로 표시된 ○○○.netlify.app 주소를 누르면 게임이 열립니다. 이 주소를 복사해 누구에게나 보낼 수 있습니다.
| 번호 | 무엇인가요 |
|---|---|
| 2 | 배포된 주소입니다. 처음에는 무작위 이름이 붙습니다. 누르면 새 탭에서 결과물이 열립니다. |
| 3 | Project configuration에서 프로젝트 이름을 바꾸면 주소가 내가정한이름.netlify.app으로 바뀝니다. 영어 소문자, 숫자, 하이픈(-)만 쓸 수 있습니다. |
| 4 | Deploys는 고친 파일을 다시 올리는 곳입니다. 여기에 새 파일을 끌어다 놓으면 주소는 그대로, 내용만 바뀝니다. |
확인
전통적 프로그래밍과 무엇이 다를까요
| 구분 | 전통적 프로그래밍 | 바이브코딩 |
|---|---|---|
| 입력 방식 | 프로그래밍 언어로 한 줄씩 작성 | 자연어로 의도와 요구사항을 설명 |
| 필요한 역량 | 언어 문법, 라이브러리, 개발 도구 지식 | 문제를 정의하고 설명하는 힘, 결과를 판단하는 힘 |
| 반복 속도 | 시간에서 며칠 단위 | 분에서 시간 단위 |
| 코드 이해 수준 | 한 줄 한 줄 깊이 이해 | 결과 중심, 코드는 얕게 이해하거나 읽지 않음 |
| 잘 맞는 일 | 실제 서비스, 복잡한 로직, 장기 유지보수 | 시제품, 수업용 도구, 개인 프로젝트, 아이디어 검증 |
| 사람의 역할 | 코드를 직접 작성하는 사람 | 요구사항을 설계하고 결과를 검증하는 사람 |
같은 AI 코딩이라도 태도에 따라 나뉩니다
위치를 눌러 비교해 보세요.
확인
사람의 역할은 사라지지 않고 바뀝니다
문법에 맞게 한 줄씩 코드를 쓰고, 오류를 직접 찾아 고칩니다.
무엇을 만들지 분명히 정의하고, 결과가 의도대로인지 판단해 방향을 잡습니다.
꼭 알아야 할 한계
확인
저장하지 않고 바로 실행할 수 있습니다
실습 1에서는 코드를 복사하고, 메모장에 붙여 넣고, 파일로 저장한 뒤 열어야 했습니다. 제미나이의 캔버스를 켜면 코드와 실행 화면이 대화창 옆에 함께 열려, 이 과정이 모두 사라집니다.
한 번 수정할 때 거치는 과정
파일 저장 방식 6단계
- 1대화창에 요청하기
- 2코드 복사하기
- 3메모장에 붙여 넣기
- 4HTML 파일로 저장하기
- 5파일 열어 실행하기
- 6대화창으로 돌아가 다시 요청하기
캔버스 방식 2단계
- 1대화창에 요청하기
- 2옆 화면에서 바로 실행하고 다시 요청하기
확인
캔버스를 켜고 바로 실행해 봅니다
캔버스는 여기에 있습니다
입력창 왼쪽의 + 버튼 → 도구 더보기 → Canvas 순서로 누릅니다.
제미나이 화면은 업데이트에 따라 메뉴 이름과 위치가 조금씩 바뀔 수 있습니다. 입력창 주변의 도구 관련 메뉴에서 Canvas를 찾으세요.
캔버스로 만들기
- 캔버스 켜기: 위 순서로 Canvas를 누르면 입력창에 캔버스가 켜졌다는 표시가 생깁니다.
- 요청하기: 아래 요청문을 보내면 화면 오른쪽에 캔버스가 열리고 코드가 작성됩니다.
- 미리보기로 실행하기: 캔버스 위쪽의 미리보기(Preview)를 누르면 그 자리에서 실행됩니다.
- 대화로 고치기: 고칠 점을 채팅창에 말하면 캔버스의 내용이 바로 수정됩니다.
- 공유하기: 완성되면 공유 기능으로 링크를 만들어 다른 사람에게 보여 줄 수 있습니다.
바로 실행할 수 있는 테트리스 게임을 만들어줘!
확인
나만의 게임 요청문을 설계합니다
왼쪽에서 원하는 게임을 고르면 오른쪽에 요청문이 만들어집니다. 좋은 요청문은 무엇을, 어떻게 조작하고, 어떤 규칙과 기능이 있는지를 담고 있습니다.
복사한 요청문을 캔버스를 켠 제미나이에 붙여 넣으세요. 그대로 써도 되고, 원하는 규칙을 더 적어도 좋습니다.
확인
실행하고, 말로 다듬어 완성합니다
첫 결과가 나오면 미리보기로 실행해 보고, 대화 루프를 두 번 이상 돌려 게임을 다듬어 보세요.
이럴 땐 이렇게 말해 보세요
| 이런 상황이면 | 이렇게 요청 |
|---|---|
| 게임이 아예 안 움직여요 | "게임이 시작되지 않아. 화면에 아무 변화가 없어. 고쳐줘." |
| 이상하게 움직여요 | "캐릭터가 벽을 통과해. 벽에 닿으면 멈추게 고쳐줘."처럼 본 현상을 그대로 설명 |
| 너무 어렵거나 쉬워요 | "처음 속도를 절반으로 줄이고, 30초마다 조금씩 빨라지게 해줘." |
| 오류 메시지가 보여요 | 메시지를 그대로 복사해 붙여 넣고 "이 오류를 고쳐줘." |
확인
교사에게 바이브코딩은 "필요한 도구를 직접 만드는 힘"입니다
딱 맞는 수업 도구를 찾지 못해 PPT와 활동지로 대신하던 일들을, 이제 말로 설명해 직접 만들 수 있습니다. 교육 현장에서는 크게 세 영역에서 활용할 수 있습니다.
교과서의 그림을 학생이 직접 조작하는 시뮬레이션으로 바꿉니다.
- 개념 시뮬레이션 (궤도, 그래프, 회로)
- 인터랙티브 학습지와 퀴즈
- 게임형 복습 활동
학생이 문제를 정의하고, 요구사항을 설명하고, 결과를 평가하며 산출물을 만듭니다.
- 문제해결 프로젝트의 시제품 만들기
- 요구사항을 글로 명세하는 연습
- 만든 결과를 서로 사용해 보고 평가하기
매번 규정을 찾아보고 계산하던 일을 한 번에 처리하는 도구를 만듭니다.
- 규정 기반 계산기와 실무 가이드
- 문서 양식 자동 작성
- 체크리스트와 안내 페이지
만든 것을 나누는 세 가지 방법
| 방법 | 특징 |
|---|---|
| 제미나이 공유 링크 | 캔버스에서 만든 결과를 공유 버튼으로 바로 링크로 만듭니다. 가장 간단합니다. |
| Claude 아티팩트 | Claude에서 만든 결과물을 공개 링크로 공유합니다. |
| Netlify 배포 | HTML 파일을 끌어다 놓아 나만의 웹 주소를 만듭니다. 원하는 이름을 붙이고 계속 고쳐 올릴 수 있습니다. |
확인
바이브코딩으로 만든 활용 사례
모두 바이브코딩으로 만든 결과물입니다. 카드를 눌러 직접 사용해 보고, 어떤 요소가 들어 있는지 살펴보세요.
확인
어떤 요소를 넣을 수 있을까요?
요소를 누르면 설명, 수업 활용 예, 요청문 예시가 나옵니다. 일부 요소는 바로 체험해 볼 수 있습니다.
확인
오늘 경험한 바이브코딩
코드를 쓰는 대신 원하는 것을 말하고, 결과를 확인하고, 다시 말하며 게임을 완성했습니다. 이 방식은 게임뿐 아니라 퀴즈, 타이머, 모둠 뽑기처럼 수업에 필요한 작은 도구를 만드는 데도 그대로 쓸 수 있습니다.