현재 JavaScript의 기초 부분을 학습 중이다.
document.createElement() 메소드를 활용하여 몬스터 잡기 게임을 만들어 보았다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#container {
width: 500px; /* 고정된 너비 */
height: 500px; /* 고정된 높이 */
position: relative; /* 자식 요소들의 위치 기준 */
border: 2px solid rgb(255, 158, 158); /* 경계 확인용 */
overflow: hidden; /* 화면 밖으로 넘어가는 요소 숨기기 */
}
</style>
</head>
<body>
<div id = "container"></div>
<script src="monster1.js"></script>
</body>
</html>
우선 HTML 코드는 이렇게 작성해 보았다. container 의 style은 추후에 작성하겠지만 monster의 움직임을 구현하기 위해 작성하였다.
const container = document.getElementById("container");
// 몬스터 모양 만들기
const monster = document.createElement("div");
monster.textContent = "*_*";
monster.style.width = "50px";
monster.style.height = "50px";
monster.style.backgroundColor = "rgb(255, 158, 158)";
monster.style.display = "flex";
monster.style.justifyContent = "center";
monster.style.alignItems = "center";
container.appendChild(monster);
우선 container를 getElementById()를 통해 container 변수에 할당해 주었다.
그 다음으로는 몬스터 모양을 만들었다. (그냥 사진을 쓸 걸 그랬나... 대충 만드느라 그냥 네모로 만들었다.)
document.createElement() 를 사용해서 div 태그를 생성해 주었고, textContent 로 표정을 만들어주었다.
그리고 display: flex; 를 이용해서 표정을 가운데로 위치시켰다.
나름.. 귀여운 몬스터 생성
다음으로는 변수를 선언 및 초기화하고, 입력을 받아주는 부분을 작성했다.
attackDamage 는 사용자에게 prompt 로 입력을 받는다. 한 번 때릴 때마다 몬스터에게 들어가는 데미지.
parseInt() 로 입력값을 정수로 변환하여 할당하였다.지금 생각해보니 attackCount 는 화면에 넣질 않았는데... 빼는 걸 깜빡했다.
// 입력 받기
let monsterHP = 100;
let attackDamage = parseInt(prompt("1회 공격시 데미지는? (양의 정수)"));
let attackCount = 0;
그리고 이벤트 리스너 함수를 만들었다.
몬스터를 클릭하면 공격할 수 있도록!
// 이벤트 리스너 함수 설정
const attackFunction = () => {
attackCount++;
monsterHP -= attackDamage;
const attackText = document.createElement("p");
attackText.textContent = `Attack! : ${-attackDamage}`;
container.appendChild(attackText);
attackText.style.color = "red";
if (monsterHP <= 0) {
container.removeChild(attackText);
container.removeChild(monster);
const h2 = document.createElement("h2");
h2.textContent = `몬스터 잡기 완료! 수고하셨습니다.`;
h2.style.color = "green";
container.appendChild(h2)
} else {
setTimeout(() => {
container.removeChild(attackText);
}, 300); // 0.3초 뒤 삭제
}
}
몬스터를 한 대씩 때릴 때마다 attackDamage만큼 monsterHP가 깎인다.
그리고 document.createElement() 로 p 태그를 하나 생성해서 container.textContent() 로 어택 메시지를 지정하였고, appendChild()로 container 에 넣어주었다.
해당 p 태그는 때릴 때마다 생겼다가 0.3초 뒤 삭제되도록 setTimeout() 을 사용하여 container.removeChild(attackText) 를 작성하였다.
monsterHP <= 0 인 경우 attackText 와 monster 를 화면에서 제거해 주고,
몬스터 잡기 완료 텍스트를 보여주도록 똑같이 createElement(), textContent, appenChild()를 사용하여 작성하였다.
monsterHP > 0 인 경우엔 attackText 0.3초 뒤 삭제!
조건문에 이걸 넣은 이유는, 몬스터 잡기가 완료 됐을 때도 0.3초 뒤 attackText 가 없어지니까 몬스터 잡기 완료 텍스트랑 겹쳐서 안 예뻤다.
함수 작성 끝!
if (attackDamage > 0) {
monster.addEventListener('click', attackFunction);
} else {
alert("잘못 입력하셨습니다. 게임을 취소합니다.")
container.removeChild(monster);
container.style.border = "none";
}
사용자에게 입력 받은 attackDamage 의 유효성 검사도 진행하였다.
유효하지 않은 값을 받은 경우엔 게임을 취소하도록.
.
.
.
.
.
그리고 마지막으로 몬스터가 이리저리 움직이도록 만들었는데, 이게 진짜 헷갈리고 짜증났다.
#container {
width: 500px; /* 고정된 너비 */
height: 500px; /* 고정된 높이 */
position: relative; /* 자식 요소들의 위치 기준 */
border: 2px solid rgb(255, 158, 158); /* 경계 확인용 */
overflow: hidden; /* 화면 밖으로 넘어가는 요소 숨기기 */
}
우선 container 에 스타일을 적용해 주었다.
그냥 화면 넓이만큼 하고 싶었는데 잘 설정이 안되고 몬스터가 화면을 자꾸 벗어나서... 너비랑 높이는 지정해 주었다.
그리고 position: relative; 로 설정하였는데,
container 의 자식 요소인 monster 의 position 에 absolute를 지정하여 좌표대로 자유롭게 위치를 제어하기 위해서이다.
(부모 요소에 position 을 설정하지 않을 경우 뷰포트 기준으로 자식 요소 위치가 지정됨!)
border 은 그냥 경계를 확인하기 위해 만들었다. overflow 역시 화면 밖으로 나가는지 아닌지 확인하려고 설정하였다.
// 화면 크기
const screenWidth = container.offsetWidth;
const screenHeight = container.offsetHeight;
// 몬스터의 이동 방향 및 속도
let xSpeed = Math.random() * 6 + 1; // X축 속도
let ySpeed = Math.random() * 6 + 1; // Y축 속도
let xPosition = 0; // 초기 X 위치
let yPosition = 0; // 초기 Y 위치
function moveMonster() {
xPosition += xSpeed;
yPosition += ySpeed;
// X축 경계에 도달했을 때 반대 방향으로 전환
if (xPosition <= 0 || xPosition >= screenWidth - 50) {
xSpeed = -xSpeed; // 속도 반전
}
// Y축 경계에 도달했을 때 반대 방향으로 전환
if (yPosition <= 0 || yPosition >= screenHeight - 50) {
ySpeed = -ySpeed; // 속도 반전
}
// 몬스터의 위치 업데이트
monster.style.left = xPosition + "px";
monster.style.top = yPosition + "px";
}
// 40ms마다 moveMonster 함수 실행
// 40ms마다 몬스터 이동
setInterval(moveMonster, 40);
먼저 몬스터가 지정된 화면 밖으로 나가면 안되므로, 화면 크기를 지정해 주었다. (container 의 width 와 height 를 그대로 가져왔다.)
그리고 초기 x, y 위치와 속도를 지정해 주었는데,
// 몬스터의 이동 방향 및 속도
let xSpeed = Math.random() * 6 + 1; // X축 속도
let ySpeed = Math.random() * 6 + 1; // Y축 속도
let xPosition = 0; // 초기 X 위치
let yPosition = 0; // 초기 Y 위치
처음에는 xSpeed 와 ySpeed 에 5 라는 같은 값을 주었는데, 같은 방향으로만 움직여서 어색했다.
그래서 랜덤값을 주었다.
Math.random() 은 0부터 1까지의 값을 랜덤으로 출력한다. 따라서 xSpeed와 ySpeed는 1에서 7까지의 랜덤 값을 갖게 된다!
xPosition 과 yPosition 은 말 그대로 초기 위치이다. 몬스터가 처음에 어디에 있을지 설정해 준 것이고, 나는 맨 왼쪽 꼭대기에 위치시켰다.
이 코드를 그림으로 그려보겠다. (오로지 나의 이해를 위해...)
xSpeed = 3, ySpeed = 4 인 상태라고 가정하자.
// moveMonster()
xPosition += xSpeed;
yPosition += ySpeed;
그렇다면 moveMonster() 의 해당 코드에 의해, xPosition = 3, yPosition = 4 가 될 것이다.
그리고 경계에 닿지 않았으니까 바로
// 몬스터의 위치 업데이트
monster.style.left = xPosition + "px";
monster.style.top = yPosition + "px";
해당 코드로 넘어간다.
그러면 monster.left = 3px;, monster.top = 4px; 이 된다! (position: absolute;)
left 에서 3px 만큼 떨어지고, top 에서 4px 만큼 떨어진 곳으로 이동하게 된다.
따라서 몬스터는 이렇게 대각선 선을 따라서 이동하게 된다.
그렇기에 저 대각선의 각도는 당연히, xSpeed랑 ySpeed에 따라서 달라지겠지?
그리고 40ms 후 다시 함수를 실행하여 또 몬스터를 이동시킨다.
그러다가 벽에 붙으면 어찌 될까?
// X축 경계에 도달했을 때 반대 방향으로 전환
if (xPosition <= 0 || xPosition >= screenWidth - 50) {
xSpeed = -xSpeed; // 속도 반전
}
// Y축 경계에 도달했을 때 반대 방향으로 전환
if (yPosition <= 0 || yPosition >= screenHeight - 50) {
ySpeed = -ySpeed; // 속도 반전
}
벽에 붙게 되면 속도가 반전되어 반대로 가게 된다!!
xPosition 이 450 (screenWidth(500) - 50 (monster의 width)) 보다 커지면... 오른쪽 경계를 벗어나게 된다.
yPosition도 역시 마찬가지.
이 정도 하면 몬스터가 경계 내에서 요리조리 움직이는 이유를 설명할 수 있을 것 같다.
그리고 마지막으로 monster 에 style 을 추가 지정해 주었다.
monster.style.position = "absolute";
monster.style.pointerEvents = "all"; // div 전체가 클릭 가능하게 만듦
자유롭게 제어하기 위해 position: absolute; 를, 그리고 div 태그 전체를 클릭할 수 있도록 pointer-Events: all;을 지정했다!
결과는 조금 허접하지만 아무튼 몬스터 때려잡기 완료~!!...
해보다 실패한 것은...
몬스터의 숫자를 지정해서 여러 마리를 보여주고 싶었는데, clone를 복제해도 원래 monster 밖에 함수 적용이 안됐다. (왜 그러지...??)
그래서 두 번째로 시도한 건
몬스터 숫자를 지정해서 한 마리 없앨 때마다 한 마리씩 다시 나타나게 하고 싶었다.
그래서 첫 번째 몬스터가 죽고 container.removeChild(monster) 를 한 뒤에
다시 container.appendChild(monster) 를 줬는데, 함수 적용은 되는데 누르자마자 또 바로 죽어버리더라...;; (왜지 이건)
나중에 시간이 되면 해당 기능들도 구현해 보고 싶다.
끄읕~~!!
'개발 > JavaScript' 카테고리의 다른 글
[JavaScript] JavaScript로 Todo List 만들기 (2) | 2024.10.18 |
---|---|
[JavaScript] 계산기를 만들어 보았습니다. (6) | 2024.10.11 |
[JavaScript] JavaScript에서의 SOLID 원칙 (3) | 2024.09.13 |
[JavaScript] 웹 개발에서 JavaScript가 중요한 이유 (2) | 2024.09.10 |
[JavaScript] var, let, const의 차이점 (2) | 2024.09.05 |