DIYPlay
블로그

Blockly, pixi.js 사용해서 블록코딩 그림판 만들기 - 2

DIYPlayer

·

1달 전

blockly
블록코딩
그림판
pixi.js

이전 포스팅

블록코딩 콘텐츠 제작을 위한 Blockly 개발 시작하기

Blockly, pixi.js 사용해서 블록코딩 그림판 만들기 - 1

완성된 프로젝트 바로가기

 블록코딩 그림판 결과물 프로젝트

 

 

지난 포스팅에서 블록코딩 그림판 제작을 위한 그림판 프로그램을 만들어 보았습니다. 이번 시간에도 이어서 계속 진행해 보겠습니다,

그림판 만들기 2

그리기 애니메이션 효과 주기

 이전에 만들어진 그림판 프로그램은 그리기 명령 함수 호출 시 해당하는 그림이 바로 그려졌습니다. 명령 호출 순서에 따라 그려지는 모습을 확인할 수 있게 그리기 애니메이션 효과를 만들어 보겠습니다.

 그리기 명령 함수 호출 시 그림을 바로 그리지 않고 배열에 그리기 명령을 순서대로 저장해 놓았다가 실행 함수가 호출 되었을 때 하나하나 수행 되도록 하겠습니다. 이를 위해 명령을 저장할 commands 배열과 실행 함수인 run() 그리고 애니메이션 효과를 업데이트 하기 위한 업데이트 루프도 만들어 보도록 하겠습니다.

 PaintApp 클레스에 다음 코드들을 추가합니다.

//paint/paintApp.js

constructor(parentEl) {
	...
	
	//그리기 명령을 순서대로 저장할 배열
	this.commands = [];
	
	//그리기 애니메이션이 진행 중인 명령
	this.currentCommand = null;
	
	//그리기가 실행되고 있는지 여부
	this.isRun = false;
	
	//pixi.js 에서 업데이트 루프를 만들기 위한 방법	
	//t: 초당 60프레임 기준으로 이전 프레임과의 시간 격차를 나타냄. 1이면 60분의 1초임을 나타냄
	this.app.ticker.add((t)=>{
		//실제 흐른 시간 단위로 변환
		const deltaTime = t / 60;
		
		this.update(deltaTime);		
	});
}

//업데이트 루프
update(dt) {
	if(!this.isRun) {
		return;
	}
}

run() {
	if(this.isRun || !this.commands.length) {
        return;
    }
	this.isRun = true;
}

 기존의 그리기 함수들도 다음과 같이 변경합니다.

drawLine(x1, y1, x2, y2, color = 0x000000, weight = 2) {
    this.commands.push({
        type: 'line',
        x1, y1, x2, y2,
        color, weight,
    })
}

drawRect(left, top, width, height, color = 0x000000, weight = 2) {
	this.commands.push({
        type: 'rect',
        left, top, width, height,
        color, weight
    });
}

drawCircle(x, y, radius, color = 0x000000, weight = 2) {
	this.commands.push({
        type: 'circlr',
        x, y, radius,
        color, weight
    });
}

drawRectFill(left, top, width, height, color = 0x000000) {
	this.commands.push({
        type: 'rectFill',
        left, top, width, height,
        color
    });
}

drawCircleFill(x, y, radius, color = 0x000000) {
	this.commands.push({
        type: 'circleFill',
        x, y, radius,
        color
    });
}

drawPolygon(points, color = 0x000000, weight = 2) {
    if(!points || points.length <= 2) return;

     this.commands.push({
        type: 'polygon',
        points,
        color, weight,
    });
}

drawPolygonFill(points, color = 0x000000) {
    if(!points || points.length <= 2) return;

    this.commands.push({
        type: 'polygonFill',
        points,
        color,
    });
}

그리기 명령이 실행 중일 때 커맨드 배열에서 커맨드를 하나씩 꺼내서 수행 하도록 update함수를 수정합니다.

update(dt) {
    if(!this.isRun) {
        return;
    }
    
    if(!this.currentCommand) {
        if(this.commands.length > 0) {
            const graphics = new PIXI.Gr
            this.container.addChild(grap
            this.currentCommand = {
                command: this.commands.s
                graphics,
                t: 0,
            }
        }
        else {
            this.isRun = false;
            return;
        }
    }
    
    const {t, graphics, command} = this.currentCommand;        
    const {type} = command;
    
    //이전에 그려진 그림을 지움
    graphics.clear();
    
    const progress = Math.min(t, 1);
    
    switch(type) {
        case 'line': {
            break;
        }
        case 'rect': {
            break;
        }
        case 'circle': {
            break;
        }
        case 'polygon': {
            break;
        }
        case 'rectFill': {
            break;
        }
        case 'circleFill': {
            break;
        }
        case 'polygonFill': {
            break;
        }
    }
    
    if(progress >= 1) {
    	this.currentCommand = null;
	}
	else {
    	this.currentCommand.t += dt;
	}        
}

 선 그리기 부터 시작점부터 끝점까기 서서히 그려지도록 애니메이션 효과를 만들어 보겠습니다. progress 가 0 ~ 1 로 증가함에 따라 선도 동일하게 끝점까지 그려집니다.

case 'line': {
    const {x1, y1, x2, y2, color, weight} = this.currentCommand.command;
    
    graphics.lineStyle(weight, color, 1);
    graphics.moveTo(x1, y1);
    graphics.lineTo(
        x1 + (x2 - x1) * progress, 
        y1 + (y2 - y1) * progress
    );
    
    break;
}

사각형 그리기는 기존에 사용하던 drawRect 대신에 moveTolineTo 함수를 사용해서 한 변씩 그려지게 하겠습니다. progress 를 한 변에 4분의 1 인 0.25 만큼 나눠서 그려지게 합니다. 0~0.25 구간에서는 윗 변이 그려지고 0.25~0.5 구간에는 오른쪽 변이 0.5~0.75에서는 아랫쪽 변, 0.75~1 구간에서는 왼쪽변이 그려집니다.

case 'rect':
case 'rectFill': {
    const {left, top, width, height, color, weight} = this.currentCommand.command;
    if(type === 'rectFill') {
        graphics.beginFill(color);
    }
    else {
        graphics.lineStyle(weight, color, 1);
    }
    
    graphics.moveTo(left, top);
    const right = left + width;
    const bottom = top + height;
    
    const t1 = progress > 0.25 ? 1 : progress / 0.25;
    const t2 = progress > 0.5 ? 1 : (progress - 0.25) / 0.25;
    const t3 = progress > 0.75 ? 1 : (progress - 0.5) / 0.25;
    const t4 = progress > 1 ? 1 : (progress - 0.75) / 0.25;
    
    graphics.lineTo(left + width * t1, top);
    if(progress > 0.25) graphics.lineTo(right, top + height * t2);
    if(progress > 0.5) graphics.lineTo(right - width * t3, bottom);
    if(progress > 0.75) graphics.lineTo(left, bottom - height * t4);
    
    break;
}

 원 그리기도 기존에 사용하던 drawCircle 대신 호를 그려주는 arc 함수를 사용합니다.

case 'circle':
case 'circleFill': {
    const {x, y, radius, color, weight} = this.currentCommand.command;
    if(type === 'circleFill') {
        graphics.beginFill(color);
    }
    else {
        graphics.lineStyle(weight, color, 1);
    }
    
    //0도 부터 360 * progress 도 까지 서서시 그림
    graphics.arc(x, y, radius, 0, Math.PI * 2 * progress);
    
    break;
}

다각형 그리기는 사각형을 그리는 형태와 비슷하게 반복문을 사용해서 구현해 줍니다.

case 'polygon': 
case 'polygonFill': {
    const {points, color, weight} = this.currentCommand.command;
    if(type === 'polygonFill') {
        graphics.beginFill(color);
    }
    else {
        graphics.lineStyle(weight, color, 1);
    }
    
    const startPoint = points[0];                
    graphics.moveTo(startPoint.x, startPoint.y);
    
    //한 변에 사용할 퍼센트
    const step = 1 / points.length;
    for (let i = 1; i <= points.length; i++) {
        const crtT = step * ( i - 1 );
        if(progress < crtT) {
            break;
        }
        
        const idx = i % points.length;
        const prevIdx = (i - 1) % points.length;
        const prevPoint = points[prevIdx];
        const nextPoint = points[idx];
        const t1 = (progress - crtT) / step;
        const t2 = Math.min(t1, 1);
        graphics.lineTo(
            prevPoint.x + (nextPoint.x - prevPoint.x) * t2, 
            prevPoint.y + (nextPoint.y - prevPoint.y) * t2);
    }
    break;

paintTest.js 파일에서 run 함수를 추가적으로 호출하여 테스트 해보겠습니다.

...
paintApp.run();

 

 

 블록코딩에서 사용할 그림판 프로그램 제작이 거의 마무리 되었습니다. 지금까지는 그리기 코드를 수정하고 페이지 새로고침을 통해 그림을 갱신하였습니다. 하지만 블록코딩에서 사용할때는 페이지 새로고침 없이 그림판을 클리어하고 다시 그릴 수 있어야 합니다. 이를 위해 PaintApp 클레스에 clear 함수를 추가합니다.

clear() {
    this.commands = [];
    this.currentCommand = null;
    
    //콘테이너에 추가되어 있는 PIXI.Graphics 객체들을 모두 제거함.
    this.container.removeChildren();
}

 

이상으로 그림판 프로그램 제작은 마치겠습니다. 다음 포스팅 부터는 Blockly의 워크스페이스에 그림판을 제어할 수 있는 커스텀 블록을 추가하는 방법과 변환된 코드를 적용하는 방법을 제작해보겠습니다.

 

프로젝트 바로가기

https://diyplay.co.kr/editor/edit/12

이전 글

2D 벡터의 내적과 외적 정리

다음 글

Blockly, pixi.js 사용해서 블록코딩 그림판 만들기 - 1

댓글 0
    서비스 이용약관|개인정보 보호정책

    Copyright © DIYPlay All rights reserved.