首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >JavaScript游戏引擎设计

JavaScript游戏引擎设计
EN

Code Review用户
提问于 2013-11-23 22:23:53
回答 2查看 327关注 0票数 9

最近,我删除并重新编写了我的游戏代码,使其面向对象(在它仅仅是函数和大量的全局变量之前)。

在实现更多功能之前,我想做最后一步,以确保在继续之前我已经有了尽可能好的基础。

注意:这个游戏的目的是更好地学习原始的JavaScript方法和设计,所以我不想要任何插件,或者有人告诉我jQuery如何使这更容易;)

如果这个脚本太长,我非常抱歉,如果这是一个不恰当的问题,我将删除这个问题。

index.html

代码语言:javascript
复制
<!DOCTYPE html>
<html>
    <head>
        <title>A Vampire's Hunt</title>
        <link rel="stylesheet" href="vamp.css">
    </head>
    <body>
        <div id="msg" class="msg"></div>
        <div>You have been dead for <span id="counter">0</span> hours..</div>
        <div id="divCycle" class="cycle">It is currently: <span id="cycle"></span></div>
        <div>Blood: <span id="blood">0</span></div>
        <div class="hp" id="hpDiv">HP: <span id="hp">20</span></div>
        <div class="gold" id="goldDiv">Gold: <span id="gold">0</span></div>
        <script src="object-vamp.js"></script>
    </body>
</html>

元素对象

代码语言:javascript
复制
function _elements() {
    this.counter;
    this.blood;
    this.gold;
    this.spanHP;
    this.divHP;
    this.bloodElement;
    this.raidElement;
    this.msg;
    this.goldDiv;
    this.day;
    this.cycle;
    this.goHunting;
    this.elm = function(name,props,style) {
        var el = document.createElement(name);
        for(var prop in props) if(props.hasOwnProperty(prop)) el[prop] = props[prop];
        for(var prop in style) if(style.hasOwnProperty(prop)) el.style[prop] = style[prop];
        return el;
    }
    this.showElement = function(id) {
        document.getElementById(id).className = "";
    }
    this.disableElement = function(id,txt) {
        document.getElementById(id).disabled = true;
        document.getElementById(id).innerHTML = txt;
    }
    this.alterHTML = function(id,txt) {
        document.getElementById(id).innerHTML = txt;
    }
    this.enableButton = function(id,e,txt) {
        var element = document.getElementById(id);
        if (engine.player.isDead() && engine.dayStatus != "night") 
            engine.elements.eventMsg("You are too weak to "+e+" until pure darkness allows it!");
        else {
            element.disabled = false;
            this.alterHTML(id,txt);
        }
    }
    this.addBorder = function(id) {
        document.getElementById(id).style.border = "1px solid black";
    }
    this.eventMsg = function(txt) {
        this.addBorder("msg");
        var temp = document.getElementById("msg");
        txt = "-"+txt+"<br />"+temp.innerHTML;
        temp.innerHTML = txt;
    }
    this.bloodButton = function() { 
        this.goHunting = this.elm("button",{innerHTML:"Hunt for Blood", id:"bloodButton"},{}); 
        document.body.appendChild(this.goHunting);
        this.goHunting.addEventListener("click",engine.player.hunt.bind());
    }
    this.raidButton = function() { 
        this.goRaiding = this.elm("button",{innerHTML:"Raid for Gold", id:"raidButton"},{}); 
        document.body.appendChild(this.goRaiding);
        this.goRaiding.addEventListener("click",engine.player.raid.bind());
    }
}

播放器对象

代码语言:javascript
复制
function _player() {
    this.hp = 20;
    this.hpMax = 20;
    this.bloodcount = 0;
    this.goldCount = 0;
    this.isDead = function() {
        if (this.hp <= 0) return true;
        else return false;
    }
    this.revive = function() {
        if (engine.dayStatus == "night") {
            engine.player.healDamage(1);
        }
    }
    this.healDamage = function(heal) {
        if ((this.hp+heal) > this.hpMax) this.hp = this.hpMax;
        else this.hp += heal;
    }
    this.triggerDeath = function(cause,bloodLoss) {
        engine.elements.eventMsg("You have died from: "+cause);
        engine.elements.eventMsg("Your death has cost you "+bloodLoss+" pints of your precious blood!");
        if (this.bloodcount < 20) this.bloodcount = 0;
        else this.bloodcount -= bloodLoss;
        engine.elements.alterHTML("blood",this.bloodcount);
    }
    this.dealDamage = function(dmg,type,bloodLossOnDeath) {
        if (!engine.firstHPLoss) {
            engine.firstHPLoss = true;
            engine.elements.showElement("hpDiv");
        }
        if ((this.hp-dmg) <= 0) {
            this.hp = 0;
            this.triggerDeath(type,bloodLossOnDeath);
        } else {
            this.hp -= dmg;
        }
    }
    this.hunt = function() {
        var bloodCollected = 0;
        engine.elements.alterHTML("bloodButton","Wait to hunt...");
        engine.elements.goHunting.disabled = true;
        if (engine.dayStatus == engine.statusCycle[0]) {
            this.dealDamage(10,"sunlight",20);
            engine.elements.eventMsg("Hunting in the daylight has hurt you! -10 HP!");
        } else {
            bloodCollected = 1*engine.multiplier;
            engine.player.bloodcount += bloodCollected;
            engine.elements.alterHTML("blood",engine.player.bloodcount);
            engine.elements.eventMsg("Your hunt yielded "+bloodCollected+" pint(s) of blood!");
            engine.player.healDamage(1);
            engine.elements.alterHTML("hp",engine.player.hp);
        }
    }
    this.raid = function() {
        engine.elements.showElement("goldDiv");
        var goldCollected;
        var hpLoss = 0;
        engine.elements.alterHTML("raidButton","Wait to raid...");
        engine.elements.goRaiding.disabled = true;
        if (engine.dayStatus == engine.statusCycle[0]) {
            engine.player.dealDamage(15,"sunlight");
            engine.elements.eventMsg("Raiding in the daylight has hurt you! -15 HP!");
        } else {
            hpLoss = Math.floor(Math.random()*(5-1+1)+1);
            goldCollected = Math.floor((Math.random()*100));
            engine.player.goldCount += goldCollected;
            engine.elements.alterHTML("gold",engine.player.goldCount);
            engine.elements.eventMsg("Your raid yielded "+goldCollected+" gold coins at the cost of "+hpLoss+"HP from the townspeople!");
            engine.player.dealDamage(hpLoss,"raiding",15);
            engine.elements.alterHTML("hp",engine.player.hp);
        }
    }
}

主机对象

代码语言:javascript
复制
function _engine() {
    this.count = 0;
    this.cycleFlag = false;
    this.firstHPLoss = false;
    this.raidFlag = false;
    this.multiplier = 1;
    this.dayStatus = "dusk";
    this.statusCycle = [
        "day",
        "dusk",
        "night",
        "dawn"
    ];
    this.huntStatus = {
        "day":0,
        "dusk":3,
        "night":4,
        "dawn":2
    };
    this.dayFlavor = [
        "The sun is bright outside..",
        "The sun is setting..",
        "The moon shines brightly..",
        "The sun is rising.."
    ];
    this.player = (function(){ return new _player(); }());
    this.elements = (function(){ return new _elements(); }());
    this.triggers = function(c) {
        if (c == 5) this.elements.bloodButton();
        if (!(c%1) && c > 5) this.elements.enableButton("bloodButton","hunt","Hunt for Blood");
        if (!(c%1) && engine.player.bloodcount > 5) this.elements.enableButton("raidButton","raid","Raid for Gold");
        if (engine.player.bloodcount >= 5 && !this.raidFlag) {
            this.elements.raidButton();
            this.raidFlag = true;
        }
        if (engine.player.bloodcount >= 10 && !this.cycleFlag) {
            this.initDayCycle();
            this.cycleFlag = true;
        }
        if (!(c%10) && this.cycleFlag) this.nextDayCycle();
    }
    this.initDayCycle = function() {
        engine.elements.showElement("divCycle");
        engine.elements.alterHTML("cycle",this.dayStatus);
        engine.multiplier = 3;
    }
    this.nextDayCycle = function() {
        var index = this.statusCycle.indexOf(this.dayStatus);
        var cycleNext = this.statusCycle[(index+1)];
        if (cycleNext) {
            this.dayStatus = cycleNext;
            this.multiplier = this.huntStatus[cycleNext];
        }
        else {
            this.dayStatus = this.statusCycle[0];
            this.multiplier = this.huntStatus[this.dayStatus];
        }
        engine.elements.alterHTML("cycle",this.dayStatus);
        var newIndex = this.statusCycle.indexOf(this.dayStatus);
        engine.elements.eventMsg(this.dayFlavor[newIndex]);
    }
}

最后给出了初始化和间隔循环。

代码语言:javascript
复制
var engine = (function(){ return new _engine(); }());

setInterval(function() { 
    engine.count++;
    engine.elements.alterHTML("counter",engine.count);
    engine.triggers(engine.count);
    if (engine.player.isDead()) {
        engine.elements.disableElement("bloodButton","You are dead..");
        engine.elements.disableElement("raidButton","You are dead..");
        engine.player.revive();
    }
}, 1000);
EN

回答 2

Code Review用户

回答已采纳

发布于 2013-11-24 11:23:29

使用原型

我注意到你这样做:

代码语言:javascript
复制
function _player() {
    ...
    this.isDead = function() {
        if (this.hp <= 0) return true;
        else return false;
    }

这个问题的一个问题是,对于_player的每个实例,您都要为构造函数的每个实例创建方法。这会消耗掉记忆。

您可以做的是使用原型继承。这个继承模型通过在实例之间“共享”构造函数的原型来工作。这样,方法只声明一次,但在实例之间共享。

代码语言:javascript
复制
function _player(){...}

_player.prototype = {
  isDead : function(){...},
  revive : function(){...},
  ...
};

var p1 = new _player();
var p2 = new _player();

// Both _player instances use the same revive method
// but operate on different _player objects
p1.revive();
p2.revive();

解耦代码

您的代码是紧密耦合的。这意味着如果我破坏了代码的一部分,其他部分也会中断。

例如,以下代码:

代码语言:javascript
复制
this.eventMsg = function(txt) {
    this.addBorder("msg");
    var temp = document.getElementById("msg");
    txt = "-"+txt+"<br />"+temp.innerHTML;
    temp.innerHTML = txt;
}

这假设存在一个元素#msg。如果我取出了HTML的那一部分或者偶然地重命名了它呢?temp将是undefined,访问temp.innerHTML会抛出一个错误。

以及以下代码:

代码语言:javascript
复制
this.player = (function(){ return new _player(); }());
this.elements = (function(){ return new _elements(); }());

您的代码假定存在_player_elements。如果我将它们取出来或重命名,您也将查看这段代码并将其重命名。不太实际。而且,这假设_player_elements只是一个整体。如果你需要更多的球员呢?还是元素?

与配准

的解耦

注册允许通过将对象注册到系统中来实现解耦,而不是让系统硬编码所需的对象。这样,如果没有注册对象(因为它们被删除或其他原因),那么代码就不会中断。下面是一个简单的玩家示例:

代码语言:javascript
复制
//Engine.js
function Engine(){
  // We can have more than one player
  this.players = [];
}

Engine.prototype = {
  // A simple register
  registerPlayer : function(player){
    this.players.push(player)
  },
  doSomethingWithPlayers : function(){
    for(var i = 0; i < this.players.length; i++){
      // Do something to registered players
      // No players registered means code won't run
    }
  }
}

//Registration.js 
var player1 = new Player();
var player2 = new Player();
var engine = new Engine();

engine.register(player1);
engine.register(player2);
engine.doSomethingWithPlayers(); //Some mass-heal effect?

因此,在上面的示例中,我们将player对象“注册”到引擎中,而不是硬编码到引擎中。如你所见,好处是显而易见的:

  • 引擎代码中没有使用player构造函数的痕迹,这意味着删除Player.js不会破坏引擎代码。
  • 你可以通过注册注册来注册任意数量的玩家,这基本上只是将玩家存储在一个数组中。

解耦事件

我注意到您使用时钟机制来启动与时间相关的事件。但是,您是硬编码事件以及对象:

代码语言:javascript
复制
this.triggers = function(c) {
    if (c == 5) this.elements.bloodButton();
    if (!(c%1) && c > 5) this.elements.enableButton("bloodButton","hunt","Hunt for Blood");
    if (!(c%1) && engine.player.bloodcount > 5) this.elements.enableButton("raidButton","raid","Raid for Gold");
    if (engine.player.bloodcount >= 5 && !this.raidFlag) {
        this.elements.raidButton();
        this.raidFlag = true;
    }
    if (engine.player.bloodcount >= 10 && !this.cycleFlag) {
        this.initDayCycle();
        this.cycleFlag = true;
    }
    if (!(c%10) && this.cycleFlag) this.nextDayCycle();
}

你能做的就是某种"pub-sub“模式或事件侦听器。您可以监听来自对象的事件并作出相应的反应。基本上,该机制将函数注册到数组中,并在运行它们的时候运行它们。实现起来非常简单,我自己建了一个小图书馆

代码语言:javascript
复制
//Engine.js
function Engine(){
  this.count = 0;
  this.timer = null;
}

//Refer to my library for a simple Event Emitter implementation

Engine.prototype.run = function(){
  //Save the context
  var instance = this;

  this.timer = setTimeout(function(){
    // Run registered events
    // If they return true, then run the corresponding handlers
  },1000);
}

//Main script

var engine = new Engine();
var player = new Player();

// Register an event that determines when it happens
engine.registerEvent('nextDayCycle',function(cycle){

  //nextDayCycle event is triggered on this tick when this returns true
  return (!(cycle%10) && this.cycleFlag);
});

// Register a handler that runs when an event happens
engine.on('nextDayCycle',function(){

  // Runs on every nextDayCycle trigger
  players.hunt();
});

engine.run();

正如您所看到的,您可以将事件与保存时间管理机制的引擎分离。

票数 4
EN

Code Review用户

发布于 2013-11-24 11:26:42

除了约瑟夫以前说过的话外,以下是一些小小的建议:

总体而言:

  1. 请为所有对象原型创建名称空间。即使您现在不想使用库,但是有一天当您想要这样做的时候,您肯定不想被现在不使用名称空间的决定所困扰。

元素对象:

  1. 由于我来自Rails背景,所以我喜欢将我的原型命名为单数,将它们的集合命名为复数。所以,我更喜欢_element而不是_elements
  2. elm的名称描述性不够。也许,createElement会做的更公正的方法。
  3. 目前还不清楚如何清空元素的className在ShowElement中显示它。你需要在这里质疑你的假设。是否存在要删除的特定类或显示元素的所有类?
  4. 为什么DisableElement也要改变元素的HTML?你似乎已经有了另一种方法。
  5. enableButtonbloodButtonraidButton突然提到了全局变量引擎。我以为我们要去掉全局变量。您可能希望使用bind()将这些函数绑定到全局引擎对象。
票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/35989

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档