게임만들기(4) - "JAVADOT" 플랫폼게임

2021. 7. 14. 00:56프로그래밍/개인프로젝트

JavaUI를 이용해서 플랫폼게임을 만드는 과정을 기록합니다.


 

 


변경점

1. MVC패턴으로 리팩토링 (맵을 생성하는 LevelData를 클래스로 만들고 상속을 통해 사용할려고)

2. 점프시 player와 block 달라붙는어서 움직이지 않던 문제 해결(block아랫면과 player윗면 접촉시 player에 Y값을 +1)

3. 메인화면에 배경화면, 스타트버튼, 스타트메시지 구현완료

4. 잘못된 함수명 교체 : MainPage(); -> mainPage();

5. 맵생성관련코드 외부클래스로 만들어서 적용시킴

6. jumpCount글씨체, 위치, 색상,크기 변경

 

앞으로할일

여러가지맵만든후 door와 player충돌시 스테이지 넘어가도록 구현

player가 맵밖으로 나가면 level1으로 초기화, 시간제한 초과시 level1으로 초기화

bgm넣기, 효과음넣기,다양한 맵 만들기, 종료화면만들기


전체코드

1. 메인클래스 JAVADOT_Main.java

package JAVADOT_MVC;

import java.io.IOException;

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;

public class JAVADOT_Main extends Application {
	
	Pane root = new Pane();
	
	//introMessage출력
	public void introMessage() {
			
		final Text text = new Text(480, 570, "Please press space bar");
		text.setFill(Color.WHITE);
		text.setFont(Font.font("verdana", FontWeight.BOLD, 20));
		root.getChildren().add(text);
		
		EventHandler<ActionEvent> eventHandler = new EventHandler<ActionEvent>() {
			public void handle(ActionEvent e) {
				if (text.getText().length() != 0) {
					text.setText("");
				}else{
					text.setText("Please press space bar");
				}
			}
		};
		Timeline animation = new Timeline(new KeyFrame(Duration.millis(700), eventHandler));
		animation.setCycleCount(Timeline.INDEFINITE);
		animation.play();
	}
//	BGM 출력 (소스있을때 추가)
//	public void startMusic() {
//		Media h = new Media(Paths.get("/Users/coqoa/eclipse-workspace/DOTRESS_Ex1/src/music/BGM.mp3").toUri().toString());
//		MediaPlayer introMusicPlayer = new MediaPlayer(h);
//		introMusicPlayer.play();
//	}
//		
	@Override
	public void start(Stage stage) throws IOException {
		//컨테이너에 FXML파일 등록
		root = FXMLLoader.load(getClass().getResource("view/introPage.fxml")); 
		Scene scene = new Scene(root);
		introMessage();
		stage.setTitle("JAVADOT");
		stage.setScene(scene);
		stage.setResizable(false);
		stage.show(); 
	}
	public static void main(String[] args) {
		launch(args);
	}
}

2. introPage.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.Pane?>

<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="720.0" prefWidth="1280.0" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="JAVADOT_MVC.JAVADOT_Controller">
   <children>
      <ImageView fitHeight="720.0" fitWidth="1281.0" layoutX="35.0" pickOnBounds="true" preserveRatio="true" scaleX="1.2">
         <image>
            <Image url="@../../../../../Downloads/자바닷플랫포머%20소스파일/스크린샷%202021-07-11%20오후%205.12.06.png" />
         </image></ImageView>
      <Button layoutX="601.0" layoutY="351.0" mnemonicParsing="false" onAction="#gameStartButton" opacity="0.0" text="Button" />
   </children>
</Pane>

fxml은 SceneBuilder로 작업

1. 왼쪽에 Controller class에 JAVADOT_MVC.JAVADOT_Controller 할당

2. Button의 onAction값으로 gameStartButton 할당하고 setOpacity = 0

 

3. ObjectData1.java

4. JAVADOT_Controller.java

package JAVADOT_MVC;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

import javafx.animation.AnimationTimer;
import javafx.event.ActionEvent;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;

public class JAVADOT_Controller {
	public Stage stage;
	public Scene scene;
	public Node player;
	public Pane mainContainer = new Pane();
	public HashMap<KeyCode, Boolean> keys = new HashMap<KeyCode, Boolean>();
	public Point2D playerVelocity = new Point2D(0, 0);
	public boolean canJump = true;
	
    //LevelData클래스 사용하기 위한 level객체 생성
	LevelData level = new LevelData(); 
	
	///점프관련 변수, 메서드
	int jumpNumber;
	Label jumpCount = new Label();
	Button jumpCountButton = new Button();
	
	public void jumpData() {
		
		jumpNumber = 1; //점프횟수 기본값
		jumpCount.setText(""+jumpNumber); //화면에 출력
		jumpCount.setTranslateX(630);
		jumpCount.setTranslateY(150);
		jumpCount.setFont(Font.font("verdana", FontWeight.BOLD, 20));
		jumpCount.setTextFill(Color.LIGHTGOLDENRODYELLOW);
		jumpCountButton.setTranslateX(10);
		jumpCountButton.setOpacity(0);
		jumpCountButton.setOnKeyPressed(e->{
			if (e.getCode() == KeyCode.SPACE && jumpNumber > 0 ) {
				jumpNumber--;
				jumpCount.setText(""+jumpNumber);
				jumpPlayer();
			}
		});
	}
    
	public void jumpPlayer() {
		if (canJump) {
			playerVelocity = playerVelocity.add(0, -35);
			canJump = false;
		}
	}
	
	public void mainPage() {
    	//게임프레임, 배경화면 색깔
		Rectangle bg = new Rectangle(1280, 720, Color.LIGHTSKYBLUE);
		
        //jumpCount, jumpCountButton
		jumpData(); 
			
		//createObject (blockContainer)
		player = level.createObject(0, 600, 20, 20, Color.DODGERBLUE); 
		
        //맵을 만드는 LevelData클래스의 객체 level생성
        level.levelData(); 
		
		mainContainer.getChildren().addAll(bg, level.blockContainer, jumpCount, jumpCountButton);
	}
	
	public boolean isPressed(KeyCode key) {
		//key가 존재하면 key값을 반환하고 존재하지않으면 디폴트값인 false를 반환		
        return keys.getOrDefault(key, false);
	}
	//AnimationTimer로 매번 업데이트
	public void sceneUpdate() { 
		//LEFT키를 누르고 player객체의 x값이 0보다 크다면 movePlayerX의 매개변수로 -6를 입력		
		if (isPressed(KeyCode.LEFT) && player.getTranslateX() > 0) { 
			movePlayerX(-6);
		}
        //RIGHT키를 누르고 player객체의 오른쪽 경계가 맵의 넓이보다 작다면 movePlayerX의 매개변수로 6를 입력
		if (isPressed(KeyCode.RIGHT) && player.getTranslateX() + 20 < level.levelWidth) { 
			movePlayerX(6);	
		}
        //playerVelocity의 y값이 10보다 작으면 y값 2씩추가(체공시간, 점프높이 담당)
		if (playerVelocity.getY() < 10) { 
			playerVelocity = playerVelocity.add(0, 2);	
		}
        //movePlayerY(int value)에 playerVelocity값 할당
		movePlayerY((int)playerVelocity.getY()); 
		//낙하시 사망구현코드 (level1으로 돌아가도록 구현할 예정)
		if (player.getTranslateY()>720)  {
			System.out.println("die");
		}
        //player의 위치 : 640 ~ (전체화면 - 640)사이일때
		//blockContainer의 X값 이동(-(player의X값-640만))
		if (player.getTranslateX() > 640 && player.getTranslateX() < level.levelWidth-640 ) {
			level.blockContainer.setLayoutX(-(player.getTranslateX()-640));
			}
		}
	
	public void movePlayerX(int value) {
     //LEFT=false, RIGHT=true
	 boolean movingRight = value > 0; 
		
	  for (int i = 0; i < Math.abs(value); i++) {
       //LevelData클래스의 blocks ArrayList에 넣어둔 block를 하나씩 실행
	   for (Node block : level.blocks) { 
	 	 if (player.getBoundsInParent().intersects(block.getBoundsInParent())) {
		  if (movingRight) {	//RIGHT
           // RIGHT입력시 player의 오른쪽경계와 block의 왼쪽경계가 맞닿았을때
		   if (player.getTranslateX() + 20 == block.getTranslateX()) { 
            // player의 y값+10이 block의 y값보다 작다면 y값을 -10해준다
			if(player.getTranslateY()+10 < block.getTranslateY()) { 
			  player.setTranslateY(player.getTranslateY()-10);
			}
			return;
		   }
		  } else  {	//LEFT
           // LEFT입력시 player의 왼쪽경계와 block의 오른쪽 경계가 맞닿았을때
		   if (player.getTranslateX() == block.getTranslateX() + 10) { 
			// player의 y값+10이 block의 y값보다 작다면 y값을 -10해준다
            if(player.getTranslateY()+10 < block.getTranslateY()) {
			 player.setTranslateY(player.getTranslateY()-10);
			}
		   return;
		  }
		 }
		}
	   }
       // RIGHT버튼을 누르면 player객체의 x위치를 +1만큼씩, LEFT버튼을 누르면 x위치를 -1만큼씩이
	   player.setTranslateX(player.getTranslateX()+(movingRight ? 1 : -1)); 
	  }
      
      // energy를 먹으면 jumpNumber를 1 증가시키고 jumpCount에 출력한
	  // energy는 충돌시 Y값을 +500만큼 증가시켜서 화면에서 제외시킨다 
	  for (Node energy : level.energys) { // player와 energy 충돌구현
		if (player.getBoundsInParent().intersects(energy.getBoundsInParent())) {
		 jumpNumber = jumpNumber+1;
		  jumpCount.setText(""+jumpNumber);
		  energy.setTranslateY(energy.getTranslateY()+500);
		 }
	   }
	   for (Node door : level.doors) { // player와 door 충돌구현, 일단은 충돌시 꺼지게 해놨음
		if (player.getBoundsInParent().intersects(door.getBoundsInParent())) {
		 System.out.println("door touch");
		 stage.close();
		}
	   }
	  }
      
	public void movePlayerY(int value) {
	 boolean movingDown = value > 0;
				
	  for (int i = 0; i < Math.abs(value); i++) {
	   for (Node block : level.blocks) {
		//player와 block비교
        if (player.getBoundsInParent().intersects(block.getBoundsInParent())) { 
		 if (movingDown) {
		  // player의 바닥변과 block의 윗면이 충돌하면
          if (player.getTranslateY() + 20 == block.getTranslateY()) { 
		    // player객체의 y값을 -1해주고 점프버튼이 작동하도록 해줌
            player.setTranslateY(player.getTranslateY() - 1);		
		    canJump=true; 
		    return;
		  }
		 } else {
         // 점프시 윗벽이 막혀있으면 더 안올라가짐
		 if (player.getTranslateY() == block.getTranslateY() + 10) { 
		  //윗벽에 막혔을 시 Y값을 +1해준다(붙어있으면 안움직임) 
          player.setTranslateY(player.getTranslateY() + 1); 
		  // 윗벽에 붙었을때 점프버튼 작동x
          canJump = false; 
		  return;
		 }
		}
	   }
	  }
      //player객체의 위치 = player객체의 이동좌표 + movingDown이 참이라면 +1 거짓이면 -1
	  player.setTranslateY(player.getTranslateY()+(movingDown ? 1 : -1)); 
	}
	
	public void gameStartButton(ActionEvent event) throws IOException {
		
		mainPage();
		stage = (Stage)((Node)event.getSource()).getScene().getWindow();
		scene = new Scene(mainContainer);
		stage.setTitle("JAVADOT");
		stage.setScene(scene);
		stage.show();
		
		scene.setOnKeyPressed(e -> keys.put(e.getCode(), true));
		scene.setOnKeyReleased(e -> keys.put(e.getCode(), false));
		
		
		
		AnimationTimer timer = new AnimationTimer() {
			@Override
			public void handle(long now) {
				sceneUpdate();
			}
		};
		timer.start();
	}
}

class LevelData { //객체생성관련 코드모음 (player, block, energy, door, 등등)
	public Pane blockContainer = new Pane();
	public int levelWidth;
	public ArrayList<Node> blocks = new ArrayList<Node>();
	public ArrayList<Node> energys = new ArrayList<Node>();
	public ArrayList<Node> doors = new ArrayList<Node>();
	public ArrayList<Node> bgObject = new ArrayList<Node>(); 
	
	public Node createObject(int x, int y, int w, int h, Color color) {
		Rectangle object = new Rectangle(w, h);
		object.setTranslateX(x);
		object.setTranslateY(y);
		object.setFill(color);
		blockContainer.getChildren().addAll(object);
		return object;
	}
	public void levelData() {
		
		levelWidth = ObjectData1.LEVEL1[0].length() * 10; 
		
		for (int i = 0; i < ObjectData1.LEVEL1.length; i++) {
		 String line = ObjectData1.LEVEL1[i];
		  for (int j = 0; j < line.length(); j++) {
			switch (line.charAt(j)) {
			case '0':
				break;
			case '1':
			Node block = createObject(j*10, i*10, 10, 10, Color.LIGHTGREEN);
				blocks.add(block);
				break;
			case '2':
				Node energy = createObject(j*10, i*10, 5, 5, Color.ORANGE);
				energys.add(energy);
				break;
			case '3':
				Node door = createObject(j*10, i*10, 10, 10, Color.BLACK);
				doors.add(door);
				break;
			case '4':
				Node sunR = createObject(j*10, i*10, 10, 10, Color.TOMATO);
				bgObject.add(sunR);
				break;
			case '5':
				Node sunY = createObject(j*10, i*10, 10, 10, Color.YELLOW);
				bgObject.add(sunY);
				break;
			case '6':
				Node cloud = createObject(j*10, i*10, 10, 10, Color.WHITE);
				bgObject.add(cloud);
				break;	
	    }
	   }
	  }
	 }
	}