0

0

JavaScript 游戏中的面向对象的设计

php中文网

php中文网

发布时间:2016-05-17 09:08:28

|

1542人浏览过

|

来源于php中文网

原创

       简介

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

       JavaScript 中的 OPP 的概述

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

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

       原型继承

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

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


// constructor functionfunction 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 practiceMyExample.getTrue;// this is undefined—the getTrue method is a part of the MyExample prototype, // not the function itselfvar example = new MyExample();// example is now an object whose prototype is MyExample.prototypeexample.getTrue; // evaluates to a functionexample.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下一页

相关文章

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Golang 网络安全与加密实战
Golang 网络安全与加密实战

本专题系统讲解 Golang 在网络安全与加密技术中的应用,包括对称加密与非对称加密(AES、RSA)、哈希与数字签名、JWT身份认证、SSL/TLS 安全通信、常见网络攻击防范(如SQL注入、XSS、CSRF)及其防护措施。通过实战案例,帮助学习者掌握 如何使用 Go 语言保障网络通信的安全性,保护用户数据与隐私。

0

2026.01.29

俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

408

2026.01.28

包子漫画在线官方入口大全
包子漫画在线官方入口大全

本合集汇总了包子漫画2026最新官方在线观看入口,涵盖备用域名、正版无广告链接及多端适配地址,助你畅享12700+高清漫画资源。阅读专题下面的文章了解更多详细内容。

136

2026.01.28

ao3中文版官网地址大全
ao3中文版官网地址大全

AO3最新中文版官网入口合集,汇总2026年主站及国内优化镜像链接,支持简体中文界面、无广告阅读与多设备同步。阅读专题下面的文章了解更多详细内容。

241

2026.01.28

php怎么写接口教程
php怎么写接口教程

本合集涵盖PHP接口开发基础、RESTful API设计、数据交互与安全处理等实用教程,助你快速掌握PHP接口编写技巧。阅读专题下面的文章了解更多详细内容。

8

2026.01.28

php中文乱码如何解决
php中文乱码如何解决

本文整理了php中文乱码如何解决及解决方法,阅读节专题下面的文章了解更多详细内容。

13

2026.01.28

Java 消息队列与异步架构实战
Java 消息队列与异步架构实战

本专题系统讲解 Java 在消息队列与异步系统架构中的核心应用,涵盖消息队列基本原理、Kafka 与 RabbitMQ 的使用场景对比、生产者与消费者模型、消息可靠性与顺序性保障、重复消费与幂等处理,以及在高并发系统中的异步解耦设计。通过实战案例,帮助学习者掌握 使用 Java 构建高吞吐、高可靠异步消息系统的完整思路。

10

2026.01.28

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

24

2026.01.27

拼多多赚钱的5种方法 拼多多赚钱的5种方法
拼多多赚钱的5种方法 拼多多赚钱的5种方法

在拼多多上赚钱主要可以通过无货源模式一件代发、精细化运营特色店铺、参与官方高流量活动、利用拼团机制社交裂变,以及成为多多进宝推广员这5种方法实现。核心策略在于通过低成本、高效率的供应链管理与营销,利用平台社交电商红利实现盈利。

124

2026.01.26

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.4万人学习

Node.js 教程
Node.js 教程

共57课时 | 9.6万人学习

CSS3 教程
CSS3 教程

共18课时 | 5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号