搜索
首页web前端H5教程JavaScript 游戏中的面向对象的设计

       简介

       在本文中,您将了解 JavaScript 中的 OOP,来探索原型继承模型和经典继承模型。举例说明游戏中能够从 OOP 设计的结构和可维护性中获得极大利益的模式。我们的最终目标是让每一块代码都成为人类可读的代码,并代表一种想法和一个目的,这些代码的结合超越了指令和算法的集合,成为一个精致的艺术品。

       JavaScript 中的 OPP 的概述

       OOP 的目标就是提供数据抽象、模块化、封装、多态性和继承。通过 OOP,您可以在代码编写中抽象化代码的理念,从而提供优雅、可重用和可读的代码,但这会消耗文件计数、行计数和性能(如果管理不善)。

       过去,游戏开发人员往往会避开纯 OOP 方式,以便充分利用 CPU 周期的性能。很多 JavaScript 游戏教程采用的都是非 OOP 方式,希望能够提供一个快速演示,而不是提供一种坚实的基础。与其他游戏的开发人员相比,JavaScript 开发人员面临不同的问题:内存是非手动管理的,且 JavaScript 文件在全局的上下文环境中执行,这样一来,无头绪的代码、命名空间的冲突和迷宫式的 if/else 语句可能会导致可维护性的噩梦。为了从 JavaScript 游戏的开发中获得最大的益处,请遵循 OOP 的最佳实践,显著提高未来的可维护性、开发进度和游戏的表现能力。

       原型继承

       与使用经典继承的语言不同,在 JavaScript 中,没有内置的类结构。函数是 JavaScript 世界的一级公民,并且,与所有用户定义的对象类似,它们也有原型。用 new 关键字调用函数实际上会创建该函数的一个原型对象副本,并使用该对象作为该函数中的关键字 this 的上下文。清单 1 给出了一个例子。

       清单 1. 用原型构建一个对象


// constructor function
function MyExample() {
// property of an instance when used with the 'new' keyword
this.isTrue = true;
};

MyExample.prototype.getTrue = function() {
return this.isTrue;
}

MyExample();
// here, MyExample was called in the global context,
// so the window object now has an isTrue property—this is NOT a good practice

MyExample.getTrue;
// this is undefined—the getTrue method is a part of the MyExample prototype,
// not the function itself

var example = new MyExample();
// example is now an object whose prototype is MyExample.prototype

example.getTrue; // evaluates to a function
example.getTrue(); // evaluates to true because isTrue is a property of the
// example instance

       依照惯例,代表某个类的函数应该以大写字母开头,这表示它是一个构造函数。该名称应该能够代表它所创建的数据结构。

       创建类实例的秘诀在于综合新的关键字和原型对象。原型对象可以同时拥有方法和属性,如 清单 2 所示。

       清单 2. 通过原型化的简单继承

// Base class
function Character() {};

Character.prototype.health = 100;

Character.prototype.getHealth = function() {
return this.health;
}

// Inherited classes

function Player() {
this.health = 200;
}

Player.prototype = new Character;

function Monster() {}

Monster.prototype = new Character;

var player1 = new Player();

var monster1 = new Monster();

player1.getHealth(); // 200- assigned in constructor

monster1.getHealth(); // 100- inherited from the prototype object

       为一个子类分配一个父类需要调用 new 并将结果分配给子类的 prototype 属性,如 清单 3 所示。因此,明智的做法是保持构造函数尽可能的简洁和无副作用,除非您想要传递类定义中的默认值。

       如果您已经开始尝试在 JavaScript 中定义类和继承,那么您可能已经意识到该语言与经典 OOP 语言的一个重要区别:如果已经覆盖这些方法,那么没有 super 或 parent 属性可用来访问父对象的方法。对此有一个简单的解决方案,但该解决方案违背了 “不要重复自己 (DRY)” 原则,而且很有可能是如今有很多库试图模仿经典继承的最重要的原因。

       清单 3. 从子类调用父方法

function ParentClass() {
this.color = 'red';
this.shape = 'square';
}

function ChildClass() {
ParentClass.call(this); // use 'call' or 'apply' and pass in the child
// class's context
this.shape = 'circle';
}

ChildClass.prototype = new ParentClass(); // ChildClass inherits from ParentClass

ChildClass.prototype.getColor = function() {
return this.color; // returns "red" from the inherited property
};

       在 清单 3 中, color 和 shape 属性值都不在原型中,它们在 ParentClass 构造函数中赋值。ChildClass 的新实例将会为其形状属性赋值两次:一次作为 ParentClass 构造函数中的 "squre",一次作为 ChildClass 构造函数中的 "circle"。将类似这些赋值的逻辑移动到原型将会减少副作用,让代码变得更容易维护。

       在原型继承模型中,可以使用 JavaScript 的 call 或 apply 方法来运行具有不同上下文的函数。虽然这种做法十分有效,可以替代其他语言的 super 或 parent,但它带来了新的问题。如果需要通过更改某个类的名称、它的父类或父类的名称来重构这个类,那么现在您的文本文件中的很多地方都有了这个 ParentClass 。随着您的类越来越复杂,这类问题也会不断增长。更好的一个解决方案是让您的类扩展一个基类,使代码减少重复,尤其在重新创建经典继承时。

       经典继承

       虽然原型继承对于 OOP 是完全可行的,但它无法满足优秀编程的某些目标。比如如下这些问题:

  • 它不是 DRY 的。类名称和原型随处重复,让读和重构变得更为困难。
  • 构造函数在原型化期间调用。一旦开始子类化,就将不能使用构造函数中的一些逻辑。
  • 没有为强封装提供真正的支持。
  • 没有为静态类成员提供真正的支持。

       很多 JavaScript 库试图实现更经典的 OOP 语法来解决上述问题。其中一个更容易使用的库是 Dean Edward 的 Base.js,它提供了下列有用特性:

  • 所有原型化都是用对象组合(可以在一条语句中定义类和子类)完成的。
  • 用一个特殊的构造函数为将在创建新的类实例时运行的逻辑提供一个安全之所。
  • 它提供了静态类成员支持。
  • 它对强封装的贡献止步于让类定义保持在一条语句内(精神封装,而非代码封装)。

       其他库可以提供对公共和私有方法和属性(封装)的更严格支持,Base.js 提供了一个简洁、易用、易记的语法。

       清单 4 给出了对 Base.js 和经典继承的简介。该示例用一个更为具体的 RobotEnemy 类扩展了抽象 Enemy 类的特性。

       清单 4. 对 Base.js 和经典继承的简介

// create an abstract, basic class for all enemies
// the object used in the .extend() method is the prototype
var Enemy = Base.extend({
health: 0,
damage: 0,
isEnemy: true,

constructor: function() {
// this is called every time you use "new"
},

attack: function(player) {
player.hit(this.damage); // "this" is your enemy!
}
});

// create a robot class that uses Enemy as its parent
//
var RobotEnemy = Enemy.extend({
health: 100,
damage: 10,

// because a constructor isn't listed here,
// Base.js automatically uses the Enemy constructor for us

attack: function(player) {
// you can call methods from the parent class using this.base
// by not having to refer to the parent class
// or use call / apply, refactoring is easier
// in this example, the player will be hit
this.base(player);

// even though you used the parent class's "attack"
// method, you can still have logic specific to your robot class
this.health += 10;
}
});

       游戏设计中的 OOP 模式

       基本的游戏引擎不可避免地依赖于两个函数:update 和 render。render 方法通常会根据 setInterval 或 polyfill 进行 requestAnimationFrame,比如 Paul Irish 使用的这个(请参阅 参考资料)。使用 requestAnimationFrame 的好处是仅在需要的时候调用它。它按照客户监视器的刷新频率运行(对于台式机,通常是一秒 60 次),此外,在大多数浏览器中,通常根本不会运行它,除非游戏所在的选项卡是活动的。它的优势包括:

  • 在用户没有盯着游戏时减少客户机上的工作量
  • 节省移动设备上的用电。
  • 如果更新循环与呈现循环有关联,那么可以有效地暂停游戏。

       出于这些原因,与 setInterval 相比,requestAnimationFrame 一直被认为是 “客户友好” 的 “好公民”。

       将 update 循环与 render 循环捆绑在一起会带来新的问题:要保持游戏动作和动画的速度相同,而不管呈现循环的运行速度是每秒 15 帧还是 60 帧。这里要掌握的技巧是在游戏中建立一个时间单位,称为滴答 (tick),并传递自上次更新后过去的时间量。然后,就可以将这个时间量转换成滴答数量,而模型、物理引擎和其他依赖于时间的游戏逻辑可以做出相应的调整。比如,一个中毒的玩家可能会在每个滴答接受 10 次损害,共持续 10 个滴答。如果呈现循环运行太快,那么玩家在某个更新调用上可能不会接受损害。但是,如果垃圾回收在最后一个导致过去 1 个半滴答的呈现循环上生效,那么您的逻辑可能会导致 15 次损害。

       另一个方式是将模型更新从视图循环中分离出来。在包含很多动画或对象或是绘制占用了大量资源的游戏中,更新循环与 render 循环的耦合会导致游戏完全慢下来。在这种情况下,update 方法能够以设置好的间隔运行(使用 setInterval),而不管 requestAnimationFrame 处理程序何时会触发,以及多久会触发一次。在这些循环中花费的时间实际上都花费在了呈现步骤中,所以,如果只有 25 帧被绘制到屏幕上,那么游戏会继续以设置好的速度运行。在这两种情况下,您可能都会想要计算更新周期之间的时间差;如果一秒更新 60 次,那么完成函数更新最多有 16ms 的时间。如果运行此操作的时间更长(或如果运行了浏览器的垃圾回收),那么游戏还是会慢下来。 清单 5 显示了一个示例。

       清单 5. 带有 render 和 update 循环的基本应用程序类

// requestAnim shim layer by Paul Irish
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback, /* DOMElement */ element){
window.setTimeout(callback, 1000 / 60);
};
})();

var Engine = Base.extend({
stateMachine: null, // state machine that handles state transitions
viewStack: null, // array collection of view layers,
// perhaps including sub-view classes
entities: null, // array collection of active entities within the system
// characters,
constructor: function() {
this.viewStack = []; // don't forget that arrays shouldn't be prototype
// properties as they're copied by reference
this.entities = [];

// set up your state machine here, along with the current state
// this will be expanded upon in the next section

// start rendering your views
this.render();
// start updating any entities that may exist
setInterval(this.update.bind(this), Engine.UPDATE_INTERVAL);
},

render: function() {
requestAnimFrame(this.render.bind(this));
for (var i = 0, len = this.viewStack.length; i // delegate rendering logic to each view layer
(this.viewStack[i]).render();
}
},

update: function() {
for (var i = 0, len = this.entities.length; i // delegate update logic to each entity
(this.entities[i]).update();
}
}
},

// Syntax for Class "Static" properties in Base.js. Pass in as an optional
// second argument to.extend()
{
UPDATE_INTERVAL: 1000 / 16
});

       如果您对 JavaScript 中 this 的上下文不是很熟悉,请注意 .bind(this) 被使用了两次:一次是在 setInterval 调用中的匿名函数上,另一次是在 requestAnimFrame 调用中的 this.render.bind() 上。setInterval 和 requestAnimFrame 都是函数,而非方法;它们属于这个全局窗口对象,不属于某个类或身份。因此,为了让此引擎的呈现和更新方法的 this 引用我们的 Engine 类的实例,调用 .bind(object) 会迫使此函数中的 this 与正常情况表现不同。如果您支持的是 Internet Explorer 8 或其更早版本,则需要添加一个 polyfill,将它用于绑定。


12下一页
声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
H5:工具,框架和最佳实践H5:工具,框架和最佳实践Apr 11, 2025 am 12:11 AM

H5开发需要掌握的工具和框架包括Vue.js、React和Webpack。1.Vue.js适用于构建用户界面,支持组件化开发。2.React通过虚拟DOM优化页面渲染,适合复杂应用。3.Webpack用于模块打包,优化资源加载。

HTML5的遗产:当前了解H5HTML5的遗产:当前了解H5Apr 10, 2025 am 09:28 AM

HTML5hassignificantlytransformedwebdevelopmentbyintroducingsemanticelements,enhancingmultimediasupport,andimprovingperformance.1)ItmadewebsitesmoreaccessibleandSEO-friendlywithsemanticelementslike,,and.2)HTML5introducednativeandtags,eliminatingthenee

H5代码:可访问性和语义HTMLH5代码:可访问性和语义HTMLApr 09, 2025 am 12:05 AM

H5通过语义化元素和ARIA属性提升网页的可访问性和SEO效果。1.使用、、等元素组织内容结构,提高SEO。2.ARIA属性如aria-label增强可访问性,辅助技术用户可顺利使用网页。

H5与HTML5相同吗?H5与HTML5相同吗?Apr 08, 2025 am 12:16 AM

"h5"和"HTML5"在大多数情况下是相同的,但它们在某些特定场景下可能有不同的含义。1."HTML5"是W3C定义的标准,包含新标签和API。2."h5"通常是HTML5的简称,但在移动开发中可能指基于HTML5的框架。理解这些区别有助于在项目中准确使用这些术语。

H5的功能是什么?H5的功能是什么?Apr 07, 2025 am 12:10 AM

H5,即HTML5,是HTML的第五个版本,它为开发者提供了更强大的工具集,使得创建复杂的网页应用变得更加简单。H5的核心功能包括:1)元素允许在网页上绘制图形和动画;2)语义化标签如、等,使网页结构清晰,利于SEO优化;3)新API如GeolocationAPI,支持基于位置的服务;4)跨浏览器兼容性需要通过兼容性测试和Polyfill库来确保。

h5链接怎么做h5链接怎么做Apr 06, 2025 pm 12:39 PM

如何创建 H5 链接?确定链接目标:获取 H5 页面或应用程序的 URL。创建 HTML 锚点:使用 <a> 标记创建锚点并指定链接目标URL。设置链接属性(可选):根据需要设置 target、title 和 onclick 属性。添加到网页:将 HTML 锚点代码添加到希望链接出现的网页中。

h5兼容问题怎么解决h5兼容问题怎么解决Apr 06, 2025 pm 12:36 PM

解决 H5 兼容问题的方法包括:使用响应式设计,允许网页根据屏幕尺寸调整布局。采用跨浏览器测试工具,在发布前测试兼容性。使用 Polyfill,为旧浏览器提供对新 API 的支持。遵循 Web 标准,使用有效的代码和最佳实践。使用 CSS 预处理器,简化 CSS 代码并提高可读性。优化图像,减小网页大小并加快加载速度。启用 HTTPS,确保网站的安全性。

h5怎么生成链接h5怎么生成链接Apr 06, 2025 pm 12:33 PM

h5页面可以通过两种方法生成链接:手动创建链接或使用短链接服务。通过手动创建,只需复制h5页面的URL即可;通过短链接服务,需将URL粘贴到服务中,然后获取缩短的URL。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
3 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器

SublimeText3 英文版

SublimeText3 英文版

推荐:为Win版本,支持代码提示!