17 분 소요

7

Start버튼을 누르면 로딩화면이 2.5초간 나타난 후 선택된 곡을 연주할 수 있다. 이전에 버튼들과 마찬가지로, 마우스가 Start버튼에 위치하면 버튼 크기가 작아지고, 커서가 손가락 모양으로 변한다.

Menu17.js
	song_list = [],
	...
	song_list = 
	[
        "Beethoven Virus(Easy)",
        "Beethoven Virus(Normal)",
        "Fantaisie(Easy)",
        "Fantaisie(Normal)",
        "Piano Conceto1(Easy)",
        "Piano Conceto1(Normal)",
        "Swan:Black(Easy)",
        "Swan:Black(Normal)",
        "Swan:White(Easy)",
        "Swan:White(Normal)",
        "The BumbleBee(Easy)",
        "The BumbleBee(Normal)",
        "Toccata and Fuga(Easy)",
        "Toccata and Fuga(Normal)"
    	];	
	...
	//캔버스에서 마우스 누르는것 감지
	canvas.addEventListener('mousedown', function(e) 
	{
		X_loc=e.offsetX;
		Y_loc=e.offsetY;	
			
		if(cur_status == 0)
		{
			...			
			//start버튼
			if(260 <= X_loc 
			  && X_loc <=500)
			{
				if(700 <= Y_loc && Y_loc <= 800)
				{
					context.drawImage(start_button,260,700,240,100);	
					
					//연주할 곡에 관한 정보를 담고있는 js파일의 경로를 module_path에 지정해 준다.
					module_path = './songs_info/'+ song_list[cur_num+(cur_page-1)*8] +'.js';
					cur_status = 1;					

					pic_path = pic_list[cur_num+(cur_page-1)*8];
					pic.src = pic_path;				
					pic.onload = function() 
					{
						context.drawImage(pic,0, 0, 565, 810);	
						loading_sound.play();						
					}	
                    
					setTimeout(
						function () 
						{ 
							playing();
						}, 
					2500);							
				}
			}	
		}	
    		...
	}

로딩화면

8

Menu17.js
	...
	image = new Image(),
	image.src = 'shared/images/Frame7.png';
	...
	function playing()
	{
		//플레이시 게임화면을 보여준다.
		context.drawImage(image,0,0);
		
        //score 및 max_combo콤보의 변화시 가릴 초기 이미지를 저장해둔다.
       	max_combo_Default_img = context.getImageData(91,733,182,15);
		score_Default_img = context.getImageData(141,750,132,15);	
	...									
	}
	...

9

buttom_line_drawer함수를 이용해 하단에 검붉은 판정바를 그려준다.

Menu17.js
	...
	function playing()
	{
		...	
        buttom_line_drawer();	
	}
	
	//하단의 판정바를 그려준다.
	function buttom_line_drawer() 
	{
		context.save();
		context.fillStyle='rgba(200,0,0,0.4)';
		context.fillRect(30,context.canvas.height-185,440,20);	
		context.restore();			
	}		
	...

10

배경이미지와 라이프바를 그려준다.

Menu17.js
	...
	background = new Image(),
        life_bar = new Image(),
        life_bar_Default_img = new Image(),
        life_bar_percentage = 0.7,
        life_bar_width = 37,
        life_bar_height = 392,

        life_bar_blank_x_loc = 495,
        life_bar_blank_y_loc = 250,		
        life_bar_blank_width = 40,
        life_bar_blank_height = 380,	
        life_bar_down,
	life_bar_edit,
		
        life_bar.src = 'shared/images/life_bar.png'; 
	...
	function playing()
        {		
            //module_path에 저장된 곡에 대한 정보들을 가져온다.
            import(module_path)
            .then( (module) => 
            {
                ...			
                background.src = module.background_src;
                ...		
            }); 			
            background.onload = function() 
            {

                ...		
                //context.canvas.width-125 = 565-125 = 440
                //context.canvas.height-180 = 810-180 = 630
                context.drawImage(background,30, 0, context.canvas.width-125, context.canvas.height-185);	

                //라이프바 채워지기진 이미지 저장
                life_bar_Default_img = context.getImageData(life_bar_blank_x_loc,life_bar_blank_y_loc,
                                                            life_bar_blank_width,life_bar_blank_height+10);

                //맨처음 실행시 라이프바 그리기(70%)
                life_bar_down = (life_bar_height  * life_bar_percentage); 	//274
                life_bar_edit = life_bar_height -life_bar_down ;		//392-274=117
                context.drawImage(life_bar
                                ,0,life_bar_edit 
                                ,life_bar_width, life_bar_height -life_bar_edit
                                ,life_bar_blank_x_loc, life_bar_blank_y_loc+life_bar_edit
                                ,life_bar_blank_width, life_bar_blank_height-life_bar_edit);
                ...																				
            }
	}

11

Menu17.js
...
b_Default_img = [],
b_Pressed_img = [],
...
function playing()
        {			
            background.onload = function() 
            {		
                ...
                context.save();
                context.fillStyle='rgba(200,0,0,1)';										
                for(var i=0; i<5; i++)
                {				
                    //버튼이 눌리기전 이미지 저장
                    b_Default_img[i] = context.getImageData(x_y[i][0],x_y[i][1]-40,x_y[i][2],x_y[i][3]+40);	

                    //버튼이 눌렸을때 보여줄 이미지 저장	
                    b_Pressed_img[i] = context.getImageData(x_y[i][0],x_y[i][1]+5,x_y[i][2],x_y[i][3]-15);
                }					
                context.restore();

                //노래 제목을 입력한다.							
                context.save();
                context.fillStyle='rgba(255,255,255,1)';	
                context.font = "italic 14px palatino";				
                context.fillText(song_name,396,749);
                context.restore();
                ...
            }
	}

버튼이 눌리기전 이미지 b_Default_img[]에 저장

14

버튼이 눌렸을 때 쓰여질 이미지 b_Pressed_img[]에 저장

15

노래 제목을 보여준다.

16

노래를 재생한다. 16m초마다 각 라인에 맞게 들어갈 노트가 있는지 확인하고, 조건에 맞다면 lines배열에 push함수를 이용해 넣어준다.

Menu17.js
...
function playing()
        {
            background.onload = function() 
            {		
                ...        
                iterable = 0;
                sound_track.play();

                run = setInterval
                (		
                    function()
                    {					
                        //노트를 넣어준다.
                        //63/5 == 126(맨 위에서 판정바까지 노트가 내려가는데 걸리는 프레임)
                        //1000/16 == 62.5(초당 프레임수)
                        //126/62.5 == 악 2초(맨 위에서 판정바까지 노트가 내려가는데 걸리는 시간)					
                        try
                        {
                            temp = Math.round(notes[cur_idx][0] / 16 - 126);	
                            if(temp == iterable)
                            {
                                temp_length = notes[cur_idx].length;
                                
                                //숏노트를 lines배열에 넣어준다.
                                if(temp_length == 1)
                                {
                                    console.log("인덱스 : " + order);
                                    for(var i of locs[cur_idx])
                                    {
                                        lines[i].push([0,15, 0, 1]);								
                                    }								
                                }
				//롱노트를 lines배열에 넣어준다.
                                else
                                {
                                    console.log("인덱스 : " + order);
                                    for(var i of locs[cur_idx])
                                    {
                                        lines[i].push([0,25*notes[cur_idx][1],0,notes[cur_idx][1]-1]);								
                                    }								
                                }	
                                cur_idx +=1;	
                                order +=1;							
                            }	
                            effect_drawer(); 																
                        }

                        catch(e)
                        {	
				...												
                        }	
                        iterable +=1;																				
                    },
                16);
		}
        }

16ms마다 effect_drawer함수를 실행되고, 키들(s,d,g,j,k)을 눌렀을 때 화면의 변화를 보여준다.

Menu17.js
	...
	hitting_effect = [],
	gradation_img = [],
	judges = [],
	numbers = [];
	cur_judges = [-1,-1,-1,-1,-1],	
	keys_pressed = [false,false,false,false,false],
	...
	for(var i=0; i<10; i++)
	{
		if(i<5)
		{
			hitting_effect[i] = new Image();
			hitting_effect[i].src = 'shared/images/hitting_effect'+(i+1)+'.png';	
			
			gradation_img[i] = new Image();
			gradation_img[i].src = 'shared/images/'+(i+1)+'.png';		
			
			//판정 나타내는 그림 가져오기
			if(i<4)
			{
				judges[i] = new Image();	
				judges[i].src = 'shared/images/judge_'+(i)+'.png';					
			}
	
		}
		numbers[i] = new Image();		
		numbers[i].src = 'shared/images/num_'+(i)+'.png';	
	}	
	...
	function effect_drawer() 
	{		
		//배경이미지 초기화
		//context.canvas.width-125 = 565-125 = 440
		//context.canvas.height-180 = 810-180 = 630
		context.drawImage(background,30,0,context.canvas.width-125,context.canvas.height-185);
		buttom_line_drawer();			
			
		for(var i=0; i<5; i++)
		{			
			if(keys_pressed[i])
			{
				//버튼이 눌렸을때 이미지를 위에 그린다.
				context.save();
				context.fillStyle='rgba(0,0,0,1)';	
				context.fillRect(x_y[i][0],x_y[i][1],x_y[i][2],20);	
				context.restore();	
				context.putImageData(b_Pressed_img[i],x_y[i][0],x_y[i][1]+15);					
				
				//그라데이션 그리기
				context.save();
				context.globalAlpha=0.6;							
				context.drawImage(gradation_img[i],x_y[i][0],0,x_y[i][2],context.canvas.height-185);		
				context.restore();	
				...																							
			}			
			
			else
			{	
				//버튼이 안눌리면 어둡게, 눌리면 밝은 빨간색으로 판정라인이 보이게 되는것은
				//바로 아랫줄의 b_Default_img[i]와 buttom_line_drawer();의
				//상호 작용의 결과이다.
				context.putImageData(b_Default_img[i],x_y[i][0],x_y[i][1]-40);		
				itrs[i] = 0;					
			}			
			...
		}
	}	
	
	...
	/*
	키코드값
		...
		s => 83
		d => 68
		g => 71
		j => 74
		k => 75
		...
	참고 : keypress이벤트의 경우 keydown,keyup이벤트와 달리 ASCII코드를 받는다.		
	*/
	k_code = [83,68,71,74,75],    
	
	//키를 때는것 감지
	window.addEventListener('keyup', function(e) 
	{
		if(-1<k_code.indexOf(e.keyCode) && k_code.indexOf(e.keyCode)<5)
		{	
			keys_pressed[k_code.indexOf(e.keyCode)] = false;	
			keep_pressing[k_code.indexOf(e.keyCode)] = false;
			hitting[k_code.indexOf(e.keyCode)] = 0;						
		}
 	}); 

키보드서 I를 누른경우

17

16ms마다 effect_drawer함수를 실행되서, playing함수에서 lines에 업력했던 정보를 이용해 노트들을 화면에 보여준다.

Menu17.js
	...
	function effect_drawer() 
	{		
		//배경이미지 초기화
		//context.canvas.width-125 = 565-125 = 440
		//context.canvas.height-180 = 810-180 = 630
		context.drawImage(background,30,0,context.canvas.width-125,context.canvas.height-185);
		buttom_line_drawer();			
			
		for(var i=0; i<5; i++)
		{	
			...		
			for(var j=0; j<lines[i].length; j++)
			{			
				cur_loc = lines[i][j][0];
				leng = lines[i][j][1];
				
				comp = (lines[i][j][2] >= 645) ? 645 : lines[i][j][2];

				//leng값은 안변한다.				
				//숏노트
				if(leng == 15)
				{
					j=single_note(i,j,cur_loc);															
				}
				
				//(25<=leng<645) 
				//롱노트
				else if(leng<645)
				{
					j=under_645(i,j,cur_loc,leng,comp);																			
				}
				
				//(645<=leng)  
				//롱노트
				else
				{
					j=more_645(i,j,cur_loc,leng,comp);														
				}				
			}
			...
		}
	}

lines에 업력된 정보가 숏노트에 해당하는 경우 j=single_note(i,j,cur_loc);가 실행된다. single_note에서는 숏노트를 화면에 보여주고, 조건에 따라 히트처리 여부를 결정한다.

Menu17.js
	...
	pre_pressing = [false,false,false,false,false],
	notes_img = [new Image(),new Image(),new Image(),new Image(),new Image()],
	notes_img[0].src = 'shared/images/note_b.png'; 	
	notes_img[1].src = 'shared/images/note_s.png'; 	
	notes_img[2].src = 'shared/images/note_g.png'; 	
	notes_img[3].src = 'shared/images/note_s.png'; 	
	notes_img[4].src = 'shared/images/note_b.png'; 		
	...
	function single_note(i,j,cur_loc)
	{
		//판정바 밖에 노트가 위치한 경우
		if(cur_loc < 610)
		{
			context.drawImage(notes_img[i],x_y[i][0],cur_loc,x_y[i][2],15);	
			lines[i][j][0] = cur_loc+5;						
		}
		
		//판정바 안에 노트가 위치한 경우		
		else
		{
			//노트가 아래 빨간바에 닿기 전 미리 버튼을 눌렀을때 BAD로 판정되는 것을 막기위한
			//바로 아래의 조건문을 코딩했다.
			if(cur_loc == 610)
			{							
				if(keys_pressed[i])
				{					
					pre_pressing[i] = true; 	
				}
				context.drawImage(notes_img[i],x_y[i][0],cur_loc,x_y[i][2],15);	
				lines[i][j][0]  = cur_loc+5;																			
			}				
			
            /* 작동 예시
            keys_pressed[i]=true, pre_pressing[i]=true	== 계속 버튼 누르고 있다			
                                                            => 히트처리 안한다.
            keys_pressed[i]=true, pre_pressing[i]=false	== 버튼을(눌렀다가 땐후 다시) 눌렀다	
                                                            => 히트처리 한다
            keys_pressed[i]=false, pre_pressing[i]=true	== 버튼 눌렀다가 땟다.			
                                                            => pre_pressing[i]=false로 바꾼다.
            keys_pressed[i]=flase, pre_pressing[i]=false== 버튼 누르지 않았다				
                                                            => 히트처리 안한다.
            */																												
			
			else if(cur_loc < 660)	
			{
				//console.log("cur_loc <= 630 실행");
				//노트가 판정바를 지나가기전 조금 걸쳐있는 상황
				if(keys_pressed[i])
				{
					//keys_pressed[i]=true, pre_pressing[i]=false	
					//== 버튼을(눌렀다가 땐후 다시) 눌렀다 => 히트처리 한다
					if(!pre_pressing[i])
					{
						cur_judge=hitting_judge_s(cur_loc);											
						hitting[i] += 1;	
															
						lines[i].splice(j, 1); 		
						j --;									
					}
					
					//keys_pressed[2]=true, pre_pressing[i]=true	    
					//== 계속 버튼 누르고 있다 => 히트처리 안한다.
					else
					{	
						if(cur_loc < 630)
						{
							context.drawImage(notes_img[i],x_y[i][0],cur_loc,x_y[i][2],15);								
						}	
						else if(cur_loc < 645)
						{
							context.drawImage(notes_img[i],x_y[i][0],cur_loc,x_y[i][2],645-cur_loc);								
						}									
						lines[i][j][0]  = cur_loc+5;													
					}								
				}
				
				//keys_pressed[2]=flase == 버튼이 안눌렸다 => 히트처리 안한다.
				else
				{	
					//keys_pressed[2]=false, pre_pressing[i]=true	
					//== 버튼 눌렀다가 땟다.=> pre_pressing[i]=false로 바꾼다.			
					if(pre_pressing[i])
					{
						pre_pressing[i]=false;										
					}	
										
					if(cur_loc < 630)
					{
						context.drawImage(notes_img[i],x_y[i][0],cur_loc,x_y[i][2],15);								
					}	
					else if(cur_loc < 645)
					{
						context.drawImage(notes_img[i],x_y[i][0],cur_loc,x_y[i][2],645-cur_loc);								
					}	
					lines[i][j][0]  = cur_loc+5;									
				}										
			}
					
			//노트가 판정바를 완전히 지나간 경우
			else
			{
				//console.log("645 < cur_loc 실행");
				//console.log("cur_judge="+ 3);
				cur_judge = 3;	
				subtracting = true;	
				hitting[i] = 0;	
				lines[i].splice(j, 1);
				j --;
				pre_pressing[i]=false;																			
			}	

		}
		return j;			
	}
	...
	
	//숏 노트의cool,good,bad,miss등을 판단
	function hitting_judge_s(iterable)
	{
		if((Math.abs(635 - iterable)/20) <= 0.25)
		{
			cool +=1;
			//console.log("cool = " + cool);			
			score += 100;			
			return 0;					
		}
		else if((Math.abs(635 - iterable)/20) <= 0.75)
		{
			good +=1;
			//console.log("good = " + good);
			score += 70;
			return 1;									
		}
		else if((Math.abs(635 - iterable)/20) <= 1.0)
		{
			bad +=1;
			//console.log("bad = " + bad);
			score += 40;
			return 2;		
		}	
	}		

lines에 업력된 정보가 길이 645px 미만의 노트에 해당하는 경우 j=under_645(i,j,cur_loc,leng,comp);가 실행된다. under_645에서는 롱노트를 화면에 보여주고, 조건에 따라 히트처리 여부를 결정한다.

Menu17.js
	...
	notes_img = [new Image(),new Image(),new Image(),new Image(),new Image()],
	notes_img[0].src = 'shared/images/note_b.png'; 	
	notes_img[1].src = 'shared/images/note_s.png'; 	
	notes_img[2].src = 'shared/images/note_g.png'; 	
	notes_img[3].src = 'shared/images/note_s.png'; 	
	notes_img[4].src = 'shared/images/note_b.png'; 	
    	...
	function under_645(i,j,cur_loc,leng,comp)
	{
		//롱노트가 상하단서 짤린 모습
		if(lines[i][j][2] < leng)
		{
			//롱노트가 상단에서 내려오고 있다(아직 롱노트 전부가 화면에 나오진 않은 상황)
			//아래 if문 실행시 lines[i][j][2]가 leng까지 커짐
			if(cur_loc == 0)
			{
				//롱 노트가 판정바에 닿기전 미리 버튼을 누르고 있었는지 판단
				if(625 == comp)
				{
					if(keys_pressed[i])
					{
						pre_pressing[i] = true;		
					}
														
				}		
				
				//롱 노트의 하단이 판정바의 영역에 들어온 경우 판단
				else if(625 < comp)
				{
					//롱 노트의 하단이 판정바 영역에 들어왔을 때, 버튼이 눌린경우
					hitting_processing_L(i, comp);																		
				}		
								
				context.drawImage(notes_img[i],x_y[i][0],0,x_y[i][2],comp);		
				lines[i][j][2] += 5;																																	
			}
			
			//롱노트가 하단의 판장바 아랫변에 닿은후 점점 사라지는 상황
			//아래 if문 실행시 lines[i][j][2]가 0까지 점점 작아짐
			else
			{
				//롱 노트의 하단이 판정바의 영역에 들어온 경우 판단
			    if(leng-comp < 20)
				{
					hitting_processing_L(i, cur_loc+leng);																		
				}
				
				//롱노트의 하단 부가 15px이상 벗어나가 시작하는 지점
				else
				{
					missing_processing_L(i);																		
				}
				
				context.drawImage(notes_img[i],x_y[i][0],cur_loc,x_y[i][2],comp);	
				lines[i][j][0] += 5;		
				lines[i][j][2] -= 5;	
								
				//하단바를 롱노트가 완전히 지난 경우 노트 관련 정보를 지워준다.
				//if(lines[i][j][0]  == 645)
				if(lines[i][j][2] == 0)
				{
					if(cur_judges[i] == 3)
					{
						subtracting = true;						
					}
					
					lines[i].splice(j, 1); 						
					j--;
						
					pre_pressing[i] = false;	
					keep_pressing[i] = false;													
					hitting[i] = 0;		
					cur_judges[i] = -1;
					once[i] = 1;							
				}
																			
			}
				
		}
		
		else
		{								
			//롱노트가 화면에 완전히 나타난 상황									
			if(cur_loc+leng < 645)
			{		
				//롱 노트가 판정바에 닿기전 미리 버튼을 누르고 있었는지 판단
				if(625 == cur_loc+leng)
				{
					if(keys_pressed[i])
					{
						pre_pressing[i] = true;		
					}
														
				}		
				
				//롱 노트의 하단이 판정바의 영역에 들어온 경우 판단
				else if(625 < cur_loc+leng )
				{
					//롱 노트의 하단이 판정바 영역에 들어왔을 때, 버튼이 눌린경우
					hitting_processing_L(i, cur_loc+leng);																		
				}
				context.drawImage(notes_img[i],x_y[i][0],cur_loc,x_y[i][2],leng);	
				lines[i][j][0] += 5;																						
			}
			
			//롱노트의 하단이 판정바 아랫변에 닿은 상황
			//else if(cur_loc+leng == 645)
			else
			{										
				//comp = (leng >= 645) ? 645 : lines[i][j][2];
				
				//롱 노트의 하단이 판정바 영역에 들어왔을 때, 버튼이 눌린경우
				hitting_processing_L(i, cur_loc+comp);
				context.drawImage(notes_img[i],x_y[i][0],cur_loc,x_y[i][2],comp);										
				lines[i][j][0] = cur_loc+5;		
				lines[i][j][2] = comp-5;																																																																			
			}									
		}
		return j;			
	}	
	...	
	function hitting_processing_L(i, loc)
	{
		//롱 노트의 하단이 판정바 영역에 들어왔을 때, 버튼이 눌린경우
		if(keys_pressed[i])
		{
			if(!pre_pressing[i])
			{
				if(cur_judges[i] ==-1)
				{													
					cur_judges[i] = hitting_judge(loc);	
					keep_pressing[i] = true;
					hitting[i] += lines[i][0][3];			
				}																								
			}
		}
		
		//롱 노트의 하단이 판정바 영역에 들어왔을 때, 버튼이 안눌린경우
		else
		{
			pre_pressing[i] = false;	
			keep_pressing[i] = false;													
		}			
	}	
	
	function missing_processing_L(i)
	{
		if(cur_judges[i] ==-1)
		{
			if(once[i])
			{
				cur_judge = 3;	
				once[i] -=1;							
			}
			cur_judges[i] = 3;	
							
			pre_pressing[i] = false;	
			keep_pressing[i] = false;	
			hitting[i] = 0;														
		}
		else
		{
			if(keep_pressing[i])
			{
				cur_judge = cur_judges[i];													
			}
			else
			{
				if(once[i])
				{
					cur_judge = 3;	
					once[i] -=1;							
				}
				cur_judges[i] = 3;		
								
				pre_pressing[i] = false;	
				keep_pressing[i] = false;	
				hitting[i] = 0;															
			}																								
		}			
	}
	
	//롱노트의 cool,good,bad,miss등을 판단
	function hitting_judge(iterable)
	{
		if((Math.abs(650 - iterable)/20) <= 0.25)
		{
			return 0;					
		}
		else if((Math.abs(650 - iterable)/20) <= 0.75)
		{
			return 1;									
		}
		else if((Math.abs(650 - iterable)/20) <= 1.0)
		{
			return 2;		
		}		
	}    

lines에 업력된 정보가 길이 645px 이상의 노트에 해당하는 경우j=more_645(i,j,cur_loc,leng,comp);가 실행된다. more_645에서는 롱노트를 화면에 보여주고, 조건에 따라 히트처리 여부를 결정한다.

Menu17.js
	...
	notes_img = [new Image(),new Image(),new Image(),new Image(),new Image()],
	notes_img[0].src = 'shared/images/note_b.png'; 	
	notes_img[1].src = 'shared/images/note_s.png'; 	
	notes_img[2].src = 'shared/images/note_g.png'; 	
	notes_img[3].src = 'shared/images/note_s.png'; 	
	notes_img[4].src = 'shared/images/note_b.png'; 	
    	...
	function more_645(i,j,cur_loc,leng,comp)
	{
		//롱노트가 상하단서 짤린 모습
		if(lines[i][j][2] < leng)
		{
			//롱노트가 내려오고 있는 중이다
			if(cur_loc == 0)
			{
				//롱 노트가 판정바에 닿기전 미리 버튼을 누르고 있었는지 판단
				if(lines[i][j][2] < 625)
				{
					
				}
				
				else if(625 == lines[i][j][2])
				{
					if(keys_pressed[i])
					{
						pre_pressing[i] = true;		
					}
														
				}		
				
				//롱 노트의 하단이 판정바의 영역에 들어온 경우 판단
				else if(625 < lines[i][j][2] && lines[i][j][2] < 660)
				{
					//롱 노트의 하단이 판정바 영역에 들어왔을 때, 버튼이 눌린경우
					hitting_processing_L(i, lines[i][j][2]);																		
				}
				
				else
				{
					missing_processing_L(i);				
				}
								
				context.drawImage(notes_img[i],x_y[i][0],0,x_y[i][2],comp);		
				lines[i][j][2] += 5;																																	
			}
			
			//롱노트가 내려오고 점점 사라지는 중이다
			else
			{
				//if(cur_loc+645 < 660)
				if(cur_loc < 20)
				{
					//롱 노트의 하단이 판정바 영역에 들어왔을 때, 버튼이 눌린경우
					hitting_processing_L(i, cur_loc+645);																		
				}
				else
				{
					missing_processing_L(i);																					
				}					
				context.drawImage(notes_img[i],x_y[i][0],cur_loc,x_y[i][2],comp);	
				lines[i][j][2] -= 5;	
				lines[i][j][0] += 5;	
										
				//하단바를 롱노트가 완전히 지난 경우 노트 관련 정보를 지워준다.
				//if(lines[i][j][0]  == 645)
				if(lines[i][j][2] == 0)
				{
					if(cur_judges[i] == 3)
					{
						subtracting = true;						
					}				
					lines[i].splice(j, 1); 	
					j--;		
					
					pre_pressing[i] = false;	
					keep_pressing[i] = false;													
					hitting[i] = 0;		
					cur_judges[i] = -1;
					once[i] = 1;									
				}														
			}
		}
		
		else
		{
			if(lines[i][j][2] < 660)
			{
				//롱 노트의 하단이 판정바 영역에 들어왔을 때, 버튼이 눌린경우
				hitting_processing_L(i, lines[i][j][2]);																		
			}	
			else
			{
				missing_processing_L(i);																						
			}								
			context.drawImage(notes_img[i],x_y[i][0],0,x_y[i][2],645);	
			lines[i][j][2] = 640;	
			lines[i][j][0] += 5;				
		}	
		return j;
	}
	
	function hitting_processing_L(i, loc)
	{
		//롱 노트의 하단이 판정바 영역에 들어왔을 때, 버튼이 눌린경우
		if(keys_pressed[i])
		{
			if(!pre_pressing[i])
			{
				if(cur_judges[i] ==-1)
				{													
					cur_judges[i] = hitting_judge(loc);	
					keep_pressing[i] = true;
					hitting[i] += lines[i][0][3];			
				}																								
			}
		}
		
		//롱 노트의 하단이 판정바 영역에 들어왔을 때, 버튼이 안눌린경우
		else
		{
			pre_pressing[i] = false;	
			keep_pressing[i] = false;													
		}			
	}	
	
	function missing_processing_L(i)
	{
		if(cur_judges[i] ==-1)
		{
			if(once[i])
			{
				cur_judge = 3;	
				once[i] -=1;							
			}
			cur_judges[i] = 3;	
							
			pre_pressing[i] = false;	
			keep_pressing[i] = false;	
			hitting[i] = 0;														
		}
		else
		{
			if(keep_pressing[i])
			{
				cur_judge = cur_judges[i];													
			}
			else
			{
				if(once[i])
				{
					cur_judge = 3;	
					once[i] -=1;							
				}
				cur_judges[i] = 3;		
								
				pre_pressing[i] = false;	
				keep_pressing[i] = false;	
				hitting[i] = 0;															
			}																								
		}			
	}
	
	//롱노트의 cool,good,bad,miss등을 판단
	function hitting_judge(iterable)
	{
		if((Math.abs(650 - iterable)/20) <= 0.25)
		{
			return 0;					
		}
		else if((Math.abs(650 - iterable)/20) <= 0.75)
		{
			return 1;									
		}
		else if((Math.abs(650 - iterable)/20) <= 1.0)
		{
			return 2;		
		}		
	}

18

16ms마다 effect_drawer함수를 실행되서, scroe, maxcombo, life, combo숫자등 에 변동된 상태를 반영해 준다.

Menu17.js
	...
	combo_marker = new Image(),
	combo_marker.src = 'shared/images/combo.png';
	judges = [],
	for(var i=0; i<10; i++)
	{
		if(i<5)
		{
			...
			//판정(cool, good, bad, miss)나타내는 그림 가져오기
			if(i<4)
			{
				judges[i] = new Image();	
				judges[i].src = 'shared/images/judge_'+(i)+'.png';					
			}
	
		}
		
		//콤보 숫자를 나타낼 0~9까지 이미지 가져오기
		numbers[i] = new Image();		
		numbers[i].src = 'shared/images/num_'+(i)+'.png';	
	}	
    	...
    
	function effect_drawer() 
	{		
		for(var i=0; i<5; i++)
		{				
			if(keys_pressed[i])
			{
				...				
				//하단의 판정바에 노트가 있고, 버튼을 눌룬 상태			
				if(hitting[i])
				{										
					itrs[i] +=1;															
					if(itrs[i]==5)
					{		
						hitting[i] -= 1;	
						itrs[i] = 0;							
								
						combo +=1;
						max_combo = (combo>max_combo) ? combo : max_combo;							
						
						//롱노트에서 히트했을 때 처리하기 위한 코딩
						switch(cur_judges[i])
						{
							case 0:								
								cool +=1;
								score += 100;
								//console.log("cool = " + cool);
								break;	
							case 1:								
								good +=1;
								score += 70;
								//console.log("good = " + good);
								break;		
							case 2:
								bad +=1;
								score += 40;
								//console.log("bad = " + bad);
								break;																		
						}
		
						//cool,good,bad등이 뜨면 라이프 게이지 1%씩 증가
						life_bar_percentage = ((life_bar_percentage+0.01)>= 1.0) ? 1.0 
											: life_bar_percentage+0.01;	
						if(life_bar_percentage <= 1.0)
						{
							context.putImageData(life_bar_Default_img,495,250);		
							
							life_bar_down = Math.floor(life_bar_height * life_bar_percentage); 	
							life_bar_edit = life_bar_height -life_bar_down ;			
							context.drawImage(life_bar,0,life_bar_edit ,
											life_bar_width, life_bar_height -life_bar_edit,																life_bar_blank_x_loc,life_bar_blank_y_loc+life_bar_edit,
											life_bar_blank_width,life_bar_blank_height-life_bar_edit);																	
						}		
						
						/*score, max_combo값 보이기위한 코딩*/
						
						//기존에 있던 값들을 지운다.
						context.save();						
						context.fillStyle='rgba(0,0,0,1)';							
						context.fillRect(95,732,178,13);
						context.fillRect(147,750,126,15);
						context.restore();	
	
						//새로운 값들을 입력한다.							
						context.save();
						context.fillStyle='rgba(255,255,255,1)';	
						context.font = "italic 14px palatino";				
						context.fillText(score,95,745);
						context.fillText(max_combo,147,760);	
						context.restore();			
					}
				}																							
			}
            		...
			if(cur_judge == 3)
			{
				combo=0;								
			}		
            		...
			//히트 효과 그리기
			if(hitting[i])
			{
				context.drawImage(hitting_effect[itrs[i]],loc_x[i],context.canvas.height-205,60,60);								
			}	
						
			if(combo > 0)
			{
				//combo란 글자 화면에 그리기
				context.drawImage(combo_marker,220,186);		
	
				//콤보수 화면에 그리기
				combo_count(combo);		
				
				//console.log("cur_judge = " + cur_judge);					
			}		
					
																						
			//cool,good,bad,miss란 글자 그리기	
			if(cur_judge != undefined)
			{				
				context.drawImage(judges[cur_judge],200,400,100,100);					
			}		
							
			if(subtracting)
			{
				//miss뜨면 라이프 게이지 5%씩 감소							
				if(life_bar_percentage-0.05> 0.0)
				{
					context.putImageData(life_bar_Default_img,
										life_bar_blank_x_loc,life_bar_blank_y_loc);	
					life_bar_percentage = (life_bar_percentage-0.05 < 0.0) ? 0.0 
										: life_bar_percentage - 0.05;																								
					life_bar_down = Math.floor(life_bar_height * life_bar_percentage); 	
					life_bar_edit = life_bar_height -life_bar_down ;			
					context.drawImage(life_bar,0,life_bar_edit ,
                                    life_bar_width, life_bar_height -life_bar_edit, 						  									  life_bar_blank_x_loc,life_bar_blank_y_loc+life_bar_edit,
                                    life_bar_blank_width,life_bar_blank_height-life_bar_edit);				
				}
				else			
				{
					//challenge mode
					if(C_P_MODE == 0)					
					{
						...
					}
					
					//Practice mode
					else
					{
						context.putImageData(life_bar_Default_img,
											life_bar_blank_x_loc,life_bar_blank_y_loc);	
					}	
																									
				}														
				subtracting = false;					
			}            
		}
	}

19

메뉴 페이지에서 Challenge Mode버튼을 선택하고 플레이시, 라이프바가 전부 없어진다면 게임오버가 된다. 게임오버 이미지가 위에서 아래로 이동하고, 전체 화면을 꽉체웠다면, 0.5초 뒤에 Retry, Quit버튼이 화면에 나타난다.

Menu17.js
	...
	gameover= new Image(),
	gameover_sound,
	gameover.src = "shared/images/GAMEOVER.png",
	gameover_sound = new Audio();
	gameover_sound.src = 'sound/gameover_audio.mp4'; 
	...
	function effect_drawer() 
	{		
		for(var i=0; i<5; i++)
		{					
			...				
			if(subtracting)
			{
				//miss뜨면 라이프 게이지 5%씩 감소							
				if(life_bar_percentage-0.05> 0.0)
				{
					...			
				}
				else			
				{
					//challenge mode
					if(C_P_MODE == 0)					
					{
					//challenge mode
					if(C_P_MODE == 0)					
					{
						clearInterval(run);		
						sound_track.pause();
						var variant = 1;
						gameover_sound.play();		
												
						//게임오버
						var top_to_bottom = setInterval
						(	
							function()
							{														
								if(variant == 162)
								{
									clearInterval(top_to_bottom);									
									setTimeout(
										function()
										{
											//초기화 작업
											notes =  [];
											locs = [];	
												
											cur_judge = undefined;	
											cur_idx = 0;	
											cur_status = 2;	
											iterable = 0;
											life_bar_percentage = 0.7;
											score = 0;
											cool = 0;
											good = 0;
											bad = 0;
											miss = 0;
											combo = 0;
											max_combo = 0;	

										
											for(var k=0; k<5; k++)
											{
												//lines[k]에 들어있는 배열의 모든 원소를 제거한다.
												lines[k].splice(0)											
											}																																				
											context.drawImage(retry,115,490,329,143);
											context.drawImage(quit,115,640,329,143);											
										}, 
									500);
									
								}				
								//gameover이미지의 가로크기가 555다, 565로 만든줄 알았는데 착각했다.
								context.drawImage(gameover,0,810-variant*5,555,variant*5,0,0,565,variant*5);
								variant +=1;	
							},
						10);
					}
					
					//Practice mode
					else
					{
						...
					}	
																									
				}														
				subtracting = false;					
			}            
		}
	}

20

게임오버가 되었을 때 , Retry를 클릭하면 같은곡을 재도전하게 되고, Quit을 클릭하면 메뉴화면으로 돌아간다.

Menu17.js
	...		
	//canvas안에서 마우스를 움직였을 때 이벤트들이 발생한다.
	canvas.addEventListener("mousemove" , function (e)
	{
		X_loc=e.offsetX;
		Y_loc=e.offsetY;	
		
		context.canvas.style.cursor = "auto";	
		...
		
		//context.canvas.style.cursor = "pointer"; 
		//=> 마우스 커서를 화살표가 아닌 손가락 모양으로 바꾼다.	
		
		...	
		//gameover
		if(cur_status == 2)
		{
			if(115 <= X_loc 
			  && X_loc <=444)
			{
				//retry
				if(490 <= Y_loc 
			 	 && Y_loc <=633)
				{
					context.save();
					context.fillStyle = "black"
					context.fillRect(115,490,329,143);
					context.restore();		
					context.drawImage(retry,120,495,319,133);	
										
					context.canvas.style.cursor = "pointer";		
					prev_button = 5;						
				}
				
				//quit
				else if(640 <= Y_loc 
			 	 && Y_loc <=783)
				{
					context.save();
					context.fillStyle = "black"
					context.fillRect(115,640,329,143);
					context.restore();		
					context.drawImage(quit,120,645,319,133);	
										
					context.canvas.style.cursor = "pointer";	
					prev_button = 6;				
				}
			}								
		}
		...	
	});
	
	...
	//캔버스에서 마우스 누르는것 감지
	canvas.addEventListener('mousedown', function(e) 
	{
		X_loc=e.offsetX;
		Y_loc=e.offsetY;		
		...
		
		//gameover
		if(cur_status == 2)
		{
			if(115 <= X_loc 
			  && X_loc <=444)
			{
				//retry
				if(490 <= Y_loc 
			 	 && Y_loc <=633)
				{
					//console.log("retry 실행");
					context.drawImage(retry,115,490,329,143);	
					module_path = './songs_info/'+ song_list[cur_num+(cur_page-1)*8] +'.js';
					cur_status = 1;
					playing();										
				}
				
				//quit
				else if(640 <= Y_loc 
			 	 && Y_loc <=783)
				{
					//console.log("quit 실행");
					context.drawImage(quit,115,640,329,143);							
					cur_status = 0;	
					total_notes_number = 0;		
					click_sound.play();									
					showing_menu(false);												
				}
			}								
		}
		...						
 	});	

곡을 끝까지 연주하면 결과화면이 나온다(별로 표시된 부분이 실행된 결과). 그리고 게임결과가 오라클에 기록된다.

Menu17.js
	...		
	function playing()
	{
			...
			iterable = 0;
			sound_track.play();
			
			run = setInterval
			(		
				function()
				{					
					//노트를 넣어준다.
					//63/5 == 126(맨 위에서 판정바까지 노트가 내려가는데 걸리는 프레임)
					//1000/16 == 62.5(초당 프레임수)
					//126/62.5 == 악 2초(맨 위에서 판정바까지 노트가 내려가는데 걸리는 시간)					
					try
					{
						temp = Math.round(notes[cur_idx][0] / 16 - 126);	
						...																	
					}
					
					//에러가 발생한다면, 노래가 끝났다는 의미이므로, 인터벌 및 타임아웃 함수를 중지한다.
					catch(e)
					{	
						if(sound_track.ended)
						{						
							 miss = total_notes_number -(cool + good + bad);
★							showing_result();	
							 clearInterval(run);											
						}	
						else
						{
							effect_drawer(); 	
						}																	
					}	
					iterable +=1;																			
				},
			16);																							
	}
Menu17.js
	...	
	function showing_result()
	{			
		if(C_P_MODE == 0)
		{
			//결과를 오라클에 집어넣는다.
			//rythm3.jsp에서 <script type="text/javascript" 
			//src="https://code.jquery.com/jquery-3.6.0.min.js"></script>라
			//코딩했기에 현재의 페이지에서 바로 아작스 사용이 가능하다.
			$.ajax
			({
				type: 'POST',
				url: './Score_record',
				data: 
				{
					id :  id,
					gname : 'rhythm king',
					gname_n : song_name,
					score : score,
					mod : 0,					
				},
				success: function() 
				{
				
				},
				error: function() 
				{
					alert('요청실패');
				}				
			});			
		}		
		...	
	}
Score_record.java
...
package games;
...
@WebServlet("/Score_record")
public class Score_record extends HttpServlet 
{
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) 
	throws ServletException, IOException 
	{
		actionDo(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) 
	throws ServletException, IOException 
	{
		actionDo(request, response);
	}

	private void actionDo(HttpServletRequest request, HttpServletResponse response) 
	throws ServletException, IOException 
	{
		request.setCharacterEncoding("UTF-8");
		response.setContentType("text/html; charset=UTF-8");
			
		HttpSession session	= request.getSession();	
		
		String id = (request.getParameter("id") != null) 
					? request.getParameter("id") : "";
		String gname  = (request.getParameter("gname") != null) 
						? request.getParameter("gname") : "";		
		String gname_n  = (request.getParameter("gname_n") != null) 
				? request.getParameter("gname_n") : "";			
		
		String score_t  = (request.getParameter("score") != null) 
					? request.getParameter("score") : "0";
		int score = Integer.parseInt(score_t);
		int mod  = Integer.parseInt(request.getParameter("mod"));	
		int current_page = (request.getParameter("current_page") == null)?
				1:Integer.parseInt(request.getParameter("current_page"));		
		
		RankingList_S list;
		
		//새로새운 기록 올리기
		if(mod == 0)
		{	
			new Score_record_DAO().score_record_renewal(gname,gname_n,id,score);	
		}

		else 
		{
			...
		}
		session.setAttribute("GNAME_S", gname);	
		
	}

}
Score_record_DAO.java
...
package games;
...

public class Score_record_DAO 
{
	private Connection conn = null;
	private PreparedStatement pstmt = null;
	private ResultSet rs = null;
	static String sql;
	
	static int SCORE;
	static int hour;
	static int min;
	static int sec;
	static int id_rank;	
	
	static RankingList_S result_S;
	public Score_record_DAO() 
	{
		try 
		{
			Class.forName("oracle.jdbc.driver.OracleDriver");
			String url = "jdbc:oracle:thin:@localhost:1521:xe";
			conn = DriverManager.getConnection(url,"koreait","1011");	
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
		}
	}
	
	//String GNAME, String GNAME_N, String ID, int SCORE
	public int score_record_renewal(String GNAME, String GNAME_N, String ID, int SCORE)
	{	
		Timestamp timestamp;
		try 
		{
			String sq1 = "select SCORE from SCORERECORD where ID=? and GNAME=? and GNAME_N=?"; 	
			pstmt = conn.prepareStatement(sq1);
			pstmt.setString(1, ID);
			pstmt.setString(2, GNAME);
			pstmt.setString(3, GNAME_N);
			rs = pstmt.executeQuery();
			
			//이전의 게임기록이 존재하는 경우
			if(rs.next())
			{
				int prev_SCORE_record = rs.getInt("SCORE");
				
				//현재의 기록이 이전의 것보다 더 좋은경우
				if(prev_SCORE_record < SCORE)
				{
					timestamp = new Timestamp(System.currentTimeMillis());
					/*
					System.out.println("timestamp =" + timestamp);
					System.out.println("SCORE =" + SCORE);
					System.out.println("GNAME =" + GNAME);
					System.out.println("ID =" + ID);
					System.out.println("GNAME_N =" + GNAME_N);
					*/
					String sql2 = "update SCORERECORD set SCORE=?, WRITEDATE=? where ID=? 
									and GNAME=? and GNAME_N=?";					
					pstmt = conn.prepareStatement(sql2);
					pstmt.setInt(1, SCORE);
					pstmt.setTimestamp(2,timestamp);					
					pstmt.setString(3, ID);
					pstmt.setString(4, GNAME);		
					pstmt.setString(5, GNAME_N);	
					pstmt.executeUpdate();
				}
			}
			
			//이전의 게임기록이 없는경우
			else
			{		
				String sql3 = "insert into SCORERECORD(ID,GNAME,SCORE,GNAME_N) values(?,?,?,?)";
				pstmt = conn.prepareStatement(sql3);
				pstmt.setString(1, ID);
				pstmt.setString(2, GNAME);
				pstmt.setInt(3, SCORE);
				pstmt.setString(4, GNAME_N);	
				pstmt.executeUpdate();	
			}
			
			conn.close();
			pstmt.close();
			rs.close();
		} 
		catch (SQLException e) 
		{
			e.printStackTrace();
		}
			
		return 0;	
	}
	...
}
Menu17.js
	...	
	function showing_result()
	{			
		if(C_P_MODE == 0)
		{
			...	
		}		
			
		offscreencontext.drawImage(result,0,0,565,810);		 
		offscreencontext.drawImage(pic,160,100,240,215);	
		
		offscreencontext.save();
		offscreencontext.fillStyle='rgba(255,255,255,1)';	
		offscreencontext.font = "normal 25px Elephant";			
		
		//id
		offscreencontext.fillText(id,220,415);
		
		//cool
		offscreencontext.fillText(cool,205,485);
		
		//good
		offscreencontext.fillText(good,440,485);
		
		//bad
		offscreencontext.fillText(bad,205,535);
		
		//miss
		offscreencontext.fillText(miss,440,535);		
		
		//maxcombo
		offscreencontext.fillText(max_combo,305,615);	

		//Score
		offscreencontext.fillText(score,200,667);	
		offscreencontext.restore();			
			
		var variant = 1;
		result_sound.play();			
		var top_to_bottom = setInterval
		(	function()
			{					
				//context.drawImage(gameover,0,ini,565,810);	
				if(variant == 162)
				{
					clearInterval(top_to_bottom);
															
					//초기화 작업
					//notes =  [];
					//locs = [];							
																		
					setTimeout
					(
						function()
						{															
							for(var k=0; k<5; k++)
							{
								//lines[k]에 들어있는 배열의 모든 원소를 제거한다.
								lines[k].splice(0)											
							}			
															
							cur_judge = undefined;	
							cur_idx = 0;	
							cur_status = 3;	
							iterable = 0;
							life_bar_percentage = 0.7;
							score = 0;
							cool = 0;
							good = 0;
							bad = 0;
							miss = 0;
							combo = 0;
							max_combo = 0;		
																												
							context.drawImage(retry,20,720,260,75);	
							context.drawImage(quit,280,720,260,75);						
						}, 
					1500);					
				}										
				context.drawImage(offscreencanvas,0,810-variant*5,565,variant*5,0,0,565,variant*5);			
				variant +=1;	
			},
		10);		
	}

21

게임오버 화면에서와 마찬가지로 , Retry를 클릭하면 같은곡을 재도전하게 되고, Quit을 클릭하면 메뉴화면으로 돌아간다.

Menu17.js
	...		
	//canvas안에서 마우스를 움직였을 때 이벤트들이 발생한다.
	canvas.addEventListener("mousemove" , function (e)
	{
		X_loc=e.offsetX;
		Y_loc=e.offsetY;	
		...
		
		//context.canvas.style.cursor = "pointer"; 
		//=> 마우스 커서를 화살표가 아닌 손가락 모양으로 바꾼다.	
		...
		//result
		if(cur_status == 3)
		{

			if(720 <= Y_loc 
			  && Y_loc <=795)
			{
				//retry
				if(20 <= X_loc 
			 	 && X_loc <=280)
				{
					context.save();
					context.fillStyle = "black"
					context.fillRect(20,720,260,75);
					context.restore();		
					context.drawImage(retry,25,725,250,65);									
					context.canvas.style.cursor = "pointer";
					prev_button = 5;							
				}
				
				//quit
				else if(280 <= X_loc 
			 	 && X_loc <=540)
				{	
					context.save();
					context.fillStyle = "black"
					context.fillRect(280,720,260,75);
					context.restore();		
					context.drawImage(quit,285,725,250,65);						
					context.canvas.style.cursor = "pointer";
					prev_button = 6;									
				}
			}			
		}		
	});

	...
	//캔버스에서 마우스 누르는것 감지
	canvas.addEventListener('mousedown', function(e) 
	{
		X_loc=e.offsetX;
		Y_loc=e.offsetY;		
		...
		
		//result
		if(cur_status == 3)
		{

			if(720 <= Y_loc 
			  && Y_loc <=795)
			{
				//retry
				if(20 <= X_loc 
			 	 && X_loc <=280)
				{
					result_sound.pause();	
					result_sound.load();
					context.drawImage(retry,25,725,250,65);		
					module_path = './songs_info/'+ song_list[cur_num+(cur_page-1)*8] +'.js';
					cur_status = 1;
											
					playing();									
				}
				
				//quit
				else if(280 <= X_loc 
			 	 && X_loc <=540)
				{	
					result_sound.pause();	
					result_sound.load();
					context.drawImage(quit,285,725,250,65);	
					cur_status = 0;		
					total_notes_number = 0;	
					click_sound.play();		
									
					showing_menu(false);													
				}
			}			
		}							
 	});

동작 영상

※전체 코드는 https://github.com/qlqlzh/Storage에서 my_project_mybatis.zip과 my_project_mybatis.z01를 다운받은 후 my_project_mybatis.zip의 압축을 풀으면 볼 수 있다.

댓글남기기