[Java] Java로 스타크래프트 게임 구현하기

JTI·2022년 11월 15일
0

📌 [Java] Project

목록 보기
1/3

질럿, 뮤탈리스크, 메딕 총 3유닛을 가지고 계층구조와 관계를 생각하여 스타크래프를 만들어보자.

🎮 스타크래프트 UML

❗️ 기초 프로젝트로 간단하게 만든 것으로 정확하지 않음 ❗️

  1. 질럿, 뮤탈리스크, 메딕은 유닛에 속하고 또한 지상유닛과 공중유닛으로 나눠진다. 공중유닛과 지상유닛은 유닛에 상속받고 뮤탈은 공중유닛에 상속받고 질럿과 메딕은 지상유닛으로 상속받는다.

  2. hp(체력), def(방어력)은 한꺼번에 묶어서 가져오기 위해 따로 빼서 유닛에
    has-a관계로 넣어주었다. 프로토스만의 갖고있는 쉴드 값도 따로 빼서 BaseStat에 상속받는다. Point(위치 좌표값)도 마찬가지.

  3. 공격기능은 코드상에서 중복이 되기 때문에 따로 빼서 각 유닛에 has-a 관계로 넣어주었다.

  4. 메딕에는 힐(체력을 채워주는 스킬)을 할 수 있는 능력이 있다. 힐을 받을 수 있는 대상은 지상유닛(다 는 아니고)으로 IHealble 이라는 interface를 만들어 질럿과 메딕을 각각 넣어주었다.

👾 code

//힐 가능
interface Healable {
	BaseStat getBaseStat();
}
//기초 스탯
class BaseStat {
	public static final int MIN_HP = 0; // 공격받을 때 hp 음수 방지
	public final int MAX_HP; // 힐받을 때 풀피 지정
	private int hp;
	private int def;
	
	public BaseStat(int hp, int def) {
		MAX_HP = hp;
		setHp(hp);
		setDef(def);
	}
	public void incrementHp() {
		hp++;
	}
	public int getHp() {
		return hp;
	}
	public void setHp(int hp) {
		if(hp <= MIN_HP) {
			hp = MIN_HP;
		}
		if(hp > MAX_HP) {
			hp = MAX_HP;
		}
		this.hp = hp;
	}
	public int getDef() {
		return def;
	}
	public void setDef(int def) {
		this.def = def;
	}
	@Override
	public String toString() {
		return "(hp: " + hp + " / def: " + def + ")";
	}
}
//쉴드 스탯 추가
class ShieldStat extends BaseStat {
	private int shield;

	public ShieldStat(int hp, int def, int shield) {
		super(hp, def);
		setShield(shield);
	}
	@Override
	public String toString() {
		return super.toString() + " + (shield: " + shield + ")";
	}
	public int getShield() {
		return shield;
	}
	public void setShield(int shield) {
		this.shield = shield;
	}
	
}
//위치좌표
class Point {
	private int moveX;
	private int moveY;
	
	public Point(int moveX, int moveY) {
		setMoveX(moveX);
		setMoveY(moveY);
	}
	public int getMoveX() {
		return moveX;
	}
	public void setMoveX(int moveX) {
		this.moveX = moveX;
	}
	public int getMoveY() {
		return moveY;
	}
	public void setMoveY(int moveY) {
		this.moveY = moveY;
	}
}
//공중공격===============================================================================
class AirAttack {
	private int airPower;
	
	public AirAttack(int airPower) {
		setAirPower(airPower);
	}
	
	public void airAttack(AirUnit airTarget) {
		if(airTarget.getHp() == 0) {
			System.out.println("적이 이미 죽어있습니다.");
		} else {
			if((int)(Math.random() * 5) + 1 != 1) { //80% 확률 4/5확률로 공격
				System.out.println("공격시작!");
				System.out.println(airTarget.getName() + "에게 " + airPower + "의 데미지를 입혔다!");
				airTarget.setHp(airTarget.getHp() - (airPower - airTarget.getBaseStat().getDef()));
				System.out.println(airTarget.getName() + "(hp: " + airTarget.getHp() + ")");
			} else {
				System.out.println("앗! 공격 MISS!!");
			} 
			if(airTarget.getHp() == 0) {
				System.out.println(airTarget.getName() + "을 물리쳤다 !");
			}
		}
	}
	public int getAirPower() {
		return airPower;
	}
	public void setAirPower(int airPower) {
		this.airPower = airPower;
	}
	
}
class GroundAttack {
	private int groundPower;
	
	public GroundAttack(int groundPower) {
		setGroundPower(groundPower);
	}
	
	public void groundAttack(GroundUnit groundTarget) {
		if(groundTarget.getHp() == 0) {
			System.out.println("적이 이미 죽어있습니다.");
		} else {
			if(groundTarget.getTribal() == Unit.PROTOSS) {
				System.out.println("공격시작!");
				if((int)(Math.random() * 5) + 1 != 1) {
					System.out.println(groundTarget.getName() + "에게 " + groundPower + "의 데미지를 입혔다!");
					int remainDamage = groundPower - ((ShieldStat)groundTarget.getBaseStat()).getShield();
					if(remainDamage > 0) {
						((ShieldStat)groundTarget.getBaseStat()).setShield(0);
						remainDamage = remainDamage - groundTarget.getBaseStat().getDef();
						groundTarget.setHp(groundTarget.getHp() - (remainDamage >= 0 ? remainDamage : 0));
					} else {
						((ShieldStat)groundTarget.getBaseStat()).setShield(-remainDamage);
					}
					System.out.println(groundTarget.getName() + "(hp: " + groundTarget.getHp() + " / def: " + groundTarget.getBaseStat().getDef() + " / Shield: " + ((ShieldStat)groundTarget.getBaseStat()).getShield());
				} else {
					System.out.println("앗! 공격 MISS!!");
				}
				if(groundTarget.getHp() == 0) {
					System.out.println(groundTarget.getName() + "을 물리쳤다 !");
				}
			} else {
				if((int)(Math.random() * 5) + 1 != 1) { //80% 확률 4/5확률로 공격
					System.out.println("공격시작!");
					System.out.println(groundTarget.getName() + "에게 " + groundPower + "의 데미지를 입혔다!");
					groundTarget.setHp(groundTarget.getHp() - (groundPower - groundTarget.getBaseStat().getDef()));
					System.out.println(groundTarget.getName() + "(hp: " + groundTarget.getHp() + ")");
				} else {
					System.out.println("앗! 공격 MISS!!");
				} 
				if(groundTarget.getHp() == 0) {
					System.out.println(groundTarget.getName() + "을 물리쳤다 !");
				}
			}
		}
	}
	public int getGroundPower() {
		return groundPower;
	}
	public void setGroundPower(int groundPower) {
		this.groundPower = groundPower;
	}
	
}
//======================================================================================
//전체 유닛
abstract class Unit {
	public static final int ZERG = 1;
	public static final int PROTOSS = 2;
	public static final int TERRAN = 3;
	private String name;
	private double speed;
	private int tribal;
	private BaseStat baseStat;
	private Point location;
	
	public Unit(String name, int tribal, double speed, BaseStat baseStat, Point location) {
		setName(name);
		setSpeed(speed);
		setBasestat(baseStat);
		setLocation(location);
		setTribal(tribal);
	}
	public void goMove(Point location) {
		System.out.println(getName() + "이 이동합니다.");
		this.location.setMoveX(this.location.getMoveX() + location.getMoveX());
		this.location.setMoveY(this.location.getMoveY() + location.getMoveY());
		System.out.println("(" + this.location.getMoveX() + ", " + this.location.getMoveY() + ")");
	}
	//임의의 hp get / set
	public void setHp(int hp) {
		baseStat.setHp(hp);
	}
	public int getHp() {
		return baseStat.getHp();
	}
	@Override
	public String toString() {
		return name + baseStat;
	}
	//getter / setter
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public double getSpeed() {
		return speed;
	}
	public void setSpeed(double speed) {
		this.speed = speed;
	}
	public void setTribal(int tribal) {
		this.tribal = tribal;
	}
	public int getTribal() {
		return tribal;
	}
	public BaseStat getBaseStat() {
		return baseStat;
	}
	public void setBasestat(BaseStat basestat) {
		this.baseStat = basestat;
	}
	public Point getLocation() {
		return location;
	}
	public void setLocation(Point location) {
		this.location = location;
	}
}
//공중유닛
abstract class AirUnit extends Unit {

	public AirUnit(String name, int tribal, double speed, BaseStat baseStat, Point location) {
		super(name, tribal, speed, baseStat, location);
	}
	@Override
	public void goMove(Point location) {
		System.out.println("공중으로 이동중...");
		super.goMove(location);
	}
	@Override
	public String toString() {
		return "(Air)_" + super.toString();
	}
}
//지상유닛
abstract class GroundUnit extends Unit {
	public GroundUnit(String name, int tribal, double speed, BaseStat baseStat, Point location) {
		super(name, tribal, speed, baseStat, location);
	}
	@Override
	public void goMove(Point location) {
		System.out.println("지상으로 이동중...");
		super.goMove(location);
	}
	@Override
	public String toString() {
		return "(Ground)_" + super.toString();
	}
}
//유닛3종류=====================================================================================================

//뮤탈리스크
class Mutalisk extends AirUnit {
	private AirAttack airAttack;
	private GroundAttack groundAttack;
	
	public Mutalisk(String name, Point location) {
		super(name, Unit.ZERG , 50, new BaseStat(120, 20), location);
		airAttack = new AirAttack(50);
		groundAttack = new GroundAttack(16);
		
	}
	public void airAttack(AirUnit airTarget) {
		airAttack.airAttack(airTarget);
	}
	public void groundAttack(GroundUnit groundTarget) {
		groundAttack.groundAttack(groundTarget);
	}
	
	@Override
	public String toString() {
		return "[Mutalisk]" + super.toString();
	}
	
}
//질럿
class Zealot extends GroundUnit implements Healable {
	private GroundAttack groundAttack;
	
	public Zealot(String name, Point location) {
		super(name, Unit.PROTOSS, 50, new ShieldStat(100, 2, 50), location);
	}
	
	//이동속도 빨라지는 스킬
	public void legEngancement() {
		setSpeed(getSpeed() + 10);
		System.out.println("이동속도가 빨라졌습니다.\nspeed: " + this.getSpeed());
	}
	public void groundAttack(GroundUnit groundTarget) {
		groundAttack.groundAttack(groundTarget);
	}
	@Override
	public String toString() {
		return "[Zealot]" + super.toString();
	}
}
//메딕
class Medic extends GroundUnit implements Healable {
	private int mp;
	
	public Medic(String name, Point location) {
		super(name, Unit.TERRAN, 30, new BaseStat(40, 5), location);
		setMp(50);
	}	
	public int getMp() {
		return mp;
	}
	public void setMp(int mp) {
		this.mp = mp;
	}

	//힐 스킬
	public void heal(Healable healable) {
		if(healable.getBaseStat().getHp() > 0) {
			setMp(getMp() - 10);
			while(healable.getBaseStat().getHp() != healable.getBaseStat().MAX_HP) { // 풀피가 아니면 
				healable.getBaseStat().incrementHp(); // 풀피 될때까지 계속 힐해라 
			}
			System.out.println(((GroundUnit)healable).getName() + " 치료 완료!");
		} else {
			System.out.println("이미 죽은 유닛은 치료할 수 없습니다.");
		}
	}
	@Override
	public String toString() {
		return "[Medic]" + super.toString() + " + (mp: " + mp + ")"; 
	}
	
}
//=========================================================================================================

public class Starcraft {
	public static void main(String[] args) {
		Mutalisk m = new Mutalisk("m", new Point(10, 10));
		Mutalisk m2 = new Mutalisk("m2", new Point(10, 10));
		System.out.println(m2);
		while(m2.getHp() != 0) { 
			m.airAttack(m2);
		}
		Zealot z = new Zealot("z", new Point(10, 10));
		System.out.println(z);
		while(((ShieldStat)z.getBaseStat()).getShield() != 0) {
			m.groundAttack(z);
		}
		m.groundAttack(z);
		Medic mm = new Medic("mm", new Point(10, 10));
		mm.heal(z);
		System.out.println(z);
		System.out.println(mm);
	}

}

attack 기능안에 중복인 부분이 많은데 제거하지 못했다 😭
아직 메서드 중복 제거에 대해 조금 미숙한 것 같다.
중복제거하는 연습도 해야겠다.


🤔 느낀점

2주동안 간단 스타크래프트를 만들어봤는데 처음으로 상속관계와 has-a관계 interface이러한 기능들을 어떻게 구현하고 묶어 쓸 것인지 생각해볼 수 있는 좋은 시간이었다.

처음에는 다형성을 배우지 않은 상태에서 만들어보라고 하셔서 정말 관계도 엉망이고 그냥 모든게 엉망진창이었는데 다형성, interface등등을 배우고 나서 정말 딱 한눈에 보기 쉽게 설계할 수 있었던 것 같다. (그래도 아직도 부족 ..)

이번 프로젝트를 통해서 설계와 코드 실력도 쌓을 수 있었고 다른 팀들의 코드도 보면서 "아 이렇게도 할 수 있구나" 하고 다양하고 더 좋은 코드들도 볼 수 있는 좋은 시간이었다.

profile
Fill in my own colorful colors🎨

0개의 댓글