useEffect
는 React Hook 중 하나로, 컴포넌트가 렌더링된 이후에 부수 효과(side effects)를 수행하는 데 사용됩니다. 예를 들어, 데이터 가져오기, 구독(subscribe), DOM 조작, 타이머 설정 등 컴포넌트의 렌더링과는 직접적인 관계가 없는 작업을 처리할 때 사용합니다.
useEffect
의 기본 사용법:import React, { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// 여기에 수행하고자 하는 부수 효과(코드)를 작성합니다.
console.log("Component mounted!");
// 컴포넌트가 언마운트될 때(정리 작업) 실행될 코드를 반환합니다.
return () => {
console.log("Component unmounted!");
};
}, []); // 빈 배열은 의존성 배열입니다.
return <div>Hello, World!</div>;
}
export default MyComponent;
useEffect(() => { ... })
:
return () => { ... }
:
[]
(의존성 배열):
useEffect
가 다시 실행됩니다.[]
이면 컴포넌트가 처음 마운트될 때 딱 한 번만 실행됩니다.useEffect
가 다시 실행됩니다.우주선에서 총을 쏴서 글자를 입력할 수 있다.
캔버스 밖에는 알파벳들이 날아다니고 있다.
그걸 맞춰야 됨. 원하지 않는 거 맞출수도.
로그인 할 때도, 회원가입 할 때도,
게시판에 글 쓸 때도 전부 쏴서 맞춰서 입력해야됨.
node.js에는 3가지 프레임 워크 있다
express : 절차형. 제일 간단.
nest : oop
next : 풀스택
power2:
power 계열의 이징은 GSAP의 기본 이징 함수 중 하나로, 숫자가 클수록 가속도가 강해집니다. power2는 중간 정도의 가속도를 나타냅니다.
숫자가 클수록 더 급격하게 속도가 변화합니다. 예를 들어, power1은 완만한 가속/감속을 나타내고, power4는 매우 급격한 속도 변화를 보여줍니다.
inOut:
inOut은 애니메이션의 시작과 끝에서 가속과 감속이 함께 일어나는 패턴입니다.
즉, 애니메이션이 처음에는 천천히 시작하고, 중간에 빠르게 진행되다가, 다시 끝에서 천천히 멈추는 방식으로 속도 변화가 나타납니다.
// Phaser 게임 초기화 함수
const initializePhaserGame = () => {
const config = {
type: Phaser.AUTO,
width: 600,
height: 400,
parent: "phaser-container", // Phaser가 렌더링될 div 또는 canvas
scene: {
preload: preload,
create: create,
update: update,
},
};
const game = new Phaser.Game(config);
// preload 함수: 게임에서 사용할 리소스를 로드
function preload() {
this.load.image("logo", "https://phaser.io/images/img.png");
}
// create 함수: 게임 로직을 초기화
function create() {
const logo = this.add.image(300, 200, "logo");
this.tweens.add({
targets: logo,
y: 300,
duration: 2000,
ease: "power2.inOut",
yoyo: true,
repeat: -1,
});
}
// update 함수: 매 프레임마다 호출되는 로직
function update() {
// 업데이트할 게임 로직이 있다면 여기서 구현
}
};
<canvas>
를 사용하여 게임을 렌더링.kenny 애셋 적극 활용
function create() {
// 캐릭터 스프라이트를 화면 중앙에 추가
player = this.physics.add.sprite(300, 200, "character");
// 좌우 이동을 위한 키보드 입력 감지
cursors = this.input.keyboard.createCursorKeys();
}
this.physics.add.sprite는 Phaser의 물리 엔진을 통해 스프라이트(이미지 오브젝트)를 생성하는 메서드입니다. 이 메서드는 물리 속성을 가진 게임 오브젝트를 만들어주므로, 캐릭터가 중력이나 충돌 등을 인식할 수 있습니다.
"character": 미리 불러온 이미지의 키(key)로, preload에서 로드한 "character"라는 이름의 스프라이트를 사용합니다.
this.input.keyboard.createCursorKeys()는 키보드 입력을 감지하기 위한 메서드로, 기본적으로 방향키(좌, 우, 상, 하)를 감지하는 객체(cursors)를 생성합니다.
이 cursors 객체는 네 방향의 키(left, right, up, down)와 관련된 정보를 담고 있으며, 각 방향키가 눌렸는지 여부를 쉽게 확인할 수 있습니다.
cursors = {
left: { isDown: false },
right: { isDown: false },
up: { isDown: false },
down: { isDown: false },
};
cursors 객체는 위와 같이 생겼습니다
// create 함수: 게임 로직을 초기화
function create() {
// 캐릭터 스프라이트를 화면 중앙에 추가
player = this.physics.add.sprite(300, 200, "character");
// 캐릭터가 화면 바깥으로 나가지 못하게 설정
player.setCollideWorldBounds(true); // 이 부분 추가
// 좌우 이동을 위한 키보드 입력 감지
cursors = this.input.keyboard.createCursorKeys();
}
이 메서드는 스프라이트가 화면 경계를 넘지 않도록 제한하는 역할을 합니다. 이 메서드가 추가되면 캐릭터가 화면의 가장자리에서 더 이상 나가지 않고, 경계에 충돌하여 멈추게 됩니다.
화면 경계는 config.width와 config.height로 정의된 게임의 크기에 의해 결정됩니다. 이 경우 화면의 너비는 600px, 높이는 400px으로 설정되어 있습니다.
Phaser에서 this는 대부분 현재의 씬(scene) 객체를 가리키며, 씬 내에서 제공되는 다양한 기능(메서드, 속성)을 사용하기 위해 this를 사용합니다. 씬 객체는 게임의 화면과 상태를 관리하며, 스프라이트 추가, 입력 처리, 물리 적용 등 다양한 기능을 제공합니다.
Phaser에서 this를 통해 씬 객체에 접근하는 이유는 씬 내에서 발생하는 대부분의 작업이 해당 씬 객체에 속해 있기 때문입니다. Phaser는 씬 객체를 통해 게임의 모든 요소를 관리하므로, this를 통해 씬의 메서드와 속성에 접근하게 됩니다.
글자가 오른쪽에서 생성되면 자꾸 스크롤바가 조금씩 생겨났다가 지워져서 화면이 드드득거림.
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f5f5f5;
overflow: hidden;
}
body에 overflow: hidden
추가해서 스크롤바 안 생기게 함.
일단 해결은 됐는데 아름다운 해결책은 아닐지도.
oncomplete로 해결
// 마우스 위치에 따라 터렛 이미지를 회전시키는 함수
export const rotateTurretToMouse = (turretRef) => {
const handleMouseMove = (e) => {
if (turretRef.current) {
// 터렛 요소의 위치
const turretRect = turretRef.current.getBoundingClientRect();
const turretCenterX = turretRect.left + turretRect.width / 2;
const turretCenterY = turretRect.top + turretRect.height / 2;
// 마우스 위치
const mouseX = e.clientX;
const mouseY = e.clientY;
// 마우스와 터렛 중심점 간의 각도 계산 (라디안)
const angle = Math.atan2(mouseY - turretCenterY, mouseX - turretCenterX);
// 각도를 도(degree)로 변환하고 터렛에 적용
const rotation = (angle * 180) / Math.PI;
turretRef.current.style.transform = `rotate(${rotation}deg)`;
}
};
// 마우스 이동 이벤트 리스너 추가
window.addEventListener("mousemove", handleMouseMove);
// 컴포넌트 언마운트 시 이벤트 리스너 제거
return () => {
window.removeEventListener("mousemove", handleMouseMove);
};
};
// HTML 입력 창을 Phaser 위에 추가
inputField = this.add.dom(this.cameras.main.width / 2, 50).createFromHTML(`
<input type="text" id="player-input" name="player-input" placeholder="Enter your name..." style="font-size: 24px; width: 200px; padding: 10px;"/>
`);
inputField.setOrigin(0.5); // 중앙에 위치
inputField.setDepth(10); // 다른 요소들 위에 위치하게 설정
Phaser에서 HTML DOM 요소를 추가할 때 DOMElement 기능을 사용하여 Phaser 씬에 추가해야 정상적으로 화면에 보입니다. 만약 입력 필드가 보이지 않는다면, DOM 요소를 활성화하는 설정을 추가해야 할 수 있습니다.
const config = {
type: Phaser.AUTO,
width: 600,
height: 400,
parent: parentId,
physics: {
default: "arcade",
arcade: {
gravity: { y: 750 },
debug: false,
},
},
scene: {
preload: preload,
create: create,
update: update,
},
dom: {
createContainer: true, // DOM 요소를 사용하기 위한 설정
},
};
창 크기를 조절할 때, 터렛의 위치가 잘못되거나 고정된 이유는 창 크기가 변경될 때마다 캔버스의 위치(canvasRect)가 업데이트되지 않기 때문입니다. 현재 터렛은 처음 생성될 때의 캔버스 위치에 맞춰 배치되며, 창 크기를 조절할 경우 이 위치 정보가 갱신되지 않아 비정상적인 위치로 이동하게 됩니다.
import { rotateTurretToMouse } from "./rotateTurretToMouse"; // 마우스 커서를 향해 회전하는 함수
export const createTurret = (canvasRef, turretRef, isTurretCreated, setIsTurretCreated) => {
// 터렛 위치 업데이트 함수
const updateTurretPosition = () => {
if (canvasRef.current && turretRef.current) {
const canvasRect = canvasRef.current.getBoundingClientRect();
turretRef.current.style.top = `${canvasRect.top - 100}px`;
turretRef.current.style.left = `${canvasRect.right - 150}px`;
}
};
if (!isTurretCreated && canvasRef.current) {
const canvasRect = canvasRef.current.getBoundingClientRect();
console.log("Canvas bounds:", canvasRect); // 캔버스 위치 출력
const turret = document.createElement("img");
turret.src = "/assets/tank_turret.png"; // 이미지 경로 확인
turret.className = "turret"; // CSS 클래스 적용
// 우측 상단에 터렛 배치
turret.style.position = "absolute";
turret.style.top = `${canvasRect.top - 100}px`;
turret.style.left = `${canvasRect.right - 150}px`;
document.body.appendChild(turret); // DOM에 터렛 추가
turretRef.current = turret; // 터렛 참조 업데이트
rotateTurretToMouse(turretRef); // 마우스 커서 방향으로 터렛 회전
setIsTurretCreated(true); // 터렛 생성 상태 업데이트
// 창 크기가 조정될 때마다 터렛 위치를 업데이트
window.addEventListener("resize", updateTurretPosition);
}
// 컴포넌트가 언마운트되면 이벤트 리스너 제거
return () => {
window.removeEventListener("resize", updateTurretPosition);
};
};
input에 유저가 키보드로는 입력 못하고 javascript로만 값이 편집될 수 있게 하려면 어케 해야됨
HTML의 readonly 속성을 사용하면 사용자가 직접 수정할 수 없지만, JavaScript로는 값을 변경할 수 있습니다.
<input type="text" id="player-input" name="player-input" placeholder="Enter your name..."
style="font-size: 24px; width: 200px; padding: 10px;" readonly />
add
와 create
의 차이this.add
: Phaser.GameObjects.GameObjectFactory
의 메소드로, 화면에 게임 객체를 추가합니다. 게임 내에서 스프라이트, 텍스트, 이미지 등 여러 가지 시각적 요소를 추가할 수 있습니다.
this.add.image()
는 화면에 이미지를 추가하는 역할을 합니다.this.add
는 단순히 화면에 이미지를 표시하는 데 사용됩니다.this.physics.add
: 물리엔진이 적용된 객체를 생성하고, 충돌 감지를 위해 사용하는 ArcadePhysics
의 메소드입니다. 물리적 상호작용(중력, 충돌 등)이 필요한 경우 이 방식을 사용해야 합니다.
this.physics.add.sprite()
는 물리엔진이 적용된 스프라이트 객체를 생성하고, 물리적인 상호작용을 지원합니다.this.physics.add.staticGroup()
: 움직이지 않는 물체 그룹을 생성하고 충돌 감지를 위한 물리 엔진을 활성화합니다.
create
는 물리엔진과 연결된 게임 객체를 생성할 때 사용되며, 주로 충돌 감지 및 물리적인 상호작용을 처리합니다.// this.add로 단순 이미지 추가 (물리적 상호작용 없음)
this.add.image(100, 100, "myImage");
// this.physics.add로 물리적 상호작용이 있는 스프라이트 추가
this.physics.add.sprite(200, 200, "mySprite");
// 정적인 물체를 물리엔진과 함께 생성
this.physics.add.staticGroup().create(300, 300, "ground");
이미지 크기가 제각각이라면, Phaser에서 이미지 크기를 조절할 수 있는 두 가지 방법이 있습니다:
setDisplaySize(width, height)
: 이미지의 표시 크기를 설정합니다. 원본 비율을 유지하지 않으며, 임의의 크기로 변경할 수 있습니다.
this.add.image(100, 100, 'myImage').setDisplaySize(100, 100);
setScale(scaleX, scaleY)
: 이미지의 원래 크기를 기준으로 스케일을 조정합니다. 원본 비율을 유지하거나, 원하는 비율로 크기를 확대/축소할 수 있습니다.
scaleX
와 scaleY
에 동일한 값을 주면 됩니다.this.add.image(100, 100, 'myImage').setScale(2); // 원래 크기보다 2배로 확대
this.add.image(100, 100, 'myImage').setScale(1, 2); // x축 100%, y축 200%
마우스에 따라서 총 쏘게 하는게 좀 빡세다
gpt한테 시키는 거 첫 한계
내가 직접 생각하고 짜야할듯
총알을 길쭉하게 만들면 방향도 조절해야돼서
원형 이미지 가져다 써야할듯
// 클릭 시마다 총알 발사
const fireBullet = () => {
console.log("pretend fired");
};
// 캔버스가 표시됐고 스위치가 활성화됐다면 window에 클릭 이벤트 리스너 추가
// 스위치가 비활성화된다면 이벤트 리스너 제거
useEffect(() => {
if (isCanvasVisible && canvasRef.current)
{
window.addEventListener('click', fireBullet);
}
// 컴포넌트가 언마운트될 때 이벤트 리스너 제거
return () => {
window.removeEventListener('click', fireBullet);
};
}, [isCanvasVisible, canvasRef]);
일단 이거 추가는 했음.
콘솔에 로그는 잘 찍힘.
적당히 하다가 gpt한테 맡김
원래 쏘면 빠르게 나가야되는데 너무 느려서 이 지경까지 가버림
export const fireBullet = (turretRef) => {
let lastFireTime = 0; // 마지막 발사 시간을 추적
const fireCooldown = 500; // 500ms = 0.5초 간격으로 발사 제한
// 총알을 발사하는 함수
const handleMouseClick = (e) => {
const currentTime = Date.now(); // 현재 시간 (밀리초)
// 발사 딜레이를 체크하여 발사 가능 여부 확인
if (currentTime - lastFireTime >= fireCooldown) {
lastFireTime = currentTime; // 발사 성공 시 마지막 발사 시간 업데이트
if (turretRef.current) {
// 터렛 요소의 위치
const turretRect = turretRef.current.getBoundingClientRect();
const turretCenterX = turretRect.left + turretRect.width / 2;
const turretCenterY = turretRect.top + turretRect.height / 2;
// 마우스 위치
const mouseX = e.clientX;
const mouseY = e.clientY;
// 마우스와 터렛 중심점 간의 각도 계산 (라디안)
const angle = Math.atan2(mouseY - turretCenterY, mouseX - turretCenterX);
// 총알 생성
const bullet = document.createElement("div");
bullet.className = "bullet"; // 총알에 사용할 스타일 클래스
bullet.style.position = "absolute";
bullet.style.width = "10px"; // 총알 크기
bullet.style.height = "10px";
bullet.style.backgroundColor = "red"; // 총알 색상
bullet.style.borderRadius = "50%"; // 총알을 원형으로
bullet.style.left = `${turretCenterX - 5}px`; // 터렛의 중심에서 발사
bullet.style.top = `${turretCenterY - 5}px`;
// 초기 transform 설정
bullet.style.transform = 'translate(0, 0)';
// 총알을 DOM에 추가
document.body.appendChild(bullet);
// 다음 프레임에서 transform 변경
requestAnimationFrame(() => {
// 총알의 방향에 따른 목표 위치 설정 (멀리 보냄)
const targetX = Math.cos(angle) * 1000; // 1000px 이동
const targetY = Math.sin(angle) * 1000;
// CSS 트랜지션으로 총알 이동 애니메이션 처리
bullet.style.transition = "transform 1s linear";
bullet.style.transform = `translate(${targetX}px, ${targetY}px)`;
});
// 총알 제거: 1초 후 자동 제거
setTimeout(() => {
bullet.remove(); // 총알 제거
}, 1000); // 1초 후 총알을 제거
}
}
};
// 마우스 클릭 이벤트 리스너 추가 (총알 발사)
window.addEventListener("click", handleMouseClick);
// 컴포넌트 언마운트 시 이벤트 리스너 제거
return () => {
window.removeEventListener("click", handleMouseClick);
};
};
gpt4o는 갈궈도 제대로 안 해주길래 o1-preview 썼더니 한 방에 해결해줌.
css로 바꾼 것 까진 gpt4o가 해줌 (내 기억에도 js로 애니메이션 쓰면 느리다고 알고 있긴 했음)
근데그러고 나니까 총알이 멀찍이에서 한 번 생성되고 사라짐
확인해보니, 현재 코드에서 총알이 제대로 움직이지 않는 문제는 CSS 트랜지션이 예상대로 작동하지 않기 때문입니다. 이는 총알 요소를 DOM에 추가한 직후에 transform 속성을 설정하면 브라우저가 트랜지션을 적용하지 않기 때문에 발생합니다.
라는 걸 바로 알아차려줌.
우다다다 갈겨도 이제 렉 안 걸림
inputField = this.add.dom(this.cameras.main.width / 2, 100).createFromHTML(
<textarea type="text" id="player-input" name="player-input" placeholder="글을 입력하세요"
style="font-size: 24px; width: 300px; height: 100px; padding: 10px;" readonly></textarea>
);
inputField는 이건데, 이거 뭐 어떻게 접근 못하는거임?
Phaser에서 DOMElement를 추가할 때, 기본적으로 DOM에서 직접적으로 접근할 수 있는 방식과는 조금 다릅니다. Phaser의 DOMElement는 Phaser 씬 위에 HTML 요소를 렌더링하지만, 이 요소에 접근하거나 값을 수정할 때는 inputField.node를 통해 실제 DOM 요소에 접근해야 합니다.
Cannot read properties of undefined (reading 'node')
아직도 안된다는데
이게 제일 가능성 있어보임
if (playerTouchingDeleteButton) {
inputField.node.value = "";
// deleteButtonPressed(deleteButton, inputField);
}
생각해보니 굳이 함수로 넣을 이유 없어서 인자로 넣어주는게 아니라 원래 파일에서 초기화 진행해줬는데 오류는 안 뜨는데 삭제도 안됨.
문제가 발생한 원인은 inputField.node가 실제로 <textarea> 요소가 아니라,
그 부모 요소(예: <div>)이기 때문입니다. createFromHTML 메서드를 사용할 때,
생성된 DOM 구조의 최상위 요소를 inputField.node로 참조하게 됩니다.
따라서 inputField.node는 <textarea>가 아니라 그 상위 요소일 가능성이 높습니다.
if (playerTouchingDeleteButton) {
const textarea = inputField.node.querySelector('textarea');
textarea.value = ''; // 값 초기화
console.log("deleteButton");
}
그 말 그대로였다. 이제 정상적으로 지워주기 시작.
input을 textarea로 바꾸고
총알이 글자랑 부딪히면 둘 다 사라지게 하고
문이랑 버튼 추가하고
플레이어랑 충돌하면서 c 누르면 함수 호출하도록 스켈레톤 짜고
제일 간단한 delete 버튼 구현
오늘은 여기까지 완성