티스토리 뷰

VSCode 익스텐션으로 Chat GPT 기능이 들어왔다고 한다.

 

간단하고 재미난 프로그램을 하나 만들고 싶었다.

 

뭐 만들지 생각하다가 술자리 병돌리기 프로그램을 한번 만들어보려고 한다.

버튼을 누르면 술래를 지목해주는 그런 프로그램.. ㅋㅋ

 

VScode에 익스텐션으로 들어와있는 chat GPT 기능이 여러개가 있는데, 그 중에 easycode - chatGPT를 활용해봤다.

다운로드 수가 많았고, 로그인이 필요 없다고 해서 이걸로 골랐다. 입문자들이 사용하기 좋을 듯하다.

나는 로그인을 해서 사용하긴했는데 불편한 점은 대화 내역을 확인할 수 없다는 것.. 

그리고 가끔 연결이 끊기고 질문이 밀리는 등 버그도 꽤 있었다. 점차 개선되겠지..!

 

 


바로가기

결과물
전체 코드 및 github 링크

 

 

나는 three.js 를 이용한 웹 프로그램을 만들 생각이었고, 프레임워크 없이 html, js, css 만으로 구성할 생각이었다.

 

 

첫 질문

답변

gpt 답변 - 전체 코드

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>병 이미지 회전</title>
    <style>
      body {
        margin: 0;
        overflow: hidden;
      }
      canvas {
        width: 100%;
        height: 100%;
      }
    </style>
  </head>
  <body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script>
      // Scene, Camera, Renderer 생성
      const scene = new THREE.Scene();
      const camera = new THREE.PerspectiveCamera(
        75,
        window.innerWidth / window.innerHeight,
        0.1,
        1000
      );
      const renderer = new THREE.WebGLRenderer();
      renderer.setSize(window.innerWidth, window.innerHeight);
      document.body.appendChild(renderer.domElement);

      // 병 이미지 로드
      const loader = new THREE.TextureLoader();
      const texture = loader.load('soju.png');

      // 병 모델 생성
      const geometry = new THREE.CylinderGeometry(1, 1, 2, 32);
      const material = new THREE.MeshBasicMaterial({ map: texture });
      const cylinder = new THREE.Mesh(geometry, material);
      scene.add(cylinder);

      // 카메라 위치 설정
      camera.position.z = 5;

      // 애니메이션 함수
      function animate() {
        requestAnimationFrame(animate);
        cylinder.rotation.y += 0.01;
        renderer.render(scene, camera);
      }
      animate();
    </script>
  </body>
</html>

 

코드와 답변이 줄줄 나왔다. 

이미지 파일 부분만 내가 다운받은 이미지로 수정하고 돌려보았다.

음... 병이 돌아가긴하는데... 

내가 원하던 건 당연히 이런게 아니었다.

 


 

질문을 뭐라고 해야 얘가 고쳐줄까..

답변

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>병 이미지 회전</title>
    <style>
      body {
        margin: 0;
        overflow: hidden;
      }
      canvas {
        width: 100%;
        height: 100%;
      }
    </style>
  </head>
  <body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script>
      // Scene, Camera, Renderer 생성
      const scene = new THREE.Scene();
      const camera = new THREE.PerspectiveCamera(
        75,
        window.innerWidth / window.innerHeight,
        0.1,
        1000
      );
      const renderer = new THREE.WebGLRenderer();
      renderer.setSize(window.innerWidth, window.innerHeight);
      document.body.appendChild(renderer.domElement);

      // 병 이미지 로드
      const loader = new THREE.TextureLoader();
      const texture = loader.load('soju.png');

      // 병 모델 생성
      const geometry = new THREE.CylinderGeometry(1, 1, 2, 32, 1, true);
      const material = new THREE.MeshBasicMaterial({ map: texture });
      const cylinder = new THREE.Mesh(geometry, material);
      scene.add(cylinder);

      // 카메라 위치 설정
      camera.position.z = 5;

      // 애니메이션 함수
      function animate() {
        requestAnimationFrame(animate);
        cylinder.rotation.z += 0.01;
        renderer.render(scene, camera);
      }
      animate();
    </script>
  </body>
</html>

흠 그래 축이 문제였구나.

 


위 코드로 수정하고 다시 실행시켜보니 까만 화면만 뜨고 병이 보이지 않았다.

답변

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>병 이미지 회전</title>
    <style>
      body {
        margin: 0;
        overflow: hidden;
      }
      canvas {
        width: 100%;
        height: 100%;
      }
    </style>
  </head>
  <body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script>
      // Scene, Camera, Renderer 생성
      const scene = new THREE.Scene();
      const camera = new THREE.PerspectiveCamera(
        75,
        window.innerWidth / window.innerHeight,
        0.1,
        1000
      );
      const renderer = new THREE.WebGLRenderer();
      renderer.setSize(window.innerWidth, window.innerHeight);
      document.body.appendChild(renderer.domElement);

      // 병 이미지 로드
      const loader = new THREE.TextureLoader();
      const texture = loader.load('https://threejsfundamentals.org/threejs/resources/images/wall.jpg');

      // 병 모델 생성
      const geometry = new THREE.CylinderGeometry(1, 1, 2, 32, 1, true);
      const material = new THREE.MeshBasicMaterial({ map: texture });
      const cylinder = new THREE.Mesh(geometry, material);
      scene.add(cylinder);

      // 카메라 위치 설정
      camera.position.set(0, 0, 5);
      camera.lookAt(0, 0, 0);

      // 애니메이션 함수
      function animate() {
        requestAnimationFrame(animate);
        cylinder.rotation.z += 0.01;
        renderer.render(scene, camera);
      }
      animate();
    </script>
  </body>
</html>

그래도 안나와..!

gpt한테 여러번 물어보고 작성해주는 코드들을 다시 실행시켜보기도 하고, 카메라 위치나 시야각을 다른 수치로 조정해주기도 했는데,

 계속 병 모델은 안보이고 생각처럼 잘 되지 않았다.

 


 

그래서 일단 얘가 짜준 코드를 차근차근 봐보기로 했다.

CylinderGeometry ? 

검색해봤더니 원기둥 모양 객체를 생성해주는 역할을 하는 친구였다.

병 모델이라 gpt가 원기둥 모양 모델을 생성하도록 이걸 쓴 것 같은데, 

내가 원하는 프로그램은 원기둥 모양일 필요가 없다.

평면에 소주병 이미지만 들어가면 되기 때문에 BoxGeometry로 변경하기로 결정.

 

gpt한테 시키려는데 자꾸 렉걸리는 바람에 그냥 직접 수정해줬다. 

 

 BoxGeometry로 수정한 코드

더보기
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Bottle Rotation</title>
    <style>
      body {
        margin: 0;
        overflow: hidden;
      }
    </style>
  </head>
  <body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script>
      const loader = new THREE.TextureLoader();
      const texture = loader.load('soju.png');

      const scene = new THREE.Scene();
      const camera = new THREE.PerspectiveCamera(
        45,
        window.innerWidth / window.innerHeight,
        0.1,
        100
      );
      camera.position.z = 3;

      const renderer = new THREE.WebGLRenderer();
      renderer.setSize(window.innerWidth, window.innerHeight);
      document.body.appendChild(renderer.domElement);

      const geometry = new THREE.BoxGeometry(1, 1, 1);
      const material = new THREE.MeshBasicMaterial({ map: texture });
      const box = new THREE.Mesh(geometry, material);
      scene.add(box);

      function animate() {
        requestAnimationFrame(animate);
        box.rotation.z += 0.01;

        renderer.render(scene, camera);
      }

      animate();
    </script>
  </body>
</html>

이제 내가 원하는 방향과 각도로 병이 돌아간다.

 


+)

코드를 파악하는 과정에서 정말 편했던 점이

이런 식으로 직접 물어봐도 친절하게 대답해주고, 

이렇게 코드 부분을 드래그해서 우클릭하면 이 코드가 어떤 기능을 하는지(What does this do?) 알려줄 뿐만 아니라

리팩토링, 유닛 테스트 코드 작성 등의 작업도 해준다.

다만 이렇게 우클릭해서 물어보면

새로운 세션이 형성돼서 이전에 질문하던 흐름은 끊기게 된다.

대화 흐름이 끊기게 되니 코드를 다 작성해놓고 리팩토링이나 유닛 테스트 작성 시에만 활용하도록 해야겠다.

 


 

현재 코드는 html 하나에 몰빵되어 있는데 js로 나누고 싶다.

이것도 gpt한테 부탁해야겠다.

js 파일로 분리할 수 있도록 친절하게 알려준다. 

(gpt 답변 코드는 일부 수정하였다.)

 

▼ gpt 전체 답변

더보기

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Bottle Rotation</title>
    <style>
      body {
        margin: 0;
        overflow: hidden;
      }
    </style>
  </head>
  <body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script src="script.js"></script>
  </body>
</html>

script.js

const loader = new THREE.TextureLoader();
const texture = loader.load('soju.png');

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
  45,
  window.innerWidth / window.innerHeight,
  0.1,
  100
);
camera.position.z = 3;

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ map: texture });
const box = new THREE.Mesh(geometry, material);
scene.add(box);

function animate() {
  requestAnimationFrame(animate);
  box.rotation.z += 0.01;

  renderer.render(scene, camera);
}

animate();

 


이제 원하는 대로 병은 돌아가니, 

실제 술게임 때 활용할 수 있도록 버튼을 누르면 돌아가다가 멈추게 수정해야겠다.

 

우선 버튼 하나를 달기 위해 다음과 같은 요청을 던졌다.

오 그런데 버튼을 달고 거기에 rotateBox()라는 함수도 연결해주었다.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Bottle Rotation</title>
    <style>
      body {
        margin: 0;
        overflow: hidden;
      }
    </style>
  </head>
  <body>
    <button onclick="rotateBox()">Rotate</button>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script src="script.js"></script>
  </body>
</html>

const loader = new THREE.TextureLoader();
const texture = loader.load('soju.png');

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
  45,
  window.innerWidth / window.innerHeight,
  0.1,
  100
);
camera.position.z = 3;

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ map: texture });
const box = new THREE.Mesh(geometry, material);
scene.add(box);

function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}

function rotateBox() {
  box.rotation.z += 0.01;
}

animate();

버튼을 클릭할 때마다 박스가 회전하게..?

내가 버튼을 다는 의도를 파악한 것 같았다. 

상단 버튼을 누를 때마다 병이 일정 각도만큼 회전하는 것을 볼 수 있다.

 


이제 저 rotateBox() 함수만 수정하면 될 듯 하다.

let isRotating = false;
let rotationSpeed = 0.01;
let rotationDuration = 3000; // 3 seconds
let rotationStartTime = 0;

function rotateBox() {
  if (!isRotating) {
    isRotating = true;
    rotationStartTime = Date.now();
  }
}

function animate() {
  requestAnimationFrame(animate);

  if (isRotating) {
    const rotationTime = Date.now() - rotationStartTime;
    if (rotationTime >= rotationDuration) {
      isRotating = false;
      rotationSpeed = 0.01;
    } else {
      rotationSpeed = (rotationDuration - rotationTime) / rotationDuration * 0.01;
    }
    box.rotation.z += rotationSpeed;
  }

  renderer.render(scene, camera);
}

animate();

gpt가 짜준 코드를 보니 rotateBox() 함수와 animate() 함수가 수정되어 있었다.

script.js에서 해당 함수들을 수정하고 실행해보았다.

조금 맥아리가 없지만 내가 원하던 대로 버튼을 누르면 돌아가다가 서서히 멈춘다..!

 


이제 회전 속도와 시간을 조정하면 될 것 같다.

const loader = new THREE.TextureLoader();
const texture = loader.load('soju.png');

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
  45,
  window.innerWidth / window.innerHeight,
  0.1,
  100
);
camera.position.z = 3;

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ map: texture });
const box = new THREE.Mesh(geometry, material);
scene.add(box);

let isRotating = false;
let rotationSpeed = 0.25;
let rotationDuration = 5000; // 5 seconds
let rotationStartTime = 0;

function rotateBox() {
  if (!isRotating) {
    isRotating = true;
    rotationStartTime = Date.now();
  }
}

function animate() {
  requestAnimationFrame(animate);

  if (isRotating) {
    const rotationTime = Date.now() - rotationStartTime;
    if (rotationTime >= rotationDuration) {
      isRotating = false;
      rotationSpeed = 0;
    } else {
      rotationSpeed = (rotationDuration - rotationTime) / rotationDuration * 0.25;
    }
    box.rotation.z += rotationSpeed;
  }

  renderer.render(scene, camera);
}

animate();

 


이제 거의 마무리 되었다.

버튼을 포함한 화면 디자인 정도만 하면 될 듯하다.

 

이것도 gpt한테 부탁해야겠다.

위와 같이 답변이 왔다.

가끔 저렇게 말을 하다가 잘리기도 한다.

그래도 핵심이 되는 코드들은 다 보내줬으니 참고해서 고쳐봐야겠다.

 

gpt의 답변을 살짝 수정해서 디자인을 변경해주었다.

약간의 B급 갬성 첨가 ㅋㅋ


여기서 끝내긴 아쉬워서 

병 돌리기가 끝나면 당첨 문구를 띄우는 기능을 추가 개발하기로 했다.

이것도 gpt한테 부탁해보자.

역시나 상세한 답변이 왔다.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Bottle Rotation</title>
    <style>
      body {
        margin: 0;
        overflow: hidden;
      }

      button {
        position: absolute;
        bottom: 20px;
        left: 50%;
        transform: translateX(-50%);
        padding: 10px 20px;
        background-color: #007bff;
        color: #fff;
        border: none;
        border-radius: 5px;
        font-size: 16px;
        cursor: pointer;
      }

      button:hover {
        background-color: #0069d9;
      }

      h1 {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        font-size: 48px;
        color: #007bff;
        display: none;
      }
    </style>
  </head>
  <body>
    <button onclick="rotateBox()">Rotate</button>
    <h1 id="win-message">당첨</h1>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script src="script.js"></script>
  </body>
</html>

function animate() {
  requestAnimationFrame(animate);

  if (isRotating) {
    const rotationTime = performance.now() - rotationStartTime;
    if (rotationTime >= rotationDuration) {
      isRotating = false;
      rotationSpeed = 0.01;
      document.getElementById("win-message").style.display = "block";
    } else {
      rotationSpeed = (rotationDuration - rotationTime) / rotationDuration * 0.01;
      requestAnimationFrame(animate);
    }
    box.rotation.z += rotationSpeed;
  }

  renderer.render(scene, camera);
}

 

위 답변을 참고해서 코드를 수정해주었다. 

 

주요 변경사항은 button 태그를 추가하고,

css에서는 button의 display 속성을 none으로 해주고,

script.js의 animate() 함수 내 회전이 멈추었을 때 (if절 부분) 버튼이 보여지도록

display 속성을 block으로 바꿔주는 것.

 

다만 위 코드를 그대로 사용하면 문구가 canvas에 가려져 제대로 출력되지 않아서

버튼과 h1에 z-index: 100 속성을 추가해주었다. 

 

또 다른 문제점은 처음 돌리고 나서는 당첨 문구가 계속 떠있다는 점이다. 

그래서 병이 돌아갈 동안에 display: none 이 되도록 수정해주었다.

 

 


결과물

그래서 결과물은 ..!

 

 

하핫 헛웃음이 나오는 퀄리티지만 나름 만족한다^^



약 3시간 할애해서 이 프로그램을 만들었는데 gpt와의 협업(?)이 은근 재밌었다.

 

종종 해결책이 아닌 코드를 당당하게 보내주기도 하지만

three.js를 이용한 병돌리기 애니메이션에,

버튼 추가하고 원하는 이벤트를 달아주고, 

당첨 문구 렌더링까지

gpt 덕분에 매우 편리하게 이 프로그램을 구현할 수 있었다!

 

보통 술자리에서는 폰을 이용할 것이기 때문에 

배포해서 친구들이랑 이용해봐야겠다.

 


전체 코드 및 github 링크

github 링크 : https://github.com/Woojung0618/RotatingBottle

 

GitHub - Woojung0618/RotatingBottle: You can use it for Drinking party.

You can use it for Drinking party. Contribute to Woojung0618/RotatingBottle development by creating an account on GitHub.

github.com

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Bottle Rotation</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <h1 id="win-message">☆당첨☆</h1>
    <button onclick="rotateBox()">병돌리기 시~ 작!</button>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script src="script.js"></script>
  </body>
</html>

script.js

const loader = new THREE.TextureLoader();
const texture = loader.load('soju.png');

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
  45,
  window.innerWidth / window.innerHeight,
  0.1,
  100
);
camera.position.z = 3;

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ map: texture });
const box = new THREE.Mesh(geometry, material);
scene.add(box);

let isRotating = false;
let rotationSpeed = 0.25;
let rotationDuration = 4500; // 5 seconds
let rotationStartTime = 0;

function rotateBox() {
  if (!isRotating) {
    isRotating = true;
    rotationStartTime = Date.now();
    document.getElementById("win-message").style.display = "none";
  }
}

function animate() {
  requestAnimationFrame(animate);

  if (isRotating) {
    const rotationTime = Date.now() - rotationStartTime;
    if (rotationTime >= rotationDuration) {
      isRotating = false;
      rotationSpeed = 0;
      document.getElementById("win-message").style.display = "block";
    } else {
      rotationSpeed = (rotationDuration - rotationTime) / rotationDuration * 0.25;
    }
    box.rotation.z += rotationSpeed;
  }

  renderer.render(scene, camera);
}

animate();

style.css

body {
    margin: 0;
    overflow: hidden;
    background-color: #000000;
}

canvas {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
}

button {
    position: absolute;
    bottom: 80px;
    left: 50%;
    transform: translateX(-50%);
    padding: 10px 20px;
    background-color: red;
    color: #fff;
    border: none;
    border-radius: 5px;
    font-size: 16px;
    cursor: pointer;
    z-index: 100;
}

button:hover {
    color: red;
    background-color: #fff;
}

#win-message {
    color: red;
    position: absolute;
    top: 10%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 100;
    display: none;
}