질럿, 뮤탈리스크, 메딕 총 3유닛을 가지고 계층구조와 관계를 생각하여 스타크래프를 만들어보자.
❗️ 기초 프로젝트로 간단하게 만든 것으로 정확하지 않음 ❗️
질럿, 뮤탈리스크, 메딕은 유닛에 속하고 또한 지상유닛과 공중유닛으로 나눠진다. 공중유닛과 지상유닛은 유닛에 상속받고 뮤탈은 공중유닛에 상속받고 질럿과 메딕은 지상유닛으로 상속받는다.
hp(체력), def(방어력)은 한꺼번에 묶어서 가져오기 위해 따로 빼서 유닛에
has-a
관계로 넣어주었다. 프로토스만의 갖고있는 쉴드 값도 따로 빼서 BaseStat에 상속받는다. Point(위치 좌표값)도 마찬가지.
공격기능은 코드상에서 중복이 되기 때문에 따로 빼서 각 유닛에 has-a
관계로 넣어주었다.
메딕에는 힐(체력을 채워주는 스킬)을 할 수 있는 능력이 있다. 힐을 받을 수 있는 대상은 지상유닛(다 는 아니고)으로 IHealble 이라는 interface
를 만들어 질럿과 메딕을 각각 넣어주었다.
//힐 가능
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등등을 배우고 나서 정말 딱 한눈에 보기 쉽게 설계할 수 있었던 것 같다. (그래도 아직도 부족 ..)
이번 프로젝트를 통해서 설계와 코드 실력도 쌓을 수 있었고 다른 팀들의 코드도 보면서 "아 이렇게도 할 수 있구나" 하고 다양하고 더 좋은 코드들도 볼 수 있는 좋은 시간이었다.