前言

致敬经典的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);    }    @Override    public 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 over            if (this.isDefending() && !this.isAttacking()) {                this.defend = false;                this.assault = false;            }            // Check guard area when not defending & attacking or assault            if (!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();                }            }        }    }    @Override    public 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();        }    }    @Override    public 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 unit            if (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 model        if (this.getTarget() instanceof ModelAttacker && !(e instanceof ModelAttacker)) {            return;        }        this.attack(e);    }    @Override    public void onHit(AbstractEntry<Tile, ModelSkill, Attributes> attacker) {        super.onHit(attacker);        if (this.isAlive() && this.riposte) {            // AI gives priority to unit riposte            if (attacker instanceof AbstractUnit && this.getTarget() instanceof AbstractBuilding && this.player instanceof AI) {                this.attack(attacker);                return;            }            // Keep closest target only            boolean closest = false;            if (this.hasTarget()) {                closest = this.getDistance(attacker) < this.getDistance(this.getTarget());            }            if ((this.hasTarget() || closest) && this.getOwnerID() != attacker.getOwnerID()) {                this.attack(attacker);            }        }    }    @Override    public 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;    }    @Override    public 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();    }        @Override    public void place(int tx, int ty) {        super.place(tx, ty);        this.map.fogOfWar.updateEntryFOV(this);    }        @Override    public 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 orientation        Orientation 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 effect            if (!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 moved            if (otx != this.getXInTile() || oty != this.getYInTile()) {                this.map.fogOfWar.updateEntryFOV(this);            }            // Apply a random angle unit entry is still idle too much time            if (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);        }    }        @Override    public 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;        }    }        @Override    public void setOwnerID(int id) {        super.setOwnerID(id);        if (id > 0) {            this.player = (Player) AbstractPlayer.get(id);        }    }        public Player player() {        return this.player;    }        @Override    public void stop() {        super.stop();        this.clearIgnoredID();        this.angleTimer = Maths.time();    }        @Override    public void onStartMove() {        this.setAnimation("MOVE");    }        @Override    public void onMove() {        if (!this.animName.equals("MOVE")) {            this.setAnimation("MOVE");        }    }        @Override    public void onArrived() {        this.setAnimation("IDLE");        this.angleTimer = Maths.time();    }        @Override    public 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);        }    }        @Override    public void onSelection() {        ControlPanel.playSfx(this.getOwnerID(), this.faction, SFX.select);    }        @Override    public void onOrderedFail(ModelSkill skill) {    }        @Override    public 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 天。
可以关注我,点赞我、评论我、收藏我啦。