display
속성을 grid
로 설정해주고,
grid-template-rows
와 grid-template-columns
속성을 이용해 가로 4, 세로 4 너비와 높이가 20vmin인 그리드를 만들어준다.
const Board = styled.div`
display: grid;
grid-template-rows: repeat(4, 20vmin);
grid-template-columns: repeat(4, 20vmin);
gap: 2vmin;
border-radius: 1vmin;
padding: 2vmin;
position: relative; // 나중에 위에 타일을 올려야하므로 relative로 설정
`;
const Cell = styled.div`
background-color: lightgray;
border-radius: 1vmin;
`;
export default function Grid() {
return (
<Board>
{new Array(16).fill(0).map((item) => (
<Cell />
))}
</Board>
);
}
vmin
& vmax
vmin
단위는 처음 사용해보는데, v
는 viewport를 의미하며,
vmax
은 viewport의 가로, 세로 너비 중 최대 너비, vmin
은 viewport의 가로, 세로 너비 중 최소 너비를 말한다.
vmax
은 viewport의 가로, 세로 너비 중에 큰 너비를 기준으로 하고,vmin
은 viewport의 가로, 세로 너비 중에 작은 너비를 기준으로 한다.나중에 4 * 4가 아닌 다른 크기의 그리드를 만들 수도 있기 때문에 GRID_SIZE
를 변수로 선언해주고, cell-size
와 cell-gap
도 반복해서 사용할 것이기 때문에 변수로 선언해준다.
const GRID_SIZE = 4;
const Board = styled.div`
--cell-size: 20vmin;
--cell-gap: 2vmin;
display: grid;
grid-template-rows: repeat(${GRID_SIZE}, var(--cell-size));
grid-template-columns: repeat(${GRID_SIZE}, var(--cell-size));
gap: var(--cell-gap);
border-radius: 1vmin;
padding: var(--cell-gap);
position: relative;
`;
const Cell = styled.div`
background-color: lightgray;
border-radius: 1vmin;
`;
export default function Grid() {
return (
<Board>
{new Array(GRID_SIZE * GRID_SIZE).fill(0).map((_, i) => (
<Cell key={i} />
))}
</Board>
);
}
이제 그리드 위에 올릴 Tile을 만들어주자.
Tile은 매개변수로 x
와 y
값을 받아 해당하는 위치에 렌더링되도록 top
, left
속성에 값을 계산해줘야 한다.
먼저 position
속성을 absolute
로 설정해주고,
top
은 입력받은 y
값에 (셀의 너비 + 셀 갭) 만큼 곱해주면 되고,
left
는 입력받은 x
값에 (셀의 너비 + 셀 갭) 만큼 곱해주면 된다.
하지만 맨 바깥 가장자리도 더해줘야 하기 때문에 마지막에 셀 갭을 한 번 더 더해줘야 한다. ➡️ calc(x * (셀의 너비 + 셀 갭) + 셀 갭)
const Tile = styled.div`
--x: 1; // 일단 CSS 내에 변수로 x, y 값을 임의로 설정해주었다.
--y: 2;
width: var(--cell-size);
height: var(--cell-size);
position: absolute;
top: calc(var(--y) * (var(--cell-size) + var(--cell-gap)) + var(--cell-gap));
left: calc(var(--x) * (var(--cell-size) + var(--cell-gap)) + var(--cell-gap));
background-color: pink;
border-radius: 1vmin;
`;
@keyframes
을 이용해 show라는 애니메이션을 만들고, 어떤 효과를 실행할건지 구체적인 단계를 작성해준다.
그리고 타일의 animation
속성에 만든 show 애니메이션을 설정해준다.
const Tile = styled.div`
// ...생략
background-color: hsl(200, 50%, 20%);
color: hsl(200, 25%, 80%);
animation: show 200ms ease-in-out; // show라는 애니메이션을 0.2초 동안 ease-in-out하게 보여준다.
@keyframes show {
0% { // 투명도 50%, 0의 크기에서 시작해서 진해지며 커지는 효과
opacity: 0.5;
transform: scale(0);
}
}
`;
hsl()
hsl()
함수는 빨강(R), 녹색(G) 및 파랑(B) 값 대신 색조 (hue), 채도 (saturation) 및 명도(lightness) 값을 받는다.
import { getInitialGrid } from '../util/tile';
function Grid() {
const [tileList, setTileList] = useState(getInitialGrid);
// ...생략
}
게임이 시작하면 랜덤한 위치에 타일 2개가 생성된다.
getInitialGrid()
는 initialTileList
라는 빈 배열을 생성하고, makeTile()
함수를 이용해 타일을 생성한 후 initialTileList
에 푸쉬해준다.
그러면 타일이 2개가 담긴 타일 리스트가 반환되며 화면에 렌더링된다.
// 첫 타일 2개 생성
export function getInitialGrid() {
const initialTileList = [];
const tile1 = makeTile(initialTileList);
tileList.push(tile1);
const tile2 = makeTile(initialTileList);
tileList.push(tile2);
return initialTileList;
}
makeTile()
함수는 타일을 만들어 반환하는 함수이다.
이미 타일이 있는 곳에 타일을 생성하면 안되므로, 매개변수로 타일리스트를 받아서 새롭게 만든 타일이 중복되지 않는지 체크한다.
x와 y에는 0, 1, 2, 3 중 랜덤한 값이 들어가고, 새롭게 생성된 타일의 value은 2로 지정해준다.
let currentId = 0;
// 새로운 타일 만드는 함수
export function makeTile(tileList) {
let tile;
while (!tile || checkCollision(tileList, tile)) {
tile = {
id: currentId++,
x: Math.floor(Math.random() * 3),
y: Math.floor(Math.random() * 3),
value: 2,
};
}
return tile;
}
checkCollision()
함수는 tileList
와 타일을 매개변수로 받아, x와 y값이 겹치는 타일이 있는지 확인하고 true나 false를 반환한다.
function checkCollision(tileList, tile) {
return tileList.some((item) => item.x === tile.x && item.y === tile.y);
}
우와 2048 게임구현이 이런식으로 가능하군용!
저도 한번 만들어보고 싶네요 ㅎㅎ 완성작 기대됩니다!