JAVA 实现《warcraft java版》游戏
前言
致敬經典的warcraft,《warcraft java版》是一款即時戰略題材單機游戲,采用魔獸原味風格和機制。收集資源,建造防御工事,消滅所有敵軍。
人類:洛丹倫人類聯盟自獸人首次穿過黑暗之門時便告成立。他們堅韌不拔,勇敢無畏,身穿堅甲,手握利刃,英勇迎敵。
獸人:獸人是一個粗獷而堅韌的種族,他們身穿簡單的皮毛和帶有尖刺的皮甲,以肆意兇狠的戰斗風格而聞名。
用java語言實現,采用了swing技術進行了界面化處理,設計思路用了面向對象思想。
主要需求
1、玩家可以自己選擇陣營:人類(Human)和獸人(Orc)兩個陣營可以挑。
2、主要資源:
- 黃金:黃金在Warcraft 中是主要的資源。黃金被用來建造新的建筑物,訓練單位和研究升級。黃金在中立的建筑物也被用來購買雇傭兵,英雄物品,或啟用特殊的服務。
- 木材:木材和黃金類似,也是主要活動的消耗資源之一。所有種族都使用木材生產戰爭所需的許多不同的結構的武器和機器。
3、建筑系統:
不同建筑的建造成本、時間和目的各不相同。城鎮大廳可以訓練工人和存放資源,有些建筑可以訓練戰斗單位,還有的可以讓玩家完成科技升級或解鎖不同類型的單位。
3、操作系統:
擇和移動:使用鼠標左鍵點擊一個單位或建筑,就可以查看相應的狀態以及可以下達的指令。選擇單位之后,玩家可以通過點擊鼠標右鍵下達移動指令,或者點擊界面底部指令面板上的按鈕(或按下相應的快捷鍵)來指揮該單位。
按住鼠標左鍵并拖拽即可拉出一個矩形的方框,玩家可以通過這種方式選擇多個單位,這也被稱之為“框選”。選擇多個單位之后,玩家可以一次性向所有選中的單位下達指令。玩家還可以按下Tab鍵來循環切換查看各個單位的指令面板。
編隊:選擇多個單位或建筑后,玩家可以按下Ctrl+任意數字鍵,以此將選中的單位編為一隊。編隊之后,玩家只需要按下該數字鍵就可以再次選中相應的編隊。
功能截圖
啟動游戲:
配置:
啟動游戲界面:
游戲主界面:
開始一個新游戲:
開局一個人,開啟種田模式
森林和金礦:
建設防御工事:
屬性:
代碼實現
啟動入口:
public class Main {public static final String PROGRAM = "Warcraft java版";public static final String VERSION = "1.0.0";private static final long serialVersionUID = 1L;private Main() {}public static Initializer initialize(boolean jar) {if (jar) {Media.loadFromJar(Main.class);}Engine.start(PROGRAM, VERSION, "ressources", true, Theme.SYSTEM);return ENGINE.createInitializer(320, 200, 32, 60);}public static void main(String[] args) {boolean jar = false;try {jar = Boolean.parseBoolean(args[0]);} catch (Exception e) {}Launcher launcher = new Launcher(null, initialize(jar));launcher.start();} }ModelAttacker類:
/*** Main abstraction representing any unit with the ability of attacking.*/ public abstract class ModelAttacker extends ModelUnit implements AttackerAbility<Tile, ModelSkill, Attributes> {private final List<AbstractEntry<Tile, ModelSkill, Attributes>> guards;private int orderX, orderY;private boolean assault, riposte;private boolean defend;private long guardTimer;public ModelAttacker(Map map, RessourcesHandler rsch, MediaRessource<BufferedImage> rsc) {super(map, rsch, rsc.file, rsc.ressource);this.guards = new ArrayList<AbstractEntry<Tile, ModelSkill, Attributes>>(1);this.damages.setMin(this.getDataInt("DMG_MIN"));this.damages.setMax(this.getDataInt("DMG_MAX"));this.riposte = true;this.assault = false;this.defend = false;this.orderX = -1;this.orderY = -1;}public void setDamages(int min, int max) {this.damages.setMin(min);this.damages.setMax(max);}@Overridepublic void update(Keyboard keyboard, Mouse mouse, float extrp) {super.update(keyboard, mouse, extrp);this.updateAttack(extrp);if (this.isAlive() && (!this.hasTarget() || this.isMoving())) {// Reset defense state when its overif (this.isDefending() && !this.isAttacking()) {this.defend = false;this.assault = false;}// Check guard area when not defending & attacking or assaultif (!this.isDefending() && (this.assault || (!this.isAttacking() && !this.isMoving()))) {if (Maths.time() - this.guardTimer > 500) {if (this.player instanceof AI) {this.guard();} else {if (!this.isMoving()) {this.guard();}}this.guardTimer = Maths.time();}}}}@Overridepublic boolean assignDestination(int tx, int ty) {boolean found = super.assignDestination(tx, ty);if (this.orderX == -1 && this.assault) {this.orderX = tx;this.orderY = ty;}return found;}public void reAssignDestination() {if (this.orderX != -1 && this.orderY != -1) {this.stopAttack();this.setTarget(null);super.assignDestination(this.orderX, this.orderY);} else {this.stopAttack();this.stopMoves();}}@Overridepublic void stop() {this.stopAttack();super.stop();}protected void guard() {int fov = this.getFieldOfView() - 1;for (int v = this.getYInTile() - fov; v <= this.getYInTile() + fov; v++) {for (int h = this.getXInTile() - fov; h <= this.getXInTile() + fov; h++) {try {int eid = this.map.getRef(v, h);if (eid > 0 && eid != this.id) {AbstractEntry<Tile, ModelSkill, Attributes> e = ModelUnit.get(eid);if (e == null) {e = ModelBuilding.get(eid);}if (e.isAlive() && e.isVisible() && e.getOwnerID() != this.getOwnerID() && e.getOwnerID() > 0 && e.isActive()) {this.guards.add(e);}}} catch (ArrayIndexOutOfBoundsException e) {continue;}}}int min = Integer.MAX_VALUE;AbstractEntry<Tile, ModelSkill, Attributes> closest = null;for (AbstractEntry<Tile, ModelSkill, Attributes> e : this.guards) {int dist = this.getDistance(e);// Priority to unitif (closest instanceof AbstractBuilding && e instanceof AbstractUnit) {min = dist;closest = e;} else if (!(closest instanceof AbstractUnit && e instanceof AbstractBuilding) || closest == null) {if (dist < min) {min = dist;closest = e;}}}this.guards.clear();if (closest != null) {this.guardAction(closest);}}protected void guardAction(AbstractEntry<Tile, ModelSkill, Attributes> e) {// Priority to attacker modelif (this.getTarget() instanceof ModelAttacker && !(e instanceof ModelAttacker)) {return;}this.attack(e);}@Overridepublic void onHit(AbstractEntry<Tile, ModelSkill, Attributes> attacker) {super.onHit(attacker);if (this.isAlive() && this.riposte) {// AI gives priority to unit riposteif (attacker instanceof AbstractUnit && this.getTarget() instanceof AbstractBuilding && this.player instanceof AI) {this.attack(attacker);return;}// Keep closest target onlyboolean closest = false;if (this.hasTarget()) {closest = this.getDistance(attacker) < this.getDistance(this.getTarget());}if ((this.hasTarget() || closest) && this.getOwnerID() != attacker.getOwnerID()) {this.attack(attacker);}}}@Overridepublic void onKilled(AbstractEntry<Tile, ModelSkill, Attributes> attacker) {if (this.assault) {this.reAssignDestination();}}public void setRiposte(boolean state) {this.riposte = state;}public void setAssault(boolean state) {this.assault = state;}public boolean getAssault() {return this.assault;}public void setDefend(boolean state) {this.defend = state;}public boolean isDefending() {return this.defend;}@Overridepublic boolean isPassive() {return super.isPassive() && !this.isAttacking();}public boolean hasTarget() {return this.getTarget() != null;} }ModelUnit類:
public abstract class ModelUnit extends AbstractUnit<Tile, ModelSkill, Attributes> {private static final TreeMap<Integer, ModelUnit> ENTRYS = new TreeMap<Integer, ModelUnit>();public static ModelUnit get(int id) {return ENTRYS.get(id);}public static void clear() {ENTRYS.clear();}public static List<ModelUnit> getByOwner(int ownerID) {List<ModelUnit> list = new ArrayList<ModelUnit>(1);Collection<ModelUnit> c = ENTRYS.values();for (ModelUnit u : c) {if (u.getOwnerID() == ownerID) {list.add(u);}}return list;}private void manage() {ENTRYS.put(this.id, this);}private static final int CORPSE_TIME = 5000;private static final int CORPSE_NUMBER = 3;private static final int CORPSE_OFFSET = 8;private static final Orientation[] orientations = Orientation.values();public final Map map;public final UnitType type;public final Race faction;protected Player player;private boolean isOnScreen;private TiledSprite corpse;private long deadTimer, angleTimer, nextAngleTimer;private boolean dead;private int deadIndex, deadOffset;public ModelUnit(Map map, RessourcesHandler rsch, String data, BufferedImage surface) {super(data, map, surface, new Attributes());this.map = map;this.type = UnitType.valueOf(this.getDataString("TYPE").toUpperCase());this.setFieldOfView(this.getDataInt("FOV"));this.setFrame(this.getDataInt("DEFAULT_FRAME"));this.setSkipLastFrameOnReverse(true);this.faction = Race.valueOf(this.getDataString("FACTION").toLowerCase());this.life.setMax(this.getDataInt("MAX_LIFE"));this.life.set(this.life.getMax());this.addSkill(new Move(0, this));this.addSkill(new Stop(1, this));this.setSpeed(1.5f, 1.5f);this.setLayer(2);this.corpse = Drawable.DRAWABLE.loadTiledSprite(rsch.get("CORPSE").ressource, 32, 32);this.corpse.load(false);this.deadTimer = -1L;this.dead = false;this.deadIndex = 0;if (this.faction == Race.orcs) {this.deadOffset = 8;} else {this.deadOffset = 0;}this.map.fogOfWar.updateEntryFOV(this);this.angleTimer = Maths.time();this.nextAngleTimer = Maths.random(0, 2000) + 5000L;this.manage();}@Overridepublic void place(int tx, int ty) {super.place(tx, ty);this.map.fogOfWar.updateEntryFOV(this);}@Overridepublic void update(Keyboard keyboard, Mouse mouse, float extrp) {int otx = this.getXInTile();int oty = this.getYInTile();super.update(keyboard, mouse, extrp);// Apply mirror depending of the orientationOrientation o = this.getOrientation();if (o.ordinal() > 4) {if (!this.getMirror()) {this.mirror(true);}} else {if (this.getMirror()) {this.mirror(false);}}if (!this.isAlive()) {// Handle dead corps effectif (!this.dead) {if (this.deadTimer == -1L) {this.deadTimer = Maths.time();}if (Maths.time() - this.deadTimer > CORPSE_TIME) {this.setVisibility(false);this.dead = true;this.deadIndex = 0;this.deadTimer = Maths.time();}} else {if (this.deadIndex <= CORPSE_NUMBER && Maths.time() - this.deadTimer > CORPSE_TIME) {this.deadIndex++;this.deadTimer = Maths.time();}}if (this.deadIndex > CORPSE_NUMBER) {this.remove();}} else {// Update fog when unit movedif (otx != this.getXInTile() || oty != this.getYInTile()) {this.map.fogOfWar.updateEntryFOV(this);}// Apply a random angle unit entry is still idle too much timeif (this.isPassive() && Maths.time() - this.angleTimer > this.nextAngleTimer) {this.setAnimation("IDLE");this.setOrientation(orientations[Maths.random(0, orientations.length - 1)]);this.angleTimer = Maths.time();this.nextAngleTimer = Maths.random(0, 2000) + 5000L;}}if (this.animName != null) {CollisionArea area = this.getCollArea(this.animName);this.updateCollision(area.getX(), area.getY(), area.getWidth(), area.getHeight());} else {this.updateCollision(16, 16, 0, 0);}}@Overridepublic void render(Graphics2D g, Camera camera) {super.render(g, camera);if (this.dead && this.deadIndex <= CORPSE_NUMBER) {int o = 0;if (this.getOrientation().ordinal() > 0) {o = 4;}this.corpse.render(g, this.deadIndex + this.deadOffset + o, this.getX() - camera.getX() - CORPSE_OFFSET,this.getY() - camera.getY() - CORPSE_OFFSET);}if (this.getX() >= camera.getX() && this.getX() <= camera.getX() + 320 && this.getY() >= camera.getY() && this.getY() <= camera.getY() + 200) {this.isOnScreen = true;} else {this.isOnScreen = false;}}@Overridepublic void setOwnerID(int id) {super.setOwnerID(id);if (id > 0) {this.player = (Player) AbstractPlayer.get(id);}}public Player player() {return this.player;}@Overridepublic void stop() {super.stop();this.clearIgnoredID();this.angleTimer = Maths.time();}@Overridepublic void onStartMove() {this.setAnimation("MOVE");}@Overridepublic void onMove() {if (!this.animName.equals("MOVE")) {this.setAnimation("MOVE");}}@Overridepublic void onArrived() {this.setAnimation("IDLE");this.angleTimer = Maths.time();}@Overridepublic void onDied() {if (this.getOrientation().ordinal() < 4) {this.setOrientation(Orientation.NORTH);} else {this.setOrientation(Orientation.NORTH_EAST);}this.setAnimation("DIE");this.player.removeUnit(this);if (this.isOnScreen()) {ControlPanel.playSfx(0, this.faction, SFX.die);}}@Overridepublic void onSelection() {ControlPanel.playSfx(this.getOwnerID(), this.faction, SFX.select);}@Overridepublic void onOrderedFail(ModelSkill skill) {}@Overridepublic void onKilled(AbstractEntry<Tile, ModelSkill, Attributes> attacker) {}public boolean isPassive() {return !this.isMoving();}public boolean isOnScreen() {return this.isOnScreen;} }總結
通過此次的《warcraft java版》游戲實現,讓我對swing的相關知識有了進一步的了解,對java這門語言也有了比以前更深刻的認識。
java的一些基本語法,比如數據類型、運算符、程序流程控制和數組等,理解更加透徹。java最核心的核心就是面向對象思想,對于這一個概念,終于悟到了一些。
源碼獲取
可關注博主后,私聊博主免費獲取
需要技術指導,寫項目程序,等更多服務請聯系博主
今天是持續寫作的第 33 / 100 天。
可以關注我,點贊我、評論我、收藏我啦。
總結
以上是生活随笔為你收集整理的JAVA 实现《warcraft java版》游戏的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 工作93:注意数据对应接口位置
- 下一篇: 常微分方程数值解法(2)