Canvas Game Event 구성
도형 구성과 배치를 위한 UI 가 구성되었으면, 이제 사용자가 선택후 Drag & Drop 으로 위치를 맞추고,
그 결과를 담아 내기 위한 Event 처리가 필요합니다.
요즘은 PC 가 아닌, 핸드폰에서도 Web 접근이 가능하기 때문에 Phone Device 를 위한 Event 처리를 위해서는
touchstart 등의 이벤트도 정리하여야 하지만, 이 글에서는 다루지 않겠습니다.
다음주소 에서 확인해 보실 수 있습니다.
Initialize 시점에 Event 구성
초기화 과정에 Canvas 에 Mouse Event 를 할당 하는 과정 입니다. mousedown 에서 event 시작과 위치등을 기록합니다. mousemove 에서 사용자에게 놓일 위치, 성공했을 때 삭제될 모형 등을 미리 보여주는 Event 를 처리 합니다. mousedown 에서 실제로 삭제할 영역, 그리고 점수 등을 계산 한후 전체를 다시 그리는 함수를 호출합니다.
canvasInfos.activeCanvas.canvas.addEventListener("mousedown", function(e) {
...
});
canvasInfos.activeCanvas.canvas.addEventListener("mousemove", function(e) {
...
});
canvasInfos.activeCanvas.canvas.addEventListener("mouseup", function(e) {
...
});
처음 초기화 단계에서 호출 되는 함수 입니다.
자료형은 앞 Post 에서 간단하게 언급한 mouseEventInfos, gameSelectedInfos 가 event 진행중에 사용되고,
gameMatrixInfos 가 최종 화면을 갱신할 때 사용하게 됩니다.
아래는 초기화 과정 소스 입니다.
function initCanvas() {
var gap = 10;
var w = window.innerWidth;
var h = window.innerHeight;
initBaseSizeUI();
var disArray = [[true,true],[true,false]];
var size = 15;
var strokeStyle = "white";
var fillStyle = "#000080";
var diff = 100;
var ctx = canvasInfos.activeCanvas.ctx;
ctx.clearRect(0,0,canvasInfos.w, canvasInfos.h);
canvasInfos.activeCanvas.canvas.addEventListener("mousedown", function(e) {
let cx = e.x;
let cy = e.y;
let idx = gameSelectedInfos.whichSelected(cx,cy);
if ( idx >= 0 ) {
canvasInfos.activeCanvas.canvas.style.cursor = "move";
mouseEventInfos.which = idx;
mouseEventInfos.mouseDown = true;
}
}, false);
canvasInfos.activeCanvas.canvas.addEventListener("mousemove", function(e) {
if ( !mouseEventInfos.mouseDown ) {
return;
}
let cx = e.x;
let cy = e.y;
gameSelectedInfos.data[mouseEventInfos.which].movedX = cx;
gameSelectedInfos.data[mouseEventInfos.which].movedY = cy;
drawMovingSelectCells();
resetMouseOverFlag();
let sx = gameSelectedInfos.data[mouseEventInfos.which].moveStartX;
let sy = gameSelectedInfos.data[mouseEventInfos.which].moveStartY;
let ox = gameSelectedInfos.data[mouseEventInfos.which].x;
let oy = gameSelectedInfos.data[mouseEventInfos.which].y;
let flag = isInRangeShape(cx-(sx-ox),cy-(sy-oy), gameMatrixInfos.data, gameSelectedInfos.data[mouseEventInfos.which].typeArray, canvasInfos.cellSize, true );
if ( flag ) {
checkDataOverValues();
drawBaseCanvasUI();
resetMouseOverFlag();
}
}, false);
canvasInfos.activeCanvas.canvas.addEventListener("mouseup", function(e) {
if ( !mouseEventInfos.mouseDown ) {
return;
}
gameSelectedInfos.data[mouseEventInfos.which].selected = false;
let cx = e.x;
let cy = e.y;
gameSelectedInfos.data[mouseEventInfos.which].movedX = cx;
gameSelectedInfos.data[mouseEventInfos.which].movedY = cy;
let sx = gameSelectedInfos.data[mouseEventInfos.which].moveStartX;
let sy = gameSelectedInfos.data[mouseEventInfos.which].moveStartY;
let ox = gameSelectedInfos.data[mouseEventInfos.which].x;
let oy = gameSelectedInfos.data[mouseEventInfos.which].y;
let flag = isInRangeShape(cx-(sx-ox),cy-(sy-oy), gameMatrixInfos.data, gameSelectedInfos.data[mouseEventInfos.which].typeArray, canvasInfos.cellSize );
if ( flag ) {
gameSelectedInfos.data[mouseEventInfos.which].isUsed = true;
}
drawMovingSelectCells();
mouseEventInfos.mouseDown = false;
mouseEventInfos.which = -1;
drawBaseCanvasUI();
canvasInfos.activeCanvas.canvas.style.cursor = "default";
setTimeout(checkDataPointValues,500);
}, false);
}
Mouse Move, Up 진행 과정
- User 가 선택한 도형을 그립니다.
drawMovingSelectCells 함수는 사용자가 이동한 위치를 기반으로 선택하 도형을 그리는 역활을 수행합니다.
- 사용자 편의를 위해 Drop 이 가능한 위치를 표시합니다.
isInRangeShape 함수에서 현재 도형의 위치를 기반으로 어디에 어떻게 적용되는지를 그려 줍니다.
대략 픽셀의 반이 걸리쳐 그 안의 내용을 기준으로 출력합니다.
- 현재 위치 기준 점수를 올릴 수 있는 영역인지 확인합니다.
checkDataOverValues 함수는 선택한 도형과, 위치를 기반으로 점수를 가져올 수 있는 영역 인지 판정합니다.
- 사용자 선택이 완료 되면 약간의 시간을 주고 최종 결과를 확인합니다.
checkDataPointValues 함수에서 전체를 재 구성하는 역활을 수행 합니다. 이 때 Game 점수를 누증 시킬 수 있습니다 .
코드 입니다.
function drawMovingSelectCells() {
var canvas = canvasInfos.activeCanvas.canvas;
var ctx = canvasInfos.activeCanvas.ctx;
ctx.clearRect(0,0,canvas.width, canvas.height);
ctx.save();
var strokeStyle = "white";
var fillStyle = "#000080";
for ( let i = 0; i < gameSelectedInfos.data.length; i++ ) {
if ( gameSelectedInfos.data[i].isUsed ) {
continue;
}
if ( gameSelectedInfos.data[i].selected ) {
let gapX = gameSelectedInfos.data[i].moveStartX - gameSelectedInfos.data[i].movedX;
let gapY = gameSelectedInfos.data[i].moveStartY - gameSelectedInfos.data[i].movedY;
let msx = gameSelectedInfos.data[i].x-gapX;
let msy = gameSelectedInfos.data[i].y-gapY;
drawCells(ctx,msx,msy, gameSelectedInfos.data[i].w, gameSelectedInfos.data[i].typeArray, strokeStyle, fillStyle);
} else {
drawCells(ctx,gameSelectedInfos.data[i].x,gameSelectedInfos.data[i].y, gameSelectedInfos.data[i].w, gameSelectedInfos.data[i].typeArray, strokeStyle, fillStyle);
}
}
ctx.restore();
}
function isInRangeShape(cx,cy, dataArray, shape, size, isOver ) {
let rows = dataArray.length;
let cols = -1;
if ( rows > 0 ) {
cols = dataArray[0].length;
}
let gap = size/2;
for ( let i = 0; i < rows; i++ ) {
for ( let j = 0; j < cols; j++ ) {
if ( dataArray[i][j].x-gap < cx && dataArray[i][j].x + gap >= cx
&& dataArray[i][j].y-gap < cy && dataArray[i][j].y + gap >= cy ) {
for ( let t = 0; t < shape.length; t++ ) {
for ( let u = 0; u < shape[t].length; u++ ) {
if ( t+i >= rows ) {
return false;
}
if ( u+j >= cols ) {
return false;
}
if ( shape[t][u] && dataArray[i+t][j+u].isFilled ) {
return false;
}
}
}
for ( let t = 0; t < shape.length; t++ ) {
for ( let u = 0; u < shape[t].length; u++ ) {
if ( shape[t][u] ) {
if ( isOver ) {
dataArray[i+t][j+u].isOver = true;
} else {
dataArray[i+t][j+u].isFilled = true;
}
}
}
}
return true;
}
}
}
return false;
}
function checkDataPointValues() {
var points = 0;
var repeat = 1;
let rows = gameMatrixInfos.data.length;
let cols = rows > 0 ? gameMatrixInfos.data[0].length : 0;
var verticalArray = [];
var horizontalArray = [];
for ( let i = 0; i < rows; i++ ) {
let flag = true;
for ( let j = 0; j < cols; j++ ) {
if ( !gameMatrixInfos.data[i][j].isFilled ) {
flag = false;
break;
}
}
if ( flag ) {
verticalArray.push(i);
}
}
for ( let i = 0; i < cols; i++ ) {
let flag = true;
for ( let j = 0; j < rows; j++ ) {
if ( !gameMatrixInfos.data[j][i].isFilled ) {
flag = false;
break;
}
}
if ( flag ) {
horizontalArray.push(i);
}
}
for ( let i = 0; i < rows; i += 3 ) {
for ( let j = 0; j < cols; j += 3 ) {
let flag = true;
for ( let t = 0; t < 3; t++ ) {
for ( let u = 0; u < 3; u++ ) {
if ( !gameMatrixInfos.data[i+t][j+u].isFilled ) {
flag = false;
break;
}
}
if ( !flag )
break;
}
if ( flag ) {
for ( let t = 0; t < 3; t++ ) {
for ( let u = 0; u < 3; u++ ) {
gameMatrixInfos.data[i+t][j+u].isFilled = false;
}
}
points += 9*repeat;
++repeat;
}
}
}
for ( let i = 0; i < verticalArray.length; i++ ) {
for (let j = 0; j < cols; j++ ) {
gameMatrixInfos.data[verticalArray[i]][j].isFilled = false;
}
points += 12*repeat;
++repeat;
}
for ( let i = 0; i < horizontalArray.length; i++ ) {
for (let j = 0; j < rows; j++ ) {
gameMatrixInfos.data[j][horizontalArray[i]].isFilled = false;
}
points += 12*repeat;
++repeat;
}
let isAll = true;
for ( let i = 0; i < gameSelectedInfos.data.length; i++ ) {
if ( !gameSelectedInfos.data[i].isUsed ) {
isAll = false;
break;
}
}
if ( isAll ) {
makeCanvasInfoValues();
}
drawBaseCanvasUI();
}
function checkDataOverValues() {
let rows = gameMatrixInfos.data.length;
let cols = rows > 0 ? gameMatrixInfos.data[0].length : 0;
var verticalArray = [];
var horizontalArray = [];
for ( let i = 0; i < rows; i++ ) {
let flag = true;
for ( let j = 0; j < cols; j++ ) {
if ( !(gameMatrixInfos.data[i][j].isFilled || gameMatrixInfos.data[i][j].isOver) ) {
flag = false;
break;
}
}
if ( flag ) {
verticalArray.push(i);
}
}
for ( let i = 0; i < cols; i++ ) {
let flag = true;
for ( let j = 0; j < rows; j++ ) {
if ( !(gameMatrixInfos.data[j][i].isFilled || gameMatrixInfos.data[j][i].isOver) ) {
flag = false;
break;
}
}
if ( flag ) {
horizontalArray.push(i);
}
}
for ( let i = 0; i < rows; i += 3 ) {
for ( let j = 0; j < cols; j += 3 ) {
let flag = true;
for ( let t = 0; t < 3; t++ ) {
for ( let u = 0; u < 3; u++ ) {
if ( !(gameMatrixInfos.data[i+t][j+u].isFilled || gameMatrixInfos.data[i+t][j+u].isOver) ) {
flag = false;
break;
}
}
if ( !flag )
break;
}
if ( flag ) {
for ( let t = 0; t < 3; t++ ) {
for ( let u = 0; u < 3; u++ ) {
gameMatrixInfos.data[i+t][j+u].isOver = true;
}
}
}
}
}
for ( let i = 0; i < verticalArray.length; i++ ) {
for (let j = 0; j < cols; j++ ) {
gameMatrixInfos.data[verticalArray[i]][j].isOver = true;
}
}
for ( let i = 0; i < horizontalArray.length; i++ ) {
for (let j = 0; j < rows; j++ ) {
gameMatrixInfos.data[j][horizontalArray[i]].isOver = true;
}
}
drawBaseCanvasUI();
}
function resetMouseOverFlag() {
let rows = gameMatrixInfos.data.length;
let cols = gameMatrixInfos.data[0].length;
for ( let i = 0; i < rows; i++ ) {
for ( let j = 0; j < cols; j++ ) {
gameMatrixInfos.data[i][j].isOver = false;
}
}
}
function drawMovingSelectCells() {
var canvas = canvasInfos.activeCanvas.canvas;
var ctx = canvasInfos.activeCanvas.ctx;
ctx.clearRect(0,0,canvas.width, canvas.height);
ctx.save();
var strokeStyle = "white";
var fillStyle = "#000080";
for ( let i = 0; i < gameSelectedInfos.data.length; i++ ) {
if ( gameSelectedInfos.data[i].isUsed ) {
continue;
}
if ( gameSelectedInfos.data[i].selected ) {
let gapX = gameSelectedInfos.data[i].moveStartX - gameSelectedInfos.data[i].movedX;
let gapY = gameSelectedInfos.data[i].moveStartY - gameSelectedInfos.data[i].movedY;
let msx = gameSelectedInfos.data[i].x-gapX;
let msy = gameSelectedInfos.data[i].y-gapY;
drawCells(ctx,msx,msy, gameSelectedInfos.data[i].w, gameSelectedInfos.data[i].typeArray, strokeStyle, fillStyle);
} else {
drawCells(ctx,gameSelectedInfos.data[i].x,gameSelectedInfos.data[i].y, gameSelectedInfos.data[i].w, gameSelectedInfos.data[i].typeArray, strokeStyle, fillStyle);
}
}
ctx.restore();
}
정리
- Html Mouse Event 로 down, move, up 을 사용 하였습니다.
down 에서 사용자 선택 초기 위치 등의 정보를 담고 있습니다.
move 에서 사용자 움직임에 따른 Game Hint 가 구성됩니다.
up 에서 점수 획득에 관련한 부분과, 최종적인 UI 구성이 완료 됩니다.
- touchstart 등의 mobile 환경은 고려 하지 않았습니다.
UI 부터 더 적게 구성하고 진행하여야 해서 적용하지 않습니다.
- 한계
기본적인 로직만을 보기 위한 부분이라서 많은 부분은 생략 하였습니다.
event 를 mouse position 으로 할 때는 사이즈 변경등에 더 고려할 내용이 많으나, 일단 모두 무시 하였습니다.
Mobile 환경은 고려 하지 않았습니다. ^^